Python Working With APIs (requests)
An API lets your program talk to another program over the internet, and the requests library makes that conversation easy.
What is a Web API?
When a weather app shows Karachi's temperature, it did not measure it. It asked a weather service's API. The app sends a request to a web address (an endpoint), and the service sends back a response, usually in JSON.
| Part | Meaning |
|---|---|
| Endpoint | The URL you call, e.g. https://api.example.com/users/1 |
| Method | What you want: GET to read, POST to create, PUT/PATCH to change, DELETE to remove |
| Status code | A number that says how it went, e.g. 200 OK |
| Headers | Extra information, like the content type or an API key |
| Body | The data itself, often JSON |
Install requests
requests is not built in. Install it inside your virtual environment:
Example
pip install requestsThe examples below use JSONPlaceholder, a free practice API with fake data. It needs no sign-up and no key.
A GET Request
Example
import requests
response = requests.get("https://jsonplaceholder.typicode.com/todos/1", timeout=10)
print(response.status_code)
print(response.headers["Content-Type"])
print(response.text)200
application/json; charset=utf-8
{
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}response.text is the raw text. Usually you want Python objects instead, so call .json():
Example
import requests
response = requests.get("https://jsonplaceholder.typicode.com/users/1", timeout=10)
user = response.json()
print(type(user))
print(user["name"])
print(user["email"])
print(user["address"]["city"])<class 'dict'> Leanne Graham Sincere@april.biz Gwenborough
Always pass a timeout. Without it, your program can wait forever if the server never answers.
Query Parameters
Many APIs let you filter results with ?key=value at the end of the URL. Let requests build that part for you with params:
Example
import requests
response = requests.get(
"https://jsonplaceholder.typicode.com/posts",
params={"userId": 1},
timeout=10,
)
posts = response.json()
print("Posts by user 1:", len(posts))
print("First post id:", posts[0]["id"])
print("Every post is by user 1:", all(p["userId"] == 1 for p in posts))Posts by user 1: 10 First post id: 1 Every post is by user 1: True
requests turns params={"userId": 1} into ?userId=1 and also handles spaces and special characters safely.
Status Codes
| Code | Meaning |
|---|---|
| 200 | OK, here is your data |
| 201 | Created (after a successful POST) |
| 400 | Bad request: your data is wrong |
| 401 / 403 | Not logged in / not allowed |
| 404 | Not found |
| 429 | Too many requests, slow down |
| 500 | The server has a problem |
Codes in the 200s mean success. response.ok is True for any code below 400.
A POST Request
To send data, use requests.post() with json=. requests converts your dictionary to JSON and sets the right header.
Example
import requests
new_post = {"title": "Python class timings", "body": "Mon to Thu, 6 pm", "userId": 1}
response = requests.post("https://jsonplaceholder.typicode.com/posts", json=new_post, timeout=10)
print(response.status_code)
created = response.json()
print(created["id"], created["title"])201 101 Python class timings
JSONPlaceholder pretends to save the post and gives it the id 101. A real API would store it.
Handling Errors
Things go wrong on networks: no internet, a slow server, a wrong URL. Use raise_for_status() to turn bad codes (400 and above) into exceptions, and catch them with try...except.
Example
import requests
def get_json(url):
try:
response = requests.get(url, timeout=5)
response.raise_for_status()
return response.json()
except requests.exceptions.HTTPError as err:
print("HTTP error:", err.response.status_code)
except requests.exceptions.ConnectionError:
print("Could not connect. Check your internet.")
except requests.exceptions.Timeout:
print("The server took too long.")
return None
print(get_json("https://jsonplaceholder.typicode.com/todos/1")["title"])
print(get_json("https://jsonplaceholder.typicode.com/posts/99999"))delectus aut autem HTTP error: 404 None
Headers and API Keys
Real APIs usually need a key to know who is calling. You send it in a header. Keep the key out of your code by reading it from an environment variable.
Example
import os
import requests
api_key = os.environ.get("WEATHER_API_KEY", "")
headers = {"Authorization": f"Bearer {api_key}", "Accept": "application/json"}
response = requests.get("https://api.example.com/v1/weather", params={"city": "Karachi"}, headers=headers, timeout=10)Never put API keys or passwords directly in your code, and never upload them to GitHub. Anyone who sees the key can use your account.
Sessions
If you call the same API many times, a Session reuses the connection and remembers headers, which is faster.
Example
import requests
with requests.Session() as s:
s.headers.update({"Accept": "application/json"})
for n in [1, 2, 3]:
r = s.get(f"https://jsonplaceholder.typicode.com/users/{n}", timeout=10)
print(n, r.status_code)Exercise
Exercise
Which line turns a JSON response into a Python dictionary?
