Back to Blog
Python

Python Requests: Response JSON, Text, Headers, Status Codes

python requests response json text headers and status codes: Learn how to access and interpret response data from the Python requests library: body text, JSON parsing,...

requestsHTTPJSONAPIresponse handlingstatus codes
Illustration of a Python requests response object showing a status code badge, a headers panel, and a JSON document.

python requests response json text headers and status codes requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

When you make an HTTP request with the Python requests library, the returned Response object carries everything you need to understand what the server sent back. The four attributes you will use most often are response.text, response.json(), response.headers, and response.status_code. Each serves a distinct purpose, and knowing when to use which one prevents subtle bugs in API clients, web scrapers, and integration scripts.

The Response Object: What You Get After a Request

A successful requests.get() or requests.post() call returns a Response instance. This object holds the server's reply, the request metadata, and the connection state. The most commonly accessed members are:

  • status_code: an integer like 200 or 404.
  • headers: a case-insensitive dictionary-like object of response headers.
  • text: the response body decoded as a string.
  • content: the raw bytes of the response body.
  • json(): a method that parses the body as JSON and returns a Python object.

These are not independent; text and content are two views of the same body, and json() is a convenience wrapper over text with JSON decoding. Understanding the relationship between them helps you avoid decoding errors and unnecessary memory usage.

Reading the Response Body as Text

The response.text property returns the body as a string. The requests library infers the character encoding from the Content-Type header, falling back to a default of ISO-8859-1 if no charset is specified. This works for most text-based responses, but you may need to override it when the server sends a wrong or missing charset.

import requests response = requests.get("https://api.example.com/data") print(response.text)

If the response contains binary data, such as an image or a compressed file, text will try to decode it and produce garbage. In that case, use response.content to get the raw bytes:

image_bytes = requests.get("https://example.com/image.png").content

When you know the response is text but the encoding is wrong, set response.encoding before accessing text:

response.encoding = "utf-8" text = response.text

This is useful when the server omits the charset or uses a non-standard one. The text property is re-evaluated each time you access it, so changing encoding affects subsequent reads.

Parsing JSON from the Response

For JSON APIs, response.json() is the direct way to get a Python dictionary or list. It internally reads response.text and applies json.loads(), but it also checks the Content-Type header to warn if the body does not look like JSON. If the body is not valid JSON, it raises requests.exceptions.JSONDecodeError, a subclass of ValueError.

response = requests.get("https://api.example.com/user/1") try: user = response.json() except requests.exceptions.JSONDecodeError: print("Response was not valid JSON") print(response.text)

You can also parse JSON manually using json.loads(response.text), which gives you more control over the decoding process, for example when you need to handle a BOM or a custom object hook. However, response.json() is simpler and handles the content-type check for you.

One common mistake is to call response.json() on a response that returns an empty body or an error page. Always check the status code or wrap the call in a try-except block to avoid crashing the script.

Inspecting Response Headers

response.headers returns a CaseInsensitiveDict, which means you can access header names without worrying about capitalization. This is convenient because HTTP header names are case-insensitive by spec, but many servers and proxies send them in mixed case.

response = requests.get("https://api.example.com/data") content_type = response.headers["Content-Type"] # or content_type = response.headers.get("content-type")

Common headers you might inspect include Content-Type, Content-Length, Cache-Control, ETag, and RateLimit-Remaining. The headers object behaves like a dictionary, so you can iterate over it or use .get() to avoid KeyError.

for name, value in response.headers.items(): print(f"{name}: {value}")

Note that response.headers contains only the response headers, not the request headers. If you need to debug the request side, use response.request.headers.

Checking Status Codes

The status_code attribute tells you whether the request succeeded. A status code in the 2xx range means success, 3xx means redirect, 4xx is a client error, and 5xx is a server error. The requests library follows redirects by default, so a final response usually has a 2xx status unless the redirect chain fails.

response = requests.get("https://api.example.com/data") if response.status_code == 200: data = response.json() elif response.status_code == 404: print("Resource not found") else: print(f"Unexpected status: {response.status_code}")

A more concise way is to use response.raise_for_status(). This method raises requests.exceptions.HTTPError if the status code indicates an error (4xx or 5xx). It is a good practice to call it after every request unless you explicitly want to handle non-2xx responses yourself.

try: response = requests.get("https://api.example.com/data") response.raise_for_status() data = response.json() except requests.exceptions.HTTPError as err: print(f"HTTP error: {err}")

Be aware that raise_for_status() does not check the response body; it only looks at the status code. A 200 response with a malformed JSON body will not raise an HTTPError, but response.json() will raise a JSONDecodeError.

Handling Errors and Unexpected Responses

In real-world API integration, you need to handle both network-level failures and application-level errors. The requests library raises exceptions for connection problems, timeouts, and too many redirects. These are separate from HTTP status errors.

import requests from requests.exceptions import RequestException try: response = requests.get("https://api.example.com/data", timeout=5) response.raise_for_status() data = response.json() except requests.exceptions.Timeout: print("The request timed out") except requests.exceptions.ConnectionError: print("Could not connect to the server") except requests.exceptions.HTTPError as err: print(f"HTTP error: {err}") except requests.exceptions.JSONDecodeError: print("Response was not valid JSON")

When you expect a specific content type, you can validate it before parsing. This avoids unnecessary JSON parsing attempts on HTML error pages that some servers return with a 200 status.

content_type = response.headers.get("Content-Type", "") if "application/json" in content_type: data = response.json() else: print("Unexpected content type")

Keep in mind that some APIs return JSON with a different content type like text/plain or application/hal+json. In those cases, checking for a substring is safer than an exact match.

Performance and Resource Considerations

When dealing with large responses, the default behavior of requests is to download the entire body into memory. This is fine for most API calls, but for large files or streaming endpoints, you should use stream=True and iterate over the content in chunks.

with requests.get("https://example.com/large-file", stream=True) as response: for chunk in response.iter_content(chunk_size=8192): process(chunk)

Using a context manager (with) ensures the connection is released back to the pool after the block exits. Without it, you should call response.close() manually, especially when streaming.

For JSON responses, response.json() loads the entire body into memory as a Python object. If the JSON is extremely large, consider parsing it incrementally with json.JSONDecoder.raw_decode() on a streaming response, but this is rarely necessary for typical API usage.

Another resource consideration is connection pooling. The requests library reuses connections through a Session object. If you make many requests to the same host, create a Session and reuse it to avoid the overhead of establishing a new TCP connection each time.

session = requests.Session() response1 = session.get("https://api.example.com/endpoint1") response2 = session.get("https://api.example.com/endpoint2")

Finally, always set a timeout on your requests. Without a timeout, a hanging server can block your script indefinitely. A short timeout like 5 seconds is often enough for internal APIs, while public endpoints may need longer.

try: response = requests.get("https://api.example.com", timeout=5) response.raise_for_status() except requests.exceptions.Timeout: print("Request timed out")

By combining status_code, headers, text, and json(), you can build robust HTTP clients that handle both expected and unexpected server behavior. The key is to check the status code, validate the content type, and parse the body only when it is safe to do so.

python requests response json text headers and status codes: | RYUSLOG DEV