What's changed: Initial version
2.5Calling APIs with Python requests
Covers building a script that calls a REST API with Python's requests library—sending requests via requests.get(url, headers=, params=, auth=) / requests.post(url, json=) and reading resp.status_code (checking the code, resp.raise_for_status()), resp.json() (parsing the JSON body into a dict), and resp.headers—framed as the diagnosis/fix of "what is wrong in this requests script and why does it not behave as expected?"
We now turn the knowledge of HTTP requests, responses, authentication, and constraints into real code with Python's requests library. Exams do not test "have you memorized the entire requests API" but whether you can read a given requests script, diagnose why it does not behave as expected, and fix it. You will learn to spot typical mistakes by reading the code—confusing headers= with params=, or calling resp.json() without first checking resp.status_code.
2.5.1Sending a request
- Retrieval is
requests.get(url, headers=..., params=..., auth=...).params=turns a dict into the query string (?key=val) ({"limit":50}->?limit=50). Credentials andAccept/Content-Typego in theheaders=dict. Confusingparams=(query) withheaders=(headers) causes mismatches, e.g., an API key ending up in the query when it belongs in a header. - Creation/update is
requests.post(url, json=payload, headers=...). Passing a Python dict tojson=makes requests serialize it to JSON automatically and also setContent-Type: application/json(convenient and prevents forgetting it). Authentication is passed viaauth=(user, pass)(Basic) orheaders={"Authorization": f"Bearer {token}"}(Bearer).
2.5.2Reading the response
- Check the HTTP code with
resp.status_code(e.g.,200). The rule is to confirm success before handling the body, usingif resp.status_code 200:orresp.raise_for_status(), which raises on 4xx/5xx. Becauserequestsdoes not raise on 4xx/5xx and returns the response object anyway, continuing without checking the code treats an error response as valid data. - Handling the body: for JSON, parse to a Python dict/list with
resp.json()(resp.textis the raw string). Headers areresp.headers(dict-like, e.g.,resp.headers["Retry-After"]). Note: an error body may not be JSON; callingresp.json()on a404's HTML raisesJSONDecodeError—which is exactly why you checkstatus_codefirst.
Most-tested: params= is the query, headers= are headers, json= is the JSON body (sets Content-Type automatically); confirm success with resp.status_code, then resp.json() for a dict; requests does not raise on 4xx/5xx (raise_for_status() for an explicit check); an error body may be non-JSON, so resp.json() before checking invites an exception. Solve by reading "where is the bug in this requests script?" and fixing it.
Suppose you are investigating a colleague's Python script that "sometimes crashes and sometimes prints wrong data." The heart of the code is: resp = requests.get("https://api.example.com/v1/devices", params={"Authorization": "Bearer " + token}); data = resp.json(); print(data["items"]). Two independent bugs hide here. First, the credentials are put in params= (a query parameter)—Authorization should be sent as a header; placed in params= it appends to the URL as ?Authorization=Bearer..., which most APIs do not accept as authentication and answer with 401 (and it also exposes the token in the URL and logs). The fix is headers={"Authorization": f"Bearer {token}"}. Second, it calls resp.json() without checking resp.status_code. Because requests returns the response object even on 4xx/5xx without raising, if authentication fails and a 401 comes back whose body is not JSON (or is an error JSON lacking the items key), resp.json() dies with JSONDecodeError or data["items"] raises KeyError—that is the "sometimes crashes." The fixed version is: resp = requests.get(url, headers={"Authorization": f"Bearer {token}"}); resp.raise_for_status(); data = resp.json(); print(data["items"]). raise_for_status() catches 4xx/5xx early and only successful responses are parsed. For more robustness, branch on resp.status_code and, on a 429, wait per resp.headers.get("Retry-After"), weaving in the earlier sections' knowledge. The lesson: most bugs in a requests script can be diagnosed by reading just two things—"are the credentials passed in headers?" and "is the code checked before calling json()?" The sound approach is not rote memorization of the library but tracing how HTTP principles appear in the code.
| Purpose | requests idiom | Common mistake |
|---|---|---|
| Add query parameters | `requests.get(url, params={"limit":50})` | Putting header-bound auth into `params=` |
| Pass headers/auth | `headers={"Authorization": f"Bearer {t}"}` | Confusing `headers=` and `params=` |
| Send a JSON body | `requests.post(url, json=payload)` | Manual JSON in `data=` and forgetting `Content-Type` |
| Check success and read body | `resp.raise_for_status(); resp.json()` | Calling `resp.json()` without checking status -> exception/wrong data |
Trap: "requests automatically raises on 4xx/5xx, so you need not check status_code" is wrong—requests returns the response object even on errors without raising, so you must check resp.status_code or call resp.raise_for_status(). Also wrong: "putting the auth header in params= is the same thing"—params= becomes the query string, so header-required authentication fails and the token is exposed in the URL.
2.5.3Section summary
- Send with
requests.get(url, headers=, params=, auth=)/requests.post(url, json=); do not confuseparams== query,headers== headers (auth),json== JSON body (Content-Typeauto) - Confirm success with
resp.status_code(orresp.raise_for_status()) beforeresp.json()to a dict; requests does not raise on 4xx/5xx - Diagnose a
requestsscript around two axes: "are credentials passed in headers?" and "is status checked before calling json()?"
Sign in to track progress — Log in.
Quick check
(just a quick review)Q1. The following Python calls an API protected by a Bearer token but always gets `401`, and the token ends up in the access-log URL. `resp = requests.get("https://api.example.com/v1/devices", params={"Authorization": f"Bearer {token}"})`. Which fix is most appropriate?
Q2. The following code is reported to "sometimes crash with `JSONDecodeError` or `KeyError`." `resp = requests.get(url, headers=h); data = resp.json(); print(data["items"])`. requests returns the response even on 4xx/5xx without raising. Which fix best hardens it?
Q3. You want to send the JSON body `{"name":"sw1","vlan":10}` to a creation endpoint. Which requests idiom is most appropriate and avoids forgetting `Content-Type: application/json`?

