Instiq
Chapter 2 · Understanding and Using APIs·v1.0.0·Updated 7/20/2026·~16 min

What's changed: Initial version

2.5Calling APIs with Python requests

Key points

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 and Accept/Content-Type go in the headers= dict. Confusing params= (query) with headers= (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 to json= makes requests serialize it to JSON automatically and also set Content-Type: application/json (convenient and prevents forgetting it). Authentication is passed via auth=(user, pass) (Basic) or headers={"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, using if resp.status_code 200: or resp.raise_for_status(), which raises on 4xx/5xx. Because requests does 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.text is the raw string). Headers are resp.headers (dict-like, e.g., resp.headers["Retry-After"]). Note: an error body may not be JSON; calling resp.json() on a 404's HTML raises JSONDecodeError—which is exactly why you check status_code first.
Exam point

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.

Purposerequests idiomCommon 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
Warning

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.

`requests.get/post` arguments and checking `status_code`/`json()`.
Diagnosing defects in a requests script

2.5.3Section summary

  • Send with requests.get(url, headers=, params=, auth=) / requests.post(url, json=); do not confuse params= = query, headers= = headers (auth), json= = JSON body (Content-Type auto)
  • Confirm success with resp.status_code (or resp.raise_for_status()) before resp.json() to a dict; requests does not raise on 4xx/5xx
  • Diagnose a requests script 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`?

Check your understandingPractice questions for Chapter 2: Understanding and Using APIs