PYTHON PROGRAMMING:Day 22: Working with APIs
Mastering day 22: working with apis concepts and implementation.
On Day 21 you built a Flask app that serves data to browsers — your Python code was the server. But most modern applications also consume data from other services. Your weather app does not calculate forecasts itself; it asks a weather API. Your payment page does not connect to banks directly; it calls a payment API.
An API (Application Programming Interface) is a contract between two programs: "if you send me a request in this format, I will send you a response in that format." You have already written a tiny API with Flask's /api/data route. Today you will be on the other side — the client making requests to someone else's API.
What You Will Learn in This Chapter
By the end of this tutorial you will be able to:
- Explain what an API is using real-world analogies
- Understand REST APIs and HTTP methods (GET, POST, PUT, DELETE)
- Make HTTP requests with Python's
requestslibrary - Parse and create JSON data
- Handle API errors gracefully
- Work with query parameters and request headers
- Understand authentication and rate limiting at a beginner level
- Avoid common API mistakes
Estimated time: 55 minutes reading + 25 minutes practice
What Is an API? The Waiter Analogy
Imagine you are at a restaurant. You do not walk into the kitchen and cook your own meal. Instead, you tell the waiter what you want. The waiter takes your order to the kitchen, the kitchen prepares the food, and the waiter brings it back to your table.
An API is the waiter between your program and another service:
- You (the client) — your Python script
- The waiter (the API) — accepts your request, translates it, delivers it
- The kitchen (the server) — the other company's database and logic
You never need to know how the kitchen works. You just need to know the menu — which "dishes" (endpoints) are available and what "orders" (requests) they accept.
Your Python code → HTTP Request → API Server → Database
Your Python code ← HTTP Response ← API Server ← Database
REST APIs — The Most Common Style
REST (Representational State Transfer) is the dominant API style on the web. REST APIs use standard HTTP methods on URL "resources":
| Method | Action | Example URL | What it does |
|---|---|---|---|
| GET | Read | `/users/42` | Fetch user 42's profile |
| POST | Create | `/users` | Create a new user |
| PUT | Update | `/users/42` | Replace user 42's data |
| DELETE | Delete | `/users/42` | Remove user 42 |
Each URL is a resource — a thing (user, product, weather report) identified by its address. The HTTP method tells the server what you want to do with that resource.
Most APIs return data as JSON — the same format Flask's jsonify() produces on Day 21.
Setting Up the requests Library
Python's built-in urllib can make HTTP requests, but the third-party requests library is simpler and more popular:
pip install requests
Basic usage:
import requests
response = requests.get("https://jsonplaceholder.typicode.com/posts/1")
print(response.status_code) # 200 (success)
print(response.json()) # parsed JSON as a Python dict
Output:
200
{'userId': 1, 'id': 1, 'title': '...', 'body': '...'}
Three lines — request, status check, data. That simplicity is why requests is the standard choice.
Making GET Requests — Fetching Data
GET requests retrieve data without changing anything on the server. Pass extra information via query parameters in the URL:
import requests
# GET with query parameters
url = "https://api.openweathermap.org/data/2.5/weather"
params = {
'q': 'London',
'appid': 'your_api_key_here', # replace with a real key
'units': 'metric'
}
response = requests.get(url, params=params)
print(response.url)
# https://api.openweathermap.org/data/2.5/weather?q=London&appid=...&units=metric
requests builds the full URL for you from the base URL and the params dictionary.
Here is a complete function with error handling:
import requests
def get_weather(city):
url = "https://api.openweathermap.org/data/2.5/weather"
params = {
'q': city,
'appid': 'your_api_key_here',
'units': 'metric'
}
try:
response = requests.get(url, params=params)
response.raise_for_status()
data = response.json()
return {
'city': data['name'],
'temperature': data['main']['temp'],
'description': data['weather'][0]['description']
}
except requests.exceptions.RequestException as e:
return {'error': str(e)}
result = get_weather("London")
print(result)
Walkthrough:
requests.get()sends the HTTP GET request.response.raise_for_status()raises an exception if the status code is 4xx or 5xx (client or server error).response.json()parses the JSON body into a Python dictionary.- We extract nested fields (
data['main']['temp']) and return a clean result. - The
exceptblock catches network errors, timeouts, and bad status codes — returning an error dict instead of crashing.
Making POST Requests — Sending Data
POST requests send data to the server — creating a new record, submitting a form, uploading a file:
import requests
def create_user(user_data):
url = "https://jsonplaceholder.typicode.com/users"
try:
response = requests.post(url, json=user_data)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
return {'error': str(e)}
new_user = {
'name': 'Priya Sharma',
'email': 'priya@schoolabe.com',
'username': 'priya_s'
}
result = create_user(new_user)
print(result)
The json= parameter tells requests to:
- Convert the Python dict to a JSON string
- Set the
Content-Type: application/jsonheader automatically
Without json=, you would need to call json.dumps() yourself and set headers manually.
Working with JSON in Python
JSON maps directly to Python types:
| JSON | Python |
|---|---|
| object `{}` | `dict` |
| array `[]` | `list` |
| string | `str` |
| number | `int` or `float` |
| true/false | `True`/`False` |
| null | `None` |
import json
# JSON string → Python object
json_data = '''
{
"users": [
{"id": 1, "name": "Priya", "email": "priya@schoolabe.com"},
{"id": 2, "name": "Marcus", "email": "marcus@schoolabe.com"}
]
}
'''
data = json.loads(json_data)
for user in data['users']:
print(f"User: {user['name']} ({user['email']})")
Output:
User: Priya (priya@schoolabe.com)
User: Marcus (marcus@schoolabe.com)
Python object → JSON string:
new_user = {"id": 3, "name": "Sofia", "email": "sofia@schoolabe.com"}
json_string = json.dumps(new_user, indent=2)
print(json_string)
Output:
{
"id": 3,
"name": "Sofia",
"email": "sofia@schoolabe.com"
}
json.loads()— load a JSON string into Pythonjson.dumps()— dump a Python object into a JSON stringresponse.json()— shortcut on arequestsresponse object
Understanding HTTP Status Codes
Every API response includes a status code. You should recognize the common ones:
| Code | Meaning | What to do |
|---|---|---|
| 200 | OK | Success — process the data |
| 201 | Created | POST succeeded — new resource created |
| 400 | Bad Request | Check your request format/parameters |
| 401 | Unauthorized | Missing or invalid API key |
| 404 | Not Found | Wrong URL or resource does not exist |
| 429 | Too Many Requests | Slow down — you hit the rate limit |
| 500 | Server Error | Problem on their end — retry later |
response = requests.get("https://jsonplaceholder.typicode.com/posts/99999")
if response.status_code == 200:
print(response.json())
elif response.status_code == 404:
print("Post not found")
else:
print(f"Unexpected error: {response.status_code}")
Or use raise_for_status() inside a try/except block to handle all error codes at once — as shown in the weather example above.
Authentication — Proving Who You Are
Many APIs require an API key or token to identify you and track usage:
headers = {
'Authorization': 'Bearer your_token_here',
'Accept': 'application/json'
}
response = requests.get(url, headers=headers)
Some APIs pass the key as a query parameter (?appid=your_key), others require it in a header. Always read the API's documentation — each service has its own rules.
Security rule: Never hardcode API keys in source code that gets shared or committed to Git. Use environment variables:
import os
api_key = os.environ.get('WEATHER_API_KEY')
Rate Limiting — Do Not Overwhelm the Server
APIs often limit how many requests you can make per minute or per day. If you exceed the limit, you get a 429 Too Many Requests response.
Good practices:
- Cache responses when data does not change often (weather every 30 minutes, not every second)
- Add delays between requests when fetching many pages
- Read the API's rate limit documentation before building
import time
import requests
urls = [f"https://jsonplaceholder.typicode.com/posts/{i}" for i in range(1, 6)]
for url in urls:
response = requests.get(url)
print(f"Post {response.json()['id']}: {response.json()['title'][:40]}...")
time.sleep(0.5) # polite pause between requests
A Complete API Client Example
import requests
import json
def get_weather(city):
url = "https://api.openweathermap.org/data/2.5/weather"
params = {
'q': city,
'appid': 'your_api_key_here',
'units': 'metric'
}
try:
response = requests.get(url, params=params)
response.raise_for_status()
data = response.json()
return {
'city': data['name'],
'temperature': data['main']['temp'],
'description': data['weather'][0]['description']
}
except requests.exceptions.RequestException as e:
return {'error': str(e)}
def create_user(user_data):
url = "https://jsonplaceholder.typicode.com/users"
try:
response = requests.post(url, json=user_data)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
return {'error': str(e)}
def process_json_data():
json_data = '''
{
"users": [
{"id": 1, "name": "Priya", "email": "priya@schoolabe.com"},
{"id": 2, "name": "Marcus", "email": "marcus@schoolabe.com"}
]
}
'''
data = json.loads(json_data)
for user in data['users']:
print(f"User: {user['name']} ({user['email']})")
new_user = {"id": 3, "name": "Sofia", "email": "sofia@schoolabe.com"}
json_string = json.dumps(new_user, indent=2)
print("\nNew user JSON:")
print(json_string)
print("Processing JSON data:")
process_json_data()
Output:
Processing JSON data:
User: Priya (priya@schoolabe.com)
User: Marcus (marcus@schoolabe.com)
New user JSON:
{
"id": 3,
"name": "Sofia",
"email": "sofia@schoolabe.com"
}
Common Mistakes
Mistake 1: Not checking the status code
# Dangerous — .json() may fail on error pages
data = requests.get(url).json()
# Safe
response = requests.get(url)
response.raise_for_status()
data = response.json()
Mistake 2: Confusing json= and data= in POST requests
# Sends JSON (Content-Type: application/json) — most REST APIs expect this
requests.post(url, json={'name': 'Priya'})
# Sends form data (Content-Type: application/x-www-form-urlencoded) — HTML forms use this
requests.post(url, data={'name': 'Priya'})
Using the wrong one causes the server to reject or misparse your request.
Mistake 3: Hardcoding API keys in source code
# BAD — visible in Git history forever
API_KEY = "sk-abc123secret"
# GOOD — read from environment
import os
API_KEY = os.environ.get('API_KEY')
Mistake 4: Ignoring timeouts
# Can hang forever if the server is down
response = requests.get(url)
# Fails fast after 10 seconds
response = requests.get(url, timeout=10)
Always set a timeout so your program does not freeze on a dead server.
Mistake 5: Assuming the response structure never changes
APIs evolve. Wrap field access in try/except or use .get() with defaults:
# Crashes if 'main' key is missing
temp = data['main']['temp']
# Safe
temp = data.get('main', {}).get('temp', 'unknown')
Practice Exercises
Exercise 1: Use requests.get() to fetch post #1 from https://jsonplaceholder.typicode.com/posts/1 and print its title.
Exercise 2: Fetch all posts (/posts) and print the titles of the first five.
Exercise 3: POST a new todo item to https://jsonplaceholder.typicode.com/todos with {"title": "Learn APIs", "completed": false, "userId": 1}.
Exercise 4: Write a function that takes a URL, makes a GET request with a 5-second timeout, and returns either the JSON data or an error message string.
→ See all Python practice exercises with solutions
What Comes Next — Day 23: Database Operations
APIs move data between programs over the network. But where does data live permanently? Databases — organized storage that survives after your program closes. Day 23 teaches you to create, read, update, and delete records with Python and SQLite.
Day 23 covers:
- What databases are and why apps need them
- SQL basics (SELECT, INSERT, UPDATE, DELETE)
- Working with SQLite using Python's built-in
sqlite3module - CRUD operations and safe parameterized queries
→ Continue to Day 23: Database Operations
Chapter navigation
- Previous: Day 21: Web Development Basics
- Next: Day 23: Database Operations
- Python Quiz: Take the Python quiz
- All Python exercises: Explore Python exercises