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

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

JSP Servlet Interview Questions For Freshers Series 1

Java program to check even or odd number