3. The response object and JSON

In the last section you printed response.text and got back some JSON. That was the body of the response. But response carries more than just text; it's a structured object with three parts, all accessible from the same variable.

Three things always live inside it:

  • Status code. Did the request succeed or fail? 200, 404, 500.
  • Headers. Metadata about the response: content type, rate-limit info, caching hints.
  • Body. The actual data you care about, often JSON, which you can read as text or parse into native objects.

Instead of you manually parsing raw HTTP text, the requests library turns the response into a structured object with properties and methods, so you can quickly check the status, inspect headers, read the body, and convert it into Python data structures.

Before we break each component down, it's worth seeing all three at once. Save this as peek_response.py (the timeout=10 is explained in Section 5; from here on, every request example will include a timeout):

peek_response.py
import requests

response = requests.get("https://catfact.ninja/fact", timeout=10)

print("Status:", response.status_code)
print("Content-Type:", response.headers["Content-Type"])
print("Body:", response.text)

Run it from the project root:

Terminal
$ python peek_response.py
Status: 200
Content-Type: application/json; charset=utf-8
Body: {"fact":"Cats have a longer memory than dogs.","length":36}

Three lines, three components. response.status_code is the status, response.headers["Content-Type"] pulls a single header, and response.text is the body as raw text. Everything else on this page is a deeper look at each of those.

The three components, in detail

1. Status information

This tells you whether the request succeeded. The status is a numeric code (like 200 or 404) and a short text description ("OK", "Not Found"). You can read it three ways:

  • response.status_code, the numeric code (200, 404, 500).
  • response.reason, the human-readable phrase ("OK", "Not Found", "Internal Server Error").
  • response.ok, a boolean shortcut that's True for any status below 400.

2. Headers

Headers are key-value pairs of metadata about the response. They tell you what type of content was sent (Content-Type: application/json), when it was sent, how large it is, and a dozen other things you rarely need until you do.

  • response.headers, a dictionary-like object with every header the server sent.
  • Common ones: Content-Type, Content-Length, Date, Server.

3. Body

The body is the payload: JSON from an API, HTML from a webpage, or the raw bytes of an image or PDF file. The requests library gives you three ways to read it, depending on what you want:

  • response.content, raw bytes (for images, PDFs, binary files).
  • response.text, a decoded string (for HTML, CSV, plain text).
  • response.json(), a parsed Python dictionary or list (for JSON API data, which is what we'll use most of the time).

You're looking at the same bytes through different lenses. Pick the one that matches what the API actually sent. For API work that's JSON nearly every time, which is why it gets its own section next.

What is JSON?

JSON (JavaScript Object Notation) is a lightweight text format for representing structured data. Almost every modern web API uses it because it's easy for humans to read and easy for computers to parse.

JSON feels familiar the first time you see it because it mirrors Python's basic containers:

  • A JSON "object" maps to a Python dictionary.
  • A JSON "array" maps to a Python list.
  • The syntax is nearly identical: curly braces, colons, commas in the same places.

Here's the same data in both formats, side by side. On the left is what arrives from the API as raw text; on the right is what you get back after calling response.json():

JSON (what the API sends)
{
  "name": "Luna",
  "age": 3,
  "is_cat": true,
  "toys": ["mouse", "string"],
  "owner": null
}
Python (after response.json())
{
    "name": "Luna",
    "age": 3,
    "is_cat": True,
    "toys": ["mouse", "string"],
    "owner": None
}

Spot the three differences: true becomes True, null becomes None, and indentation is slightly different. Everything else is identical. Once the JSON text is parsed, you're navigating familiar dictionaries and lists with bracket notation and key lookups. Patterns you already know like data.get(), for item in data['results'], if 'key' in data all work exactly the same way.

Terminology

API documentation uses JSON terminology, but the terms map directly onto Python types you already understand:

JSON term Python term Example
Object Dictionary (dict) { "id": 1 }
Array List [1, 2, 3]
Field Key/value pair "name": "Luna"

Parsing JSON from a real response

Now we'll put response.json() to work with a real API. In practice, parsing JSON is a two-step move: call response.json() to get Python objects, then extract the fields you care about with dictionary keys.

Save this as parse_cat_fact.py:

parse_cat_fact.py
import requests

response = requests.get("https://catfact.ninja/fact", timeout=10)

print(response.status_code)
print("-" * 30)

# .json() does the work of converting string -> dict
data = response.json()

print("Fact:", data["fact"])
print("Length:", data["length"])

Run it from the project root:

Terminal
$ python parse_cat_fact.py
200
------------------------------
Fact: Cats must have fat in their diet because they can't produce it on their own.
Length: 76

That's the whole pattern. GET request, Response object, parsed dictionary, key lookups. The response.json() method does all the hard work of converting JSON text into Python data structures, and you get back a regular dict you can work with using every technique you already know.

Next, we'll meet a free practice server called httpbin.org that lets you see exactly what your HTTP client is sending, without needing an account or an API key.