APIs 101: HTTP, REST, JSON and Python's requests Library
Part 1 of the Python for Web & APIs track. Last updated: September 2026.
You have wrangled data and trained models. Now for the skill that turns a Python script into something the rest of the world can talk to: APIs. Almost every modern app — weather apps, payment checkouts, your own data dashboard — is powered by APIs, and Python's requests library makes consuming them a joy. This post teaches you to read APIs like a native: HTTP, REST, JSON, and the requests calls you will use daily.
What an API actually is
An API (Application Programming Interface) is a contract: "send me a request shaped like this, and I will reply with data shaped like that." When a weather app shows today's forecast, it asked a weather API over the internet and got back structured data it could display. No web scraping, no fragile HTML parsing — just a clean machine-to-machine conversation.
HTTP in five minutes
Web APIs speak HTTP. Every request has:
- A method — what you want to do: GET (read), POST (create), PUT/PATCH (update), DELETE (delete).
- A URL — where:
https://api.example.com/books/3. The?q=pythontail holds query parameters (filters, search terms). - Headers — metadata: authentication tokens, content type.
- A body — the payload for POST/PUT, usually JSON.
Every response carries a status code:
- 200 OK — worked. 201 Created — a new resource was made.
- 400 Bad Request — your input was invalid. 401 Unauthorized / 403 Forbidden — auth problems.
- 404 Not Found — no such resource. 429 Too Many Requests — you hit the rate limit.
- 500 Server Error — their side broke, not yours.
REST is the dominant style for web APIs: resources have URLs (/books, /books/3), HTTP methods are the verbs, and responses are JSON.
JSON: the language of APIs
JSON maps to Python types almost one-to-one: objects → dict, arrays → list, strings/numbers/booleans/null → str/int/float/bool/None.
import json
payload = '{"title": "Dune", "year": 1965, "genres": ["sci-fi"], "in_print": true}'
book = json.loads(payload) # JSON text -> Python dict
print(book["title"], type(book["genres"])) # Dune
print(json.dumps(book, indent=2)) # Python dict -> pretty JSON text
Your first API call with requests
Install it, then hit JSONPlaceholder — a free fake API made for practice (no key needed):
import requests
r = requests.get("https://jsonplaceholder.typicode.com/posts/1")
print(r.status_code) # 200
print(r.json()) # dict: {'userId': 1, 'id': 1, 'title': ..., 'body': ...}
print(r.json()["title"])
# sunt aut facere repellat provident occaecati excepturi optio reprehenderit
One call, and you got structured data back. r.json() parses the JSON body straight into a dict.
Query parameters the right way
Never hand-build ?userId=1 strings — pass a params dict and requests encodes it:
r = requests.get(
"https://jsonplaceholder.typicode.com/posts",
params={"userId": 1}, # -> /posts?userId=1
)
posts = r.json()
print(len(posts)) # 10 — all posts by user 1
print(posts[0]["title"])
POST: sending data as JSON
Creating a resource means POST with a JSON body — pass it as the json= argument (requests serializes it and sets the right Content-Type header):
new_post = {"title": "My first API post", "body": "Hello, world!", "userId": 1}
r = requests.post("https://jsonplaceholder.typicode.com/posts", json=new_post)
print(r.status_code) # 201 Created
print(r.json()["id"]) # 101 — the fake API assigned an id
Error handling: the calls you should always write
Networks fail, servers error, typos happen. Three habits separate production code from tutorial code:
try:
r = requests.get(
"https://jsonplaceholder.typicode.com/posts/1",
timeout=10, # 1. ALWAYS set a timeout
)
r.raise_for_status() # 2. raise on 4xx/5xx responses
except requests.exceptions.Timeout:
print("Server took too long — retry later")
except requests.exceptions.HTTPError as e:
print("Bad response:", e) # e.g. 404 Not Found
except requests.exceptions.RequestException as e:
print("Network problem:", e)
data = r.json() if r.ok else None # 3. never parse a failed response
Headers and authentication
Most real APIs need a key or token, sent in a header:
headers = {"Authorization": "Bearer YOUR_API_KEY_HERE"}
r = requests.get("https://api.github.com/user", headers=headers, timeout=10)
print(r.status_code) # 200 with a valid token, 401 without one
The GitHub API also works without a key (with low rate limits) — great for practice:
r = requests.get("https://api.github.com/repos/psf/requests", timeout=10)
info = r.json()
print(info["full_name"]) # psf/requests
print(info["stargazers_count"]) # tens of thousands (grows over time)
print(info["language"]) # Python
Sessions: faster repeated calls
Hitting the same API many times? A Session reuses the underlying connection and lets you set headers once:
s = requests.Session()
s.headers.update({"Authorization": "Bearer YOUR_API_KEY_HERE"})
user = s.get("https://api.example.com/users/42", timeout=10).json()
orders = s.get("https://api.example.com/users/42/orders", timeout=10).json()
print(user["name"], len(orders))
Pagination: reading big lists
APIs rarely return everything at once — they page. The common pattern is a page/limit parameter:
def get_all_comments(session):
comments, page = [], 1
while True:
r = session.get(
"https://jsonplaceholder.typicode.com/comments",
params={"_page": page, "_limit": 50},
timeout=10,
)
r.raise_for_status()
batch = r.json()
if not batch:
break
comments.extend(batch)
page += 1
return comments
s = requests.Session()
all_comments = get_all_comments(s)
print(len(all_comments)) # 500
Loop until a page comes back empty — that works with most page-number APIs.
Key takeaways
- APIs are contracts over HTTP: methods (GET/POST/PUT/DELETE) + URLs + status codes + JSON.
requests.get(url, params={...})for reading,requests.post(url, json={...})for creating.- Always set a timeout, call raise_for_status(), and wrap calls in try/except.
- Use a Session for repeated calls — set auth headers once.
- Big lists come in pages: loop until a page is empty.
Next in this series: Build Your First REST API with Flask — flip the perspective and serve your own API.
Comments
Post a Comment