With external APIs, Python developers can implement data retrieval, financial information, and image processing features without building everything from scratch. However, “free to use” and “safe to use in production” are two entirely different things.
This article introduces 10 practical free APIs for Python with production-ready code, Rate Limit details, and safety design patterns. Beyond a simple API list, we cover the design philosophy professionals use when working with external APIs.
Target level: Beginner to intermediate (able to use requests). Prerequisite:
pip install requests
For secure API key management, also see “10 Python Security Implementation Patterns.”
Basic API Call Template
Before diving into individual APIs, let’s establish the baseline template that every API call should follow. In production, these four points are the minimum standard:
- Set a timeout — prevent your program from waiting indefinitely
- Check status codes — handle non-200 responses properly
- Handle exceptions — cope with network failures and timeouts
- Don’t assume JSON — avoid crashes when the response is HTML or text
import requests
url = "https://api.example.com/data"
try:
response = requests.get(url, timeout=5)
response.raise_for_status()
data = response.json()
print(data)
except requests.exceptions.Timeout:
print("Timeout: server not responding")
except requests.exceptions.HTTPError as e:
print(f"HTTP error: {e.response.status_code}")
except requests.exceptions.RequestException as e:
print(f"Request error: {e}")
except ValueError:
print("JSON decode error: response is not JSON")
Keep this template as a shared utility function in your project. The code examples below omit error handling for brevity, but always apply this pattern in production code.
Free API Comparison Table
| API | Purpose | API Key | Rate Limit | Commercial | Difficulty |
|---|---|---|---|---|---|
| ① Open-Meteo | Weather data | None | Relaxed | Yes | ★☆☆ |
| ② ExchangeRate | Forex rates | None | 1,500/day | Yes | ★☆☆ |
| ③ CoinGecko | Crypto prices | None | 10–30/min | Check ToS | ★☆☆ |
| ④ JSONPlaceholder | Test/Mock | None | None | — | ★☆☆ |
| ⑤ REST Countries | Country info | None | Relaxed | Yes | ★☆☆ |
| ⑥ The Cat API | Cat images | Optional | 10/min | Yes | ★☆☆ |
| ⑦ IP-API | IP geolocation | None | 45/min | Paid only | ★☆☆ |
| ⑧ Advice Slip | Random quotes | None | 1/2sec | Yes | ★☆☆ |
| ⑨ PokeAPI | Pokémon data | None | Relaxed | Yes | ★★☆ |
| ⑩ Numbers API | Number trivia | None | Relaxed | Yes | ★☆☆ |
① Open-Meteo — No-Key Weather API
Open-Meteo is a completely free, no-API-key weather data API. It provides current weather, temperature, wind speed, precipitation, and forecasts up to 16 days ahead. Commercial use is permitted, making it the go-to choice for weather-related tools.
import requests
url = "https://api.open-meteo.com/v1/forecast"
params = {
"latitude": 35.6762,
"longitude": 139.6503,
"current_weather": True
}
response = requests.get(url, params=params, timeout=5)
data = response.json()
weather = data["current_weather"]
print(f"Temp: {weather['temperature']}°C")
print(f"Wind: {weather['windspeed']} km/h")
The biggest advantage is instant data access without any authentication. No API key registration or account creation needed. Change the latitude/longitude and you get weather data from anywhere in the world.
Open-Meteo is an open-source project using data from European meteorological agencies. Accuracy is high even for locations in Asia and the Americas, making it suitable for both personal projects and commercial tools.
② ExchangeRate API — Forex Rates
ExchangeRate API provides free foreign exchange rates for major currencies. Useful for currency conversion tools, price comparison sites, and financial dashboards. No API key required, with 1,500 requests per day.
import requests
url = "https://open.er-api.com/v6/latest/USD"
response = requests.get(url, timeout=5)
data = response.json()
jpy_rate = data["rates"]["JPY"]
print(f"1 USD = {jpy_rate} JPY")
# Currency conversion example
usd_amount = 100
jpy_amount = usd_amount * jpy_rate
print(f"{usd_amount} USD = {jpy_amount:.0f} JPY")
A critical consideration with financial data is update frequency. This API updates roughly once per day, so it’s unsuitable for real-time trading. It provides sufficient accuracy for approximate currency conversion and daily price references.
When displaying exchange rates to users, always state “rates are approximate.” Discrepancies with real-time rates are possible, and this data should not be used as the basis for financial transactions.
③ CoinGecko API — Crypto Prices & Market Data
CoinGecko provides cryptocurrency prices, market caps, trading volumes, and rankings. The free plan is surprisingly powerful and widely used for crypto-related tool development.
import requests
url = "https://api.coingecko.com/api/v3/simple/price"
params = {
"ids": "bitcoin,ethereum",
"vs_currencies": "usd,eur",
"include_24hr_change": "true"
}
response = requests.get(url, params=params, timeout=5)
data = response.json()
btc = data["bitcoin"]
print(f"BTC: ${btc['usd']:,.0f} (24h: {btc['usd_24h_change']:.1f}%)")
CoinGecko’s Rate Limit on the free plan is roughly 10–30 requests per minute. Since crypto prices are volatile, caching is essential. Fetch data every minute, store locally, and serve from cache.
CoinGecko supports fetching multiple currencies in a single request. Use ids=bitcoin,ethereum,solana with comma separation to dramatically reduce request count.
④ JSONPlaceholder — The Standard for API Practice
JSONPlaceholder is a mock API designed for REST API learning and testing. It provides simulated data for users, posts, comments, and albums, widely used for API development prototyping and frontend testing.
import requests
# Get posts
url = "https://jsonplaceholder.typicode.com/posts"
params = {"_limit": 3}
response = requests.get(url, params=params, timeout=5)
posts = response.json()
for post in posts:
print(f"[{post['id']}] {post['title']}")
# Practice POST requests
new_post = {"title": "Test", "body": "Hello", "userId": 1}
res = requests.post(url, json=new_post, timeout=5)
print(f"Created: status={res.status_code}")
The biggest advantage is support for all HTTP methods: GET, POST, PUT, PATCH, DELETE. POST and DELETE requests don’t modify actual data — responses are simulated, making it safe to experiment. Ideal for onboarding new engineers and testing API clients.
If you need a mock API for your own project, use JSONPlaceholder’s design as reference and set up a local mock server with json-server (npm package).
⑤ REST Countries — Country Information at a Glance
REST Countries provides basic information about every country in the world — population, area, capital, flag, currency, languages, and region — all in JSON format. Great for geography education tools, statistics dashboards, and internationalized applications.
import requests
url = "https://restcountries.com/v3.1/name/japan"
response = requests.get(url, timeout=5)
data = response.json()[0]
print(f"Country: {data['name']['common']}")
print(f"Capital: {data['capital'][0]}")
print(f"Population: {data['population']:,}")
print(f"Region: {data['region']}")
print(f"Flag: {data['flag']}")
This API is ideal for caching since country data rarely changes. Fetch all data once and store locally — no need to call the API again. Loading all data into memory at startup is the most efficient approach.
The /v3.1/all endpoint returns data for all 250+ countries at once. Use field filters (?fields=name,population,capital) to reduce response size and save bandwidth.
⑥ The Cat API — Random Cat Images
The Cat API returns random cat images. While it seems playful, it’s genuinely useful in production for UI image display testing, dynamic placeholder generation, and API client testing.
import requests
url = "https://api.thecatapi.com/v1/images/search"
params = {"limit": 3}
response = requests.get(url, params=params, timeout=5)
images = response.json()
for img in images:
print(f"URL: {img['url']} (size: {img['width']}x{img['height']})")
Basic features work without an API key, but registering for a free key increases rate limits and unlocks breed filters and favorites.
When testing “dynamic image display” features in frontend development, this API provides a more realistic test environment than static dummy images.
⑦ IP-API — IP Geolocation
IP-API returns geolocation data from IP addresses — country, region, city, ISP, and timezone. Used for access analytics, region-based content display, and security monitoring.
import requests
url = "http://ip-api.com/json/"
response = requests.get(url, timeout=5)
data = response.json()
print(f"Country: {data['country']}")
print(f"Region: {data['regionName']}")
print(f"City: {data['city']}")
print(f"ISP: {data['isp']}")
print(f"Timezone: {data['timezone']}")
The free tier is HTTP only (HTTPS requires a paid plan). For production environments requiring HTTPS, consider upgrading or switching to alternatives like ipinfo.io. Commercial use also requires the paid plan.
Rate limit is 45 requests per minute. For batch IP lookups, use the http://ip-api.com/batch endpoint for better efficiency.
⑧ Advice Slip API — Random Quotes
Advice Slip API returns random advice and quotes in English. Useful for UI test displays, daily message features, and chatbot embellishments.
import requests
url = "https://api.adviceslip.com/advice"
response = requests.get(url, timeout=5)
data = response.json()
advice = data["slip"]["advice"]
print(f"Today's advice: {advice}")
This API features a minimalist endpoint design — just /advice (random) and /advice/{id} (by ID). A 2-second interval limit applies between consecutive calls.
APIs with “one endpoint, one response” designs like this serve as textbook references for API design. When building your own API, starting this simple is a production best practice.
⑨ PokeAPI — Perfect for Learning Structured Data
PokeAPI provides Pokémon names, types, stats, and images. Beyond game data, its structure makes it excellent for learning nested JSON processing, pagination, and relational data handling — patterns you’ll encounter in every production API.
import requests
url = "https://pokeapi.co/api/v2/pokemon/pikachu"
response = requests.get(url, timeout=5)
data = response.json()
print(f"Name: {data['name']}")
print(f"Type: {data['types'][0]['type']['name']}")
print(f"HP: {data['stats'][0]['base_stat']}")
print(f"Image: {data['sprites']['front_default']}")
PokeAPI responses are deeply nested, requiring access patterns like data["types"][0]["type"]["name"]. This is common in production APIs (Stripe, Twilio, AWS SDK), so practicing here makes real-world API integration smoother.
PokeAPI has 1,000+ entries with pagination support (?offset=20&limit=20). The list → detail pattern directly applies to e-commerce and social media API design.
⑩ Numbers API — Number Trivia
Numbers API returns trivia and fun facts about any number. Useful for educational sites, quiz tools, and UI embellishment with daily content.
import requests
number = 42
url = f"http://numbersapi.com/{number}?json"
response = requests.get(url, timeout=5)
data = response.json()
print(f"{number}: {data['text']}")
# e.g.: 42: 42 is the answer to the Ultimate Question ...
Types include trivia (default), math, date, and year. For example, http://numbersapi.com/3/14/date?json returns trivia about March 14th.
Numbers API is HTTP only — no HTTPS support. For production use, either avoid it or relay requests through your server and return HTTPS responses to clients.
Rate Limits and Caching Strategy
The most dangerous pitfall with free APIs is exceeding rate limits. Free services have strict limits, and violations result in IP bans that can last hours or days.
Typical rate limit patterns:
| Pattern | Example | Use Case |
|---|---|---|
| Per second | 1/sec | Real-time data |
| Per minute | 60/min | Standard APIs |
| Per day | 1,000/day | Data retrieval |
The solution is caching. Store API responses locally and serve from cache within a TTL window.
import requests
import time
import json
import os
CACHE_FILE = "cache.json"
CACHE_TTL = 300 # 5 minutes
def get_with_cache(url):
if os.path.exists(CACHE_FILE):
with open(CACHE_FILE) as f:
cache = json.load(f)
if url in cache and time.time() - cache[url]["time"] < CACHE_TTL:
return cache[url]["data"]
response = requests.get(url, timeout=5)
data = response.json()
cache = {}
if os.path.exists(CACHE_FILE):
with open(CACHE_FILE) as f:
cache = json.load(f)
cache[url] = {"data": data, "time": time.time()}
with open(CACHE_FILE, "w") as f:
json.dump(cache, f)
return data
In production, use Redis or memcached instead of file caching. Django provides django.core.cache, and FastAPI has aiocache for built-in caching support.
Essential Safety Design Patterns
Production code using external APIs requires these five design patterns:
| Pattern | Purpose | Risk if Missing |
|---|---|---|
| timeout | Prevent infinite wait | Program freezes |
| retry | Handle transient failures | Processing halts |
| rate control | Respect API limits | IP ban |
| fallback | Handle API outages | Service down |
| cache | Reduce requests | Limit exceeded |
A combined production pattern:
import requests
import time
def safe_api_call(url, max_retries=3, delay=1):
for attempt in range(max_retries):
try:
response = requests.get(url, timeout=5)
if response.status_code == 429: # Rate Limit
wait = int(response.headers.get("Retry-After", delay * 2))
time.sleep(wait)
continue
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException:
if attempt < max_retries - 1:
time.sleep(delay * (attempt + 1))
continue
return None # fallback
return None
Remember this production principle: external APIs are unreliable by default. Outages, spec changes, latency spikes, limit changes — these aren’t “might happen” scenarios, they’re “will happen.” Professionals design with the assumption that external APIs cannot be trusted.
For more on secure design, see “10 Python Security Implementation Patterns.”
Common Mistakes and Terms of Service
Know the failure patterns beginners commonly hit:
| Mistake | Result | Solution |
|---|---|---|
| API calls in a loop | IP ban | sleep + cache |
| No caching | Rate limit exceeded | File/Redis cache |
| No error handling | Program crash | try/except required |
| No timeout | Program freezes | timeout=5 as standard |
| Ignoring ToS | Legal risk | Always read Terms |
Always check the Terms of Service. Even free APIs may have restrictions:
- No commercial use — personal use only
- Attribution required — “Powered by ○○” display obligation
- No redistribution — cannot republish raw data on another service
- HTTPS restrictions — free tier HTTP only (IP-API, Numbers API)
“Free” does not mean “use however you want.” Ignoring Terms of Service is more dangerous than shipping buggy code — it’s a direct legal risk.
Professional Design — Provider Abstraction
Experienced developers always think about external APIs this way:
- Free APIs are for prototyping and validation
- Plan for paid tier migration or self-hosted replacement in production
- Design to avoid over-dependence on any single provider
This is achieved through Provider Abstraction — wrapping API calls behind an interface so providers can be swapped without changing application code.
class WeatherProvider:
def get_temperature(self, lat, lon):
raise NotImplementedError
class OpenMeteoProvider(WeatherProvider):
def get_temperature(self, lat, lon):
url = f"https://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lon}¤t_weather=true"
data = requests.get(url, timeout=5).json()
return data["current_weather"]["temperature"]
class PaidWeatherProvider(WeatherProvider):
def get_temperature(self, lat, lon):
# Swap to paid API by changing just this class
pass
# Usage
weather = OpenMeteoProvider()
temp = weather.get_temperature(35.67, 139.65)
print(f"Temperature: {temp}°C")
With this design, switching API providers requires changing just one class. No need to rewrite your entire application. During testing, you can swap in a mock provider as well.
This is also known as the “Strategy Pattern” and applies beyond APIs to databases, email services, file storage — any external service integration.
FAQ
Q: What’s the difference between APIs with and without keys?
API keys identify users. Keyless APIs are convenient but tend to have stricter IP-based rate limits. APIs with keys can manage limits per user and typically allow more requests.
Q: Can I build a public service using free APIs?
It depends on the Terms of Service. Open-Meteo and REST Countries allow commercial use, but IP-API’s free tier is non-commercial only. Always verify each API’s ToS before publishing.
Q: What if an API suddenly goes down?
This is exactly why fallback design matters. Keep recent data in cache so you can serve it during outages. For prolonged downtime, use Provider Abstraction to switch to an alternative API.
Q: What does 429 Too Many Requests mean?
You’ve exceeded the rate limit. Check the Retry-After response header and wait the specified seconds before retrying. If you’re consistently hitting 429, implement caching or increase request intervals.
Conclusion
Free APIs let Python developers implement weather, forex, crypto, image, and countless other features in record time. But what truly matters is not which APIs you choose — it’s how you use them.
- Always implement timeout and error handling
- Check Rate Limits and stay within them using caching
- Read the Terms of Service before using any API
- Manage dependencies with Provider Abstraction
- Design with the assumption that external APIs are unreliable
APIs are powerful, but over-dependence breaks things. Used wisely, they can dramatically accelerate your development speed. That’s the essence of external API integration.

Leave a Reply