> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zenrows.com/llms.txt
> Use this file to discover all available pages before exploring further.

# The Response Object with the Python SDK

> Understand the requests.Response object returned by every ZenRows Python SDK call.

Every method on `ZenRowsClient` (`get`, `post`, `put`, and their `_async` counterparts) returns a standard <a href="https://docs.python-requests.org/en/latest/api/#requests.Response" target="_blank" rel="noopener noreferrer nofollow">`requests.Response`</a> object. The SDK doesn't wrap or transform it, so everything you already know about `requests` applies directly.

## Commonly used attributes

| Attribute              | Returns                              | Use it when                                      |
| ---------------------- | ------------------------------------ | ------------------------------------------------ |
| `response.text`        | Response body as a string            | Reading HTML or plain text                       |
| `response.content`     | Response body as raw bytes           | Handling binary data (PDFs, screenshots, images) |
| `response.json()`      | Parsed JSON                          | The body is JSON (e.g. `json_response=true`)     |
| `response.status_code` | HTTP status code, e.g. `200`         | Checking exactly what happened                   |
| `response.ok`          | `True` if `status_code` is under 400 | Quick success check                              |
| `response.headers`     | Dict-like object of response headers | Reading ZenRows monitoring headers               |
| `response.request.url` | The final URL that was requested     | Debugging what was actually sent                 |

<Tabs>
  <Tab title="Text / HTML">
    ```python theme={null} theme={null}
    response = client.get(url)
    html = response.text
    ```
  </Tab>

  <Tab title="Binary (PDF, screenshot)">
    ```python theme={null} theme={null}
    response = client.get(url, params={"response_type": "pdf", "js_render": True})

    with open("page.pdf", "wb") as f:
        f.write(response.content)
    ```

    <Tip>
      Use `response.content` (not `response.text`) when the target returns binary data. For example, a PDF from `response_type=pdf` or a screenshot. Writing `response.content` in binary mode (`"wb"`) avoids corrupting the file.
    </Tip>
  </Tab>

  <Tab title="JSON">
    ```python theme={null} theme={null}
    response = client.get(url, params={"json_response": True, "js_render": True})
    data = response.json()
    ```
  </Tab>
</Tabs>

## Reading ZenRows-specific headers

ZenRows adds monitoring headers to every response, useful for debugging and tracking usage:

```python theme={null} theme={null}
response = client.get(url)

print(response.headers.get("X-Request-Id"))          # unique ID for this request
print(response.headers.get("X-Request-Cost"))        # dollar cost of this request (e.g., 0.001)
print(response.headers.get("X-Request-Credits"))     # number of credits consumed by this request
print(response.headers.get("Concurrency-Remaining"))  # concurrency slots left on your plan
print(response.headers.get("Zr-Final-Url"))           # final URL after redirects
```

<Tip>
  If you ever contact ZenRows support about a failed request, include the `X-Request-Id` header value. It lets the team trace the exact request in the logs.
</Tip>

Headers coming from the target website itself are prefixed with `Zr-` to distinguish them from ZenRows' own headers. See the full list in the [Universal Scraper API setup guide](/universal-scraper-api/universal-scraper-api-setup#response-headers).

## Checking success before using the body

Because the SDK returns the response as-is rather than raising on error status codes, always confirm the request succeeded first:

```python theme={null} theme={null}
response = client.get(url)

if response.ok:
    data = response.text
else:
    print(f"Request failed: {response.status_code}")
```

<Card title="Error Handling and Retries" icon="shield" href="/universal-scraper-api/sdk/python/error-handling-and-retries">
  More on this pattern, plus how to configure automatic retries.
</Card>

## Troubleshooting

* **`response.json()` raises `JSONDecodeError`**: The body isn't JSON. Confirm you requested `json_response=true` or that the target actually returns JSON before calling `.json()`.
* **Saved file (PDF, screenshot, image) is corrupted**: You wrote `response.text` instead of `response.content`, or opened the file in text mode (`"w"`) instead of binary (`"wb"`).
* **`response.headers.get("X-Request-Id")` returns `None`**: The header name is case-insensitive in `requests`, so this shouldn't normally happen; confirm the request actually reached ZenRows (check `response.status_code` first) rather than failing at the network layer.
* **`response.request.url` doesn't match the URL you passed**: This is expected. It's the full URL sent to ZenRows' API (including your `apikey` and `params` as query parameters), not the target URL by itself.

## Pricing

The response itself doesn't add cost. ZenRows adds two headers to help you track usage:

* `X-Request-Cost`: the dollar cost of the request (e.g., `0.001`)
* `X-Request-Credits`: the number of credits consumed by the request

Both values are based on the `params` used:

| Configuration                           | Cost multiplier vs. basic |
| --------------------------------------- | ------------------------- |
| Basic request                           | 1x                        |
| `js_render=True`                        | 5x                        |
| `premium_proxy=True`                    | 10x                       |
| `js_render=True` + `premium_proxy=True` | 25x                       |

See [ZenRows Pricing](/first-steps/pricing).

## FAQ (Frequently Asked Questions)

<AccordionGroup>
  <Accordion title="Is the response object specific to ZenRows?">
    No. It's the standard `requests.Response` object from Python's `requests` library. Any `requests` documentation or Stack Overflow answer about it applies here too.
  </Accordion>

  <Accordion title="How do I get the response as a Python dict?">
    Call `response.json()` if the body is JSON. For example, when using `json_response=true` or `autoparse=true` on structured data.
  </Accordion>

  <Accordion title="Where do I find how much a request cost?">
    Check the response headers: `X-Request-Cost` gives you the dollar cost (e.g., `0.001`), and `X-Request-Credits` gives you the number of credits consumed.
  </Accordion>
</AccordionGroup>
