Beyond the basicsChapter 94 of 114
Working with APIs
Ask another service for data over HTTP, and handle what comes back.
What an API call is
You send an HTTP request to a URL and get a response, usually JSON. The parts that matter: the method (GET to read, POST to send), the status code, and the body.
requests
Not in the standard library, and the one nearly everybody uses:
python -m pip install requestsimport requests
response = requests.get("https://api.example.com/people/1")
print(response.status_code)
data = response.json()
print(data["name"])Output, from a real run elsewhere
200 Ada
Always check the status
requests does not raise on a 404 or a 500. You get a response object saying it went wrong, and ignoring that means parsing an error page as if it were data:
import requests
response = requests.get("https://api.example.com/missing")
if response.status_code == 200:
print(response.json())
else:
print("failed with", response.status_code)Output, from a real run elsewhere
failed with 404
response.raise_for_status() turns a bad status into an exception, which is often what you want inside a try.
The ranges are worth memorising: 2xx worked, 3xx redirect, 4xx you got it wrong, 5xx they got it wrong.
Query parameters
Pass a dictionary rather than building the URL by hand, so encoding is handled:
import requests
response = requests.get(
"https://api.example.com/search",
params={"q": "ada lovelace", "limit": 10},
)
print(response.url)Output, from a real run elsewhere
https://api.example.com/search?q=ada+lovelace&limit=10
Headers and authentication
import requests
response = requests.get(
"https://api.example.com/me",
headers={
"Authorization": "Bearer " + token,
"Accept": "application/json",
},
timeout=10,
)import os
token = os.environ.get("API_TOKEN")
if token is None:
print("API_TOKEN is not set; refusing to continue")
else:
print("token found")Output
API_TOKEN is not set; refusing to continue
Always set a timeout
Without one, a hung server hangs your program indefinitely:
import requests
try:
response = requests.get("https://api.example.com/slow", timeout=5)
except requests.Timeout:
print("gave up after five seconds")Output, from a real run elsewhere
gave up after five seconds
Sending data
import requests
response = requests.post(
"https://api.example.com/people",
json={"name": "Ada", "born": 1815},
timeout=10,
)
print(response.status_code)Output, from a real run elsewhere
201
Use json= and requests sets the content type and encodes the body. Use data= only for form-encoded submissions.
Handling the response
This part is ordinary Python, and it does run here:
import json
body = '{"results": [{"name": "Ada", "born": 1815}, {"name": "Grace"}], "next": null}'
data = json.loads(body)
for person in data["results"]:
born = person.get("born", "unknown")
print(f"{person['name']}: {born}")
print("more pages?", data["next"] is not None)Output
Ada: 1815 Grace: unknown more pages? False
JSON null becomes Python None, so there is no next page. Use get() with a default for fields an API may omit, as with Grace's missing birth year.
Paging
Most APIs return results in pages. The shape is nearly always the same:
import requests
url = "https://api.example.com/people"
names = []
while url:
response = requests.get(url, timeout=10)
response.raise_for_status()
payload = response.json()
names.extend(person["name"] for person in payload["results"])
url = payload.get("next")
print(len(names))Be a good client
- Send a
User-Agentthat identifies you - Respect rate limits, and back off when you see a 429
- Cache what does not change
- Read the terms before scraping anything
The standard library alternative
If one call is all you need, urllib avoids a dependency:
import json
from urllib.request import urlopen
with urlopen("https://api.example.com/people/1") as response:
data = json.load(response)
print(data["name"])Output, from a real run elsewhere
Ada
It is clumsier for anything with headers or authentication, which is exactly why requests is so widely used.
Test yourself
2 questionsWhat does requests do when the server returns a 404?
Show the answer
Returns a response with that status code, without raising — Check status_code, or call raise_for_status() to turn a bad status into an exception.
Why always pass a timeout?
Show the answer
Without one, a hung server hangs your program indefinitely — There is no default timeout, which surprises people the first time a script hangs overnight.
Testing
Write code that checks your code, so a change cannot break it quietly.