Developer Guides

Python Requests Headers: Send and Inspect Them Correctly

Send custom headers with Python Requests, inspect outgoing and response headers, handle JSON content types, and keep proxy authentication separate.

Outgoing request headers, server-observed User-Agent, and incoming response headers

Overview

Workflow at a glance

  1. Send explicit headers

    Configure a controlled request and inspect its prepared headers.

  2. Verify at the receiver

    Compare the echo response and response headers; keep destination and proxy credentials separate.

Pass a dictionary through headers= to send Python Requests headers. Inspect response.request.headers for the outgoing request and response.headers for the received response. Those objects describe opposite sides of the exchange.

Headers can declare the expected representation, identify an application or authenticate to an API. They do not change your source IP, run JavaScript or guarantee that a website will accept an automated request.

Send a request with explicit headers

Install Requests with python -m pip install requests. Start the included network_demo.py, then run this local example. The demonstration endpoint echoes the headers it receives.

import requests

headers = {
    "User-Agent": "MiyaIP-Tutorial/1.0",
    "Accept": "application/json",
}
with requests.Session() as session:
    session.trust_env = False  # Isolate this loopback demonstration.
    response = session.get(
        "http://127.0.0.1:8766/headers", headers=headers, timeout=(1, 3)
    )
    response.raise_for_status()
    print("sent_user_agent:", response.request.headers.get("User-Agent"))
    print("received_type:", response.headers.get("Content-Type"))
    observed = response.json()["headers"]
    print("server_saw:", observed.get("User-Agent"))

The local output identifies the sent User-Agent as MiyaIP-Tutorial/1.0, the response type as application/json, and the server-observed User-Agent as the same tutorial value. Compare selected non-sensitive fields rather than dumping every header from an authenticated production request.

Outgoing request headers, server-observed User-Agent, and incoming response headers
The local echo example separates sent fields, server observations and response headers.

Choose headers for the receiving service

Header

What it expresses

Common mistake

Accept

Preferred response representation

Assuming the server must return JSON because the client asked for it

Content-Type

The representation of the body being sent

Setting JSON while sending a form-encoded body

User-Agent

A client-supplied application identifier

Treating a browser-looking string as a complete browser environment

Authorization

Credentials for the destination service

Using a proxy password or sending a token to the wrong host

Accept-Language

Language preference

Assuming it changes the network exit country

HTTP field names are case-insensitive, but use their conventional spelling for readability. Values still have their own syntax. The Requests quickstart explains custom headers and precedence.

Let the body format and Content-Type agree

Use json= for a JSON request body. Requests serializes the object and supplies its JSON Content-Type unless you override that header. This local example sends only demonstration data:

import requests

with requests.Session() as session:
    session.trust_env = False
    session.headers.update({"User-Agent": "MiyaIP-Tutorial/1.0"})
    response = session.post(
        "http://127.0.0.1:8766/headers",
        json={"demo": 1},
        headers={"Accept": "application/json"},
        timeout=(1, 3),
    )
    response.raise_for_status()
    print(response.request.headers.get("Content-Type"))
    print(response.json()["method"])

The example reports application/json and POST. A data= dictionary normally creates a form body instead. Do not set Content-Type manually unless it describes the body you actually send.

A Session is useful for common headers across requests. Per-request values can override those defaults. Keep authentication scoped to the intended destination; do not put a secret in a broadly reused Session and send it to unrelated services.

Keep destination headers separate from proxy credentials

For a MIYAIP proxy connection, configure the proxy URL and credentials through the proxies argument described in the connection guide. Do not add a proxy password as an ordinary Authorization header for the website. Likewise, an MCP key or Crawler API token belongs to its own service and authentication contract.

Unexpected result

What to inspect

HTML arrives when JSON was requested

Status, actual response Content-Type, final URL and whether the endpoint supports JSON

Header value differs from your dictionary

Session defaults, authentication configuration, prepared request and any redirect

HTTP 401

The destination service's authentication requirements

HTTP 407

Proxy authentication, independently of destination headers

HTTP 403

The response explanation and permitted access method; changing User-Agent alone is not a general fix

Connection or TLS error

Diagnose the connection before adjusting content-negotiation headers

For redirects, response.request describes the final request. Inspect the entries in response.history when an earlier request matters. Check only the fields needed for diagnosis; cookies and bearer tokens should stay out of shared logs.

Frequently asked questions

Is response.headers what my client sent?

No. It contains response headers. Use response.request.headers to inspect the associated outgoing request, or a controlled echo endpoint to see what the receiving server observed.

Does Accept-Language select a proxy country?

No. It declares a language preference. Proxy location selection happens in the connection configuration supported by the product.

Can headers make Requests execute JavaScript?

No. Choose a browser workflow when the task requires JavaScript execution or user-interface interaction.

Verify request configuration and network routing separately

Confirm the expected response first. Add a proxy only where the task needs its routing or exit behavior.

Next steps

Run the local demonstration

Download network_demo.py, or copy the complete source below into a file with that name. Run python network_demo.py using Python 3. The service listens only on 127.0.0.1:8766; stop it with Ctrl+C.

"""Local HTTP demonstration. No external requests, credentials or MIYAIP service calls."""
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import urlsplit
import json
import time


class DemoHandler(BaseHTTPRequestHandler):
    def log_message(self, *_):
        pass

    def reply(self, code, body=b"", headers=None):
        self.send_response(code)
        for key, value in (headers or {}).items():
            self.send_header(key, value)
        self.send_header("Content-Length", str(len(body)))
        try:
            self.end_headers()
            self.wfile.write(body)
        except (BrokenPipeError, ConnectionResetError, ConnectionAbortedError):
            pass

    def do_CONNECT(self):
        self.reply(407, headers={"Proxy-Authenticate": 'Basic realm="local-demo"'})

    def do_GET(self):
        path = urlsplit(self.path).path
        if path == "/redirect":
            return self.reply(302, headers={"Location": "/headers"})
        if path == "/loop":
            return self.reply(302, headers={"Location": "/loop"})
        if path == "/slow":
            time.sleep(2)
        if path in {"/headers", "/slow"}:
            payload = {"method": self.command, "headers": dict(self.headers)}
            return self.reply(200, json.dumps(payload, indent=2).encode(),
                              {"Content-Type": "application/json"})
        self.reply(404, b"Use /headers, /redirect, /loop or /slow")

    def do_POST(self):
        body = self.rfile.read(int(self.headers.get("Content-Length", "0")))
        if urlsplit(self.path).path == "/redirect307":
            return self.reply(307, headers={"Location": "/headers"})
        if urlsplit(self.path).path == "/redirect302":
            return self.reply(302, headers={"Location": "/headers"})
        payload = {"method": self.command, "headers": dict(self.headers),
                   "body": body.decode("utf-8", errors="replace")}
        self.reply(200, json.dumps(payload, indent=2).encode(),
                   {"Content-Type": "application/json"})


if __name__ == "__main__":
    print("Local demonstration: http://127.0.0.1:8766 (Ctrl+C to stop)", flush=True)
    ThreadingHTTPServer(("127.0.0.1", 8766), DemoHandler).serve_forever()

Sources