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=python tail 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

Popular posts from this blog

JSP Servlet Interview Questions For Freshers Series 1

What are the core classes and interfaces of hibernate 4

Java Banking Finance Services and Insurance (BFSI) domain interview questions