Calling APIs with Python
The requests library is the standard way to call an HTTP API from Python. Install it with pip install requests,
then a whole integration is usually fewer than twenty lines.
A first call
import requests
r = requests.get("https://api.github.com/repos/django/django", timeout=10)
r.raise_for_status() # turns 4xx/5xx into an exception
data = r.json() # parse the JSON body into a dict
print(data["stargazers_count"])
resp = requests.post("https://httpbin.org/post", json={"name": "BeeProton"}, timeout=10)
print(resp.status_code, resp.json()["json"])
r.status_code, r.headers, r.text and r.json() cover almost everything you need from a response.
Query parameters and JSON bodies
Never build a URL by pasting strings together; pass a dict and let requests encode it correctly (spaces, &,
Devanagari text and all).
params = {"city": "Pune", "from": "2026-04-01", "page": 2}
r = requests.get("https://api.example.in/v1/orders", params=params, timeout=10)
payload = {"customer": "Sharma Sweets", "gstin": "08ABCDE1234F1Z5", "amount": 11760}
r = requests.post("https://api.example.in/v1/invoices", json=payload, timeout=10)
json= sets Content-Type: application/json and serialises the dict for you. Use data= only when the API expects
an HTML-style form.
Headers and secrets
Most APIs authenticate with a header. Keep the key in an environment variable, never in the code you commit.
import os
headers = {"Authorization": f"Bearer {os.environ['ORDERS_API_TOKEN']}"}
r = requests.get("https://api.example.in/v1/orders", headers=headers, timeout=10)
Handling failure properly
Networks fail, tokens expire and servers throttle. Wrap calls so one bad response does not crash a nightly job.
try:
r = requests.get(url, headers=headers, timeout=(5, 30))
r.raise_for_status()
orders = r.json()
except requests.Timeout:
print("API did not respond in time")
except requests.HTTPError as exc:
print("API returned", exc.response.status_code, exc.response.text[:200])
except requests.RequestException as exc:
print("Network problem:", exc)
The tuple timeout=(5, 30) means five seconds to connect and thirty to read.
Sessions and retries
For many calls to the same host, a Session reuses the TCP connection and keeps the headers in one place. Attaching
a retry policy handles temporary 429 and 5xx errors with exponential backoff.
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
session = requests.Session()
session.headers.update(headers)
retry = Retry(total=3, backoff_factor=1, status_forcelist=[429, 500, 502, 503, 504])
session.mount("https://", HTTPAdapter(max_retries=retry))
r = session.get("https://api.example.in/v1/orders", params={"page": 1}, timeout=20)
Paging through results
Most list endpoints return a page at a time. Loop until the API stops giving you a next link.
url, all_rows = "https://api.example.in/v1/orders", []
while url:
r = session.get(url, timeout=20)
r.raise_for_status()
body = r.json()
all_rows.extend(body["results"])
url = body.get("next")
print(len(all_rows), "orders fetched")
| Need | Use |
|---|---|
| Send JSON | json=payload |
| Send a form | data=payload |
| Upload a file | files={"report": open("sales.csv", "rb")} |
| Follow the response type | r.headers["Content-Type"] |
Common mistakes
- Omitting
timeout, so a hung server freezes your script forever. - Calling
r.json()without checking the status code, then crashing on an HTML error page. - Hard-coding API keys and pushing them to GitHub.
- Hammering an endpoint in a tight loop instead of respecting the documented rate limit.
Practice
Call https://api.github.com/search/repositories with params={"q": "django", "per_page": 5}, print each
repository's name and star count, and add error handling plus a Session with retries. Then move the token for
any authenticated API you use into an environment variable.