ScreenshotNeo

BlogHow-to

Python Requests Headers: Set, Reuse, and Inspect Them (2026)

Learn how to set headers with Python Requests, reuse them safely with sessions, inspect prepared requests, and troubleshoot overrides.

By the ScreenshotNeo team30 September 20268 min read

Python Requests Headers: Set, Reuse, and Inspect Them (2026)

Direct answer: pass a dictionary to headers= for one request, put stable defaults on requests.Session().headers when several calls share them, and inspect response.request.headers to see the prepared headers Requests sent. Use response.headers for headers returned by the server. Set an explicit timeout on every network call.

This guide covers Requests 2.34.2, the release documented by the Python Requests project in 2026. Requests officially supports Python 3.10+ and also runs on PyPy. The examples use ordinary HTTP APIs, but the same rules apply when you call internal services, webhooks, or a website screenshot endpoint.

1. Set headers on one request

For a single call, create a mapping and pass it to headers. Header names are case-insensitive. Values should be strings, bytestrings, or Unicode-compatible values.

import requests

url = "https://api.example.com/items"
headers = {
    "Accept": "application/json",
    "User-Agent": "inventory-client/1.0",
}

response = requests.get(url, headers=headers, timeout=(3.05, 20))
response.raise_for_status()
items = response.json()
print(items)

The Requests Quickstart recommends passing a dictionary to headers=. Requests carries your custom names into the final request, subject to authentication, redirect, proxy, and body-preparation rules described later.

Common request headers

Header Typical purpose Example
Accept Response formats your client can read application/json
Content-Type Format of the request body application/json
User-Agent Identifies the client inventory-client/1.0
Authorization Credentials or bearer token Bearer …
X-Request-ID Trace identifier for one operation abc-123

When you send JSON, prefer json=payload; Requests serializes the object and sets the appropriate content type. If you use data= with a pre-encoded string, set Content-Type yourself.

payload = {"name": "Ada", "role": "admin"}
response = requests.post(
    "https://api.example.com/users",
    json=payload,
    headers={"Accept": "application/json"},
    timeout=20,
)
response.raise_for_status()

2. Reuse defaults with requests.Session

Use a Session when multiple calls share headers, cookies, or a connection pool. Session-level and per-request settings are combined: defaults live on session.headers, while a call can override them with its own headers= mapping.

import requests

session = requests.Session()
session.headers.update({
    "Accept": "application/json",
    "User-Agent": "inventory-client/1.0",
})

first = session.get("https://api.example.com/items", timeout=20)
first.raise_for_status()

second = session.get(
    "https://api.example.com/items/42",
    headers={"X-Request-ID": "abc-123"},
    timeout=20,
)
second.raise_for_status()

Sessions persist cookies and use HTTP keep-alive and connection pooling automatically through urllib3. The Session documentation explains this reusable client state.

Override or remove a default for one call

session.headers.update({"Accept": "application/json"})

response = session.get(
    "https://api.example.com/raw",
    headers={"Accept": "application/octet-stream"},
    timeout=20,
)
response.raise_for_status()

Keep short-lived bearer tokens and endpoint-specific content types out of a Session shared by unrelated hosts. If you need to remove a default for one request, set that key to None in the per-request mapping; Requests omits the key while preparing the request.

session.headers.update({"X-Experimental": "enabled"})
response = session.get(
    "https://api.example.com/items",
    headers={"X-Experimental": None},
    timeout=20,
)

3. Inspect what Requests sent

A response has two different header mappings. response.request.headers belongs to the outgoing PreparedRequest. response.headers belongs to the server response.

Headers can be set per call, shared by a Session, and inspected after preparation.
Headers can be set per call, shared by a Session, and inspected after preparation.
response = session.get("https://api.example.com/items", timeout=20)

sent_headers = dict(response.request.headers)
received_headers = dict(response.headers)

print("sent:", sent_headers)
print("received:", received_headers)

The Advanced Usage guide documents response.request as the PreparedRequest used for the call. Header lookup is case-insensitive, so response.request.headers["user-agent"] and ["User-Agent"] address the same value.

Redact secrets before logging. Do not print or persist bearer tokens, cookies, API keys, proxy credentials, or signed URLs in normal application logs.

Inspect before sending with PreparedRequest

When a value is missing or unexpectedly changed, prepare the request through the Session and inspect it before transmission.

from requests import Request, Session

session = Session()
session.headers.update({"Accept": "application/json"})

request = Request(
    "GET",
    "https://api.example.com/items",
    headers={"X-Debug": "1"},
)
prepared = session.prepare_request(request)

print(dict(prepared.headers))
print(prepared.method, prepared.url)

response = session.send(prepared, timeout=20)
response.raise_for_status()

The API reference describes PreparedRequest as the fully mutable object containing the exact request data that will be sent. Preparing through a Session applies session defaults before inspection.

4. Header precedence and surprising overrides

A dictionary passed to headers= is not always the final authority. Requests applies other configuration while preparing and sending:

  • Authentication: credentials from .netrc can replace an Authorization value, and the auth= parameter has higher precedence.
  • Redirects: Requests removes Authorization when a redirect moves to another host.
  • Proxy credentials: credentials embedded in a proxy URL can replace Proxy-Authorization.
  • Body length: Requests may calculate or replace Content-Length when it can determine the body size.
  • Session merging: per-request values override Session defaults for that call.

If an authorization header disappears, or a content length differs from the value you supplied, inspect the prepared request after all these steps. Avoid forcing transport-managed headers unless you have a specific protocol requirement.

5. Authentication patterns that stay maintainable

Bearer token for one request

import os
import requests

token = os.environ["API_TOKEN"]
response = requests.get(
    "https://api.example.com/items",
    headers={
        "Accept": "application/json",
        "Authorization": f"Bearer {token}",
    },
    timeout=20,
)
response.raise_for_status()

Bearer token on a dedicated Session

session = requests.Session()
session.headers.update({
    "Accept": "application/json",
    "Authorization": f"Bearer {token}",
})

response = session.get("https://api.example.com/items", timeout=20)

Use a dedicated Session per credential scope. This limits accidental credential reuse if your program talks to several services.

6. Timeouts, retries, and reliability

Requests has no default timeout. A call can wait indefinitely if a server stops responding, so set one explicitly. A tuple separates connection and read limits:

response = requests.get(
    "https://api.example.com/items",
    headers={"Accept": "application/json"},
    timeout=(3.05, 20),
)

The Advanced Usage documentation recommends attaching a timeout to requests to external servers. Catch timeout and connection exceptions at your application boundary, and retry only idempotent operations unless the API provides an idempotency key.

import requests

try:
    response = requests.get(
        "https://api.example.com/items",
        timeout=(3.05, 20),
    )
    response.raise_for_status()
except requests.Timeout:
    print("The connection or response took too long")
except requests.HTTPError as exc:
    print(f"The server returned an HTTP error: {exc}")
except requests.RequestException as exc:
    print(f"The request failed: {exc}")

7. Performance and cost considerations

A Session avoids repeatedly creating transport state and enables connection reuse. For a small script, the difference is usually less important than correct timeouts and error handling; for a worker making many calls to the same host, reuse a Session throughout the worker’s lifetime.

Keep headers small and stable. Large cookies or tracing metadata increase request size. Do not add a unique header value to Session defaults when it should be generated per operation; pass it on that request instead.

Requests itself does not charge per request. Your API provider may charge for calls, bandwidth, or authentication. If the request is a screenshot job, provider billing rules apply to the screenshot service rather than to Requests.

8. Troubleshooting checklist

Symptom Likely cause Fix
Server says a header is missing Typo, wrong scope, or a later preparation rule Print a redacted dict(response.request.headers); for pre-send debugging inspect prepared.headers.
Authorization is different auth=, .netrc, or a cross-host redirect Remove competing credentials, disable redirects while diagnosing, and inspect the prepared request.
Content type is rejected Using data= without matching Content-Type Use json=payload for JSON, or set the correct content type for encoded data.
Request hangs No timeout was supplied Set timeout=(connect_seconds, read_seconds) on every call.
Session header leaks to another API One Session is shared across unrelated hosts Use separate Sessions or pass credentials per request.
Header casing looks different Case-insensitive mapping normalizes display casing Compare names case-insensitively; behavior is unchanged.
Proxy authentication fails Proxy URL credentials override your supplied value Inspect proxy configuration and the prepared request; correct the proxy credentials.

9. A complete reusable client

import os
from typing import Any

import requests

class InventoryClient:
    def __init__(self, base_url: str, token: str) -> None:
        self.session = requests.Session()
        self.base_url = base_url.rstrip("/")
        self.session.headers.update({
            "Accept": "application/json",
            "User-Agent": "inventory-client/1.0",
            "Authorization": f"Bearer {token}",
        })

    def get_item(self, item_id: str, request_id: str | None = None) -> Any:
        headers = {"X-Request-ID": request_id} if request_id else None
        response = self.session.get(
            f"{self.base_url}/items/{item_id}",
            headers=headers,
            timeout=(3.05, 20),
        )
        response.raise_for_status()
        return response.json()

    def debug_last(self, response: requests.Response) -> dict[str, dict[str, str]]:
        sent = dict(response.request.headers)
        for secret in ("authorization", "cookie", "proxy-authorization"):
            for key in list(sent):
                if key.lower() == secret:
                    sent[key] = "[redacted]"
        return {"sent": sent, "received": dict(response.headers)}

client = InventoryClient(
    "https://api.example.com",
    os.environ["API_TOKEN"],
)
print(client.get_item("42"))

10. Or skip the browser setup: ScreenshotNeo

If your Python client ultimately needs a clean image or PDF of a web page, you can call ScreenshotNeo directly with Requests. It accepts custom headers, cookies, user agents, Authorization values, wait conditions, CSS and JavaScript, device settings, full-page capture, element selectors, PDF options, caching, signed links, asynchronous jobs, bulk capture, and more. See the ScreenshotNeo API documentation for the complete option list.

ScreenshotNeo removes common overlays before producing the captured page.
ScreenshotNeo removes common overlays before producing the captured page.
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)
print(r.headers.get("X-Page-Verdict"), r.headers.get("X-Billed"))

Equivalent cURL:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Equivalent Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000 screenshots. Create a free ScreenshotNeo account.

11. Frequently asked questions

Are header names case-sensitive?

No. Requests uses a case-insensitive header mapping. Servers should also treat HTTP field names case-insensitively.

Should I use a dictionary or a Session?

Use headers= for one call or an endpoint-specific value. Use Session.headers for stable defaults shared by several calls to the same service.

Why does response.headers not show my request header?

It contains response headers from the server. Inspect response.request.headers for the outgoing request.

Can I force an exact header set?

Prepare a request with session.prepare_request() and inspect or modify the resulting PreparedRequest. Authentication, redirects, proxies, and body handling can still affect what is appropriate to send.

Where should secrets go?

Load them from environment variables or a secret manager, scope them to a dedicated Session or request, and redact them before logging prepared headers.