A REST API exposes data at URLs; your program sends HTTP requests to them and receives responses, usually in JSON. In Python the standard tool is the requests library (python -m pip install requests):
import requests
response = requests.get("https://api.example.org/observations",
params={"night": "Saturday"}, timeout=10)
response.raise_for_status()
print(response.json())The web APIs lesson introduced requests. This one goes deeper into what each HTTP method means, why every request needs a timeout, how to separate status checks from JSON decoding, and how to collect data that the API returns one page at a time. The examples talk to an astronomy club's observation schedule, served by a small local server so you can run them without internet access.
HTTP methods
The method tells the server what you intend:
| Method | Typical meaning | Has a body | Safe to repeat |
|---|---|---|---|
GET | read a resource or list | no | yes |
POST | create something, or trigger an action | yes | not necessarily |
PUT | replace a resource at a known URL | yes | yes (idempotent) |
PATCH | change part of a resource | yes | depends |
DELETE | remove a resource | rarely | yes (idempotent) |
requests.get(url, params={"night": "Saturday"}, timeout=10)
requests.post(url, json={"object": "M31", "night": "Saturday"}, timeout=10)
requests.patch(f"{url}/17", json={"status": "observed"}, timeout=10)
requests.delete(f"{url}/17", timeout=10)These meanings are conventions defined by HTTP; each API documents exactly what its endpoints do, which fields they accept and which status codes they return. Read the documentation rather than assuming, for example, that DELETE is allowed or that POST returns the created object.
Requests and timeouts
Build requests from parts instead of assembling URLs by hand:
params={...}adds a correctly encoded query string:?night=Saturday&limit=20.json={...}sends a JSON body and setsContent-Type: application/json.headers={...}adds headers such asAcceptor authentication tokens.
Always pass timeout=. Without it, requests may wait indefinitely for a server that never answers, freezing your program. timeout=10 limits the connection attempt and each wait for data to 10 seconds; timeout=(3, 10) sets them separately.
A timeout is not a total deadline: a server that sends a byte every 9 seconds can keep a download going much longer. If you need an overall time limit, track elapsed time yourself. When a timeout fires, requests raises requests.Timeout; connection failures raise requests.ConnectionError. Both inherit from requests.RequestException, which is the type to catch for "the request didn't complete".
Response handling
Treat a response in two separate steps: check the status, then decode the body.
response = requests.get(url, timeout=10)
response.raise_for_status() # raises HTTPError for 4xx and 5xx
try:
data = response.json()
except requests.JSONDecodeError:
raise ValueError(f"Expected JSON, got: {response.text[:100]!r}")A 200 status doesn't guarantee JSON. A misconfigured proxy, a maintenance page or an endpoint that returns CSV can all answer 200 with a body json() can't parse. Conversely, error responses often carry useful JSON describing the problem; read response.json() inside your error handling when the API documents it.
Useful response attributes: status_code, headers (case-insensitive), text (decoded string), content (raw bytes), url (the final URL after redirects) and elapsed. Validate the decoded data's shape before relying on it, as in the JSON lesson.
Pagination
APIs rarely return thousands of records at once. They return a page and tell you how to get the next one, in one of a few ways:
- a next link in the body (
"next": "https://.../observations?page=3") or aLinkheader; - a cursor or token to send back (
"next_cursor": "abc123"); - page or offset numbers you increment yourself.
def fetch_all(url, session):
results = []
while url:
response = session.get(url, timeout=10)
response.raise_for_status()
page = response.json()
results.extend(page["items"])
url = page.get("next") # None on the last page
return resultsReading only the first page and treating it as complete is a classic data-loss bug: the report looks plausible but silently omits most records. Follow the API's documented scheme, stop when it signals the end, and add a safety limit on the number of pages so a misbehaving API can't loop forever. A requests.Session reuses the connection across pages, which is noticeably faster.
Independent practice and a worked check
Task: use a real documented schedule endpoint in place of the illustrative URL and handle status, decoding and pagination. Write the client as a function you can point at any compatible API:
import requests
def fetch_schedule(start_url, max_pages=50):
items, url = [], start_url
with requests.Session() as session:
for _ in range(max_pages):
if not url:
return items
response = session.get(url, timeout=10)
response.raise_for_status()
try:
page = response.json()
except requests.JSONDecodeError:
raise ValueError(f"{url} did not return JSON")
items.extend(page.get("items", []))
url = page.get("next")
raise RuntimeError(f"Stopped after {max_pages} pages; check the pagination rules")Point it at the local server from the example above to test it, then at a real API whose pagination uses a next link, adapting the field names to its documentation. For the worked check, a request timeout exists to bound a wait for network activity, so an unresponsive server can't freeze the program.
Reference and next practice
The requests documentation covers sessions, timeouts and errors, and MDN's HTTP reference explains methods and status codes. Continue with API authentication and errors. Practise with the Python exercises and Python quiz.
HTTP Requests and REST APIs Summary
- HTTP methods: GET retrieves representations while POST commonly submits new data, with endpoint contracts defining exact behavior.
- Requests and timeouts: A request needs a URL, optional parameters and a deliberate timeout.
- Response handling: Status checks and decoding are separate steps.
- Pagination: Many APIs return only one page and provide a next token or link.
HTTP Requests and REST APIs Examples
Each example shows the code, the output it produces and what each part does.
HTTP Requests and REST APIs in a small program
This shows the pattern with an illustrative URL; replace it with a real endpoint before running. The next example runs completely offline.
import requests
response = requests.get(
"https://example.org/observations",
params={"night": "Saturday"},
timeout=10,
)
response.raise_for_status()
print(response.json())Line-by-line explanation
- Lines 4 to 7Query parameters go in
params, and the timeout bounds each wait. - Line 8
raise_for_status()turns a 4xx or 5xx response into an exception before any decoding. - Line 9Only then is the body decoded as JSON.
A paginated API served locally
import json, threading
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import urlparse, parse_qs
import requests
OBJECTS = ["M31", "M42", "M45", "M13", "M57"]
class ScheduleAPI(BaseHTTPRequestHandler):
def do_GET(self):
page = int(parse_qs(urlparse(self.path).query).get("page", ["1"])[0])
items = OBJECTS[(page - 1) * 2: page * 2]
port = self.server.server_address[1]
has_more = page * 2 < len(OBJECTS)
nxt = f"http://127.0.0.1:{port}/schedule?page={page + 1}" if has_more else None
body = json.dumps({"items": items, "next": nxt}).encode()
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(body)
def log_message(self, *args):
pass
server = HTTPServer(("127.0.0.1", 0), ScheduleAPI)
threading.Thread(target=server.serve_forever, daemon=True).start()
url = f"http://127.0.0.1:{server.server_address[1]}/schedule?page=1"
with requests.Session() as session:
collected, pages = [], 0
while url and pages < 10:
response = session.get(url, timeout=5)
response.raise_for_status()
page = response.json()
collected.extend(page["items"])
url = page["next"]
pages += 1
server.shutdown()
print(pages, "pages:", collected)3 pages: ['M31', 'M42', 'M45', 'M13', 'M57']Line-by-line explanation
- Lines 8 to 21A tiny API that returns two objects per page plus a
nextlink, orNoneon the last page. - Lines 23 to 24Port 0 asks the operating system for any free port; the server runs in a background thread.
- Line 29The loop follows
nextlinks, with a ten-page safety limit.
Common HTTP Requests and REST APIs Mistakes
Reading only the first page
data = requests.get(url, timeout=10).json()
observations = data["items"] # only the first pageobservations = []
while url:
page = requests.get(url, timeout=10).json()
observations.extend(page["items"])
url = page.get("next")Leaving out the timeout
response = requests.get(url) # can hang foreverresponse = requests.get(url, timeout=10)HTTP Requests and REST APIs in Real Projects
REST clients in practice
Data pipelines pull from paginated APIs every night: GitHub issues, payment transactions, analytics events. Robust clients use a session, set timeouts, check statuses, validate JSON, follow pagination to the end, respect rate limits and log the request id the API returns for troubleshooting. Many companies wrap this in a small client class with one method per endpoint so the rest of the code never builds URLs.
For high volumes, httpx offers the same interface with async support and HTTP/2, and API providers often publish official Python SDKs that implement pagination and retries for you.
HTTP Requests and REST APIs FAQ
What is a REST API?
An interface that exposes resources at URLs and uses HTTP methods to act on them: GET to read, POST to create, PUT or PATCH to update and DELETE to remove. Responses usually contain JSON.
What is a good timeout for Python requests?
It depends on the API, but a few seconds to connect and 10 to 30 seconds to read suits most services: timeout=(3, 20). The important thing is to set one, because the default is to wait indefinitely.
What is the difference between params and json in requests?
params adds values to the URL's query string, as used by GET filters. json sends a JSON body, as used by POST, PUT and PATCH. data sends form-encoded or raw bodies.
Why use requests.Session?
A session reuses the underlying connection across requests to the same host, which is faster, and remembers settings such as headers and cookies. It's ideal for pagination and for clients that make many calls.
How do I handle pagination in an API?
Read the API's documentation to see whether it uses next links, cursors or page numbers, then loop: fetch a page, collect its items, read the next marker, and stop when it's missing. Add a maximum page count as a safety net.
HTTP Requests and REST APIs Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Why set a request timeout?
Which review concern is valid for http requests and rest apis?
HTTP Requests and REST APIs Quiz
2 questions to check what you learned.
Why set a request timeout?
- To guarantee the server returns valid JSON
- To bound a wait for network activity
- Python ignores the distinction
Which statement correctly describes response handling?
- Status checks and decoding are separate steps.
- Only the example output matters, not the rule
- Every operation has the same behavior