# Requests User Guide

Requests is the de-facto Python HTTP library — the one Pythonistas reach for
when they need to talk to a REST API, scrape a web page, download a file, or
hit any HTTP endpoint without wrestling with `urllib`. It wraps `urllib3` with
a clean, expressive API: `requests.get(url)` returns a `Response`; the rest of
the surface flows from that single idea.

This guide covers everything an application developer needs to use Requests
effectively: installation, the seven HTTP verb helpers, session management,
authentication, file uploads, streaming, proxies, TLS, and the full exception
taxonomy. It documents version **2.33.1** (built from the `514c162` commit on
the `main` branch) and targets **Python 3.10 or later**.

For deep architecture, the transport-adapter contract, hooks internals, and
extension points, see the companion **Requests Administrator & Developer Guide**.

## 1. Quick Start

Requests provides a Pythonic API for HTTP. You ask for a URL with a verb, you
get a `Response` object back, you read attributes off it. There is no event
loop, no callback dance, no `urlopen()` ceremony.

### What you get

- **Seven verb helpers** at the package root — `requests.get`, `post`, `put`,
  `patch`, `delete`, `head`, `options`, plus the generic `requests.request`.
- **`requests.Session`** for connection pooling, cookie persistence, and shared
  auth/headers across many requests.
- **`Response`** object with `.status_code`, `.text`, `.content`, `.json()`,
  `.headers`, `.cookies`, `.iter_content()`, `.iter_lines()`, `.raise_for_status()`.
- **Built-in authentication** — Basic, Digest, Proxy-Basic — plus a simple
  `AuthBase` hook for custom schemes.
- **Automatic JSON encoding/decoding**, **multipart file uploads**, **automatic
  decompression** of gzip/deflate, **automatic content-encoding detection**
  via `charset_normalizer`/`chardet`, and **transparent connection reuse**
  through `urllib3`'s pool manager.
- **TLS verification on by default** with the `certifi` CA bundle.
- **A clean exception hierarchy** rooted at `requests.exceptions.RequestException`.

### Prerequisites

- Python 3.10 or newer. Earlier Pythons will refuse to install at `setup.py`
  level: the package raises `Requests requires Python 3.10 or later.` and exits.
- An internet connection for the install step. Nothing else.

### Install

```bash
python -m pip install requests
```

```text
Successfully installed requests-2.33.1
```

### Make your first request

```python
>>> import requests
>>> r = requests.get('https://www.python.org')
>>> r.status_code
200
>>> b'Python is a programming language' in r.content
True
```

That is the entire pattern. `requests.get(url)` returns a `Response`; you read
off whatever attribute you want.

> [!TIP]
> If you only need one or two requests, the top-level helpers (`requests.get`,
> `requests.post`, ...) are perfect. If you are about to issue multiple
> requests against the same host, jump to the Sessions section — sharing
> a `Session` reuses TCP connections and is dramatically faster.

### What to try next

- **POST a JSON payload:** `requests.post(url, json={'key': 'value'})`.
- **Add an auth tuple:** `requests.get(url, auth=('user', 'pass'))`.
- **Set a timeout:** `requests.get(url, timeout=5)`.
- **Persist a session:** `s = requests.Session(); s.get(...)`.

## 2. Installation

### System requirements

| Requirement | Minimum | Notes |
|---|---|---|
| Python | 3.10 | Hard floor — `setup.py` exits with an error on older Pythons. |
| `urllib3` | >= 1.21.1 | Installed automatically. The 2.x series is fully supported. |
| `certifi` | latest | Provides the bundled trust store used by `requests.certs.where()`. |
| `charset_normalizer` | >= 2.0, < 4.0 | Default character-set detection. |
| `chardet` | >= 3.0.2, < 8.0.0 | Optional alternative to `charset_normalizer`. |
| `idna` | latest | Used for IDNA hostname encoding (international domain names). |

If both `chardet` and `charset_normalizer` are missing at import time, you will
see a `RequestsDependencyWarning`: *"Unable to find acceptable character
detection dependency (chardet or charset_normalizer)."* Encoding detection
falls back to UTF-8 in that case.

> [!IMPORTANT]
> Requests will refuse to install on Python < 3.10. If you are stuck on an
> older interpreter, pin to a legacy Requests release; this version (2.33.1)
> will not run.

### Install methods

=== "pip"
    ```bash
    python -m pip install requests
    ```

=== "Poetry"
    ```bash
    poetry add requests
    ```

=== "uv"
    ```bash
    uv add requests
    ```

=== "From source"
    ```bash
    git clone https://github.com/psf/requests
    cd requests
    pip install .
    ```

### Optional companion packages

Requests does not declare its own optional extras in the briefing surface, but
the runtime probes for two well-known optional packages and changes behavior
when they are present:

- **SOCKS proxy support** via `urllib3[socks]` — install `PySocks` (or the
  `urllib3` SOCKS extra) if you need to route through `socks5://`,
  `socks5h://`, `socks4://`, or `socks4a://` proxies. Without it, any attempt
  to use a SOCKS proxy raises `InvalidSchema('Missing dependencies for SOCKS
  support.')`.
- **PyOpenSSL fallback** — if Python's `ssl` module reports `HAS_SNI=False`
  (a now-rare situation on legacy embedded builds), Requests calls
  `urllib3.contrib.pyopenssl.inject_into_urllib3()` to enable SNI via PyOpenSSL.
  You almost certainly do not need to think about this on modern CPython.

### Post-install verification

```python
>>> import requests
>>> requests.__version__
'2.33.1'
>>> requests.get('https://httpbin.org/get').status_code
200
```

If a request succeeds and you get a 200 back, your install is healthy.

### Bug-report helper

Requests ships a tiny module that prints diagnostic information about your
environment — the Python implementation and version, OS, `urllib3`, OpenSSL,
`charset_normalizer`/`chardet`, `idna`, `cryptography`, and the Requests
version itself.

```bash
python -m requests.help
```

```json
{
  "chardet": {"version": null},
  "charset_normalizer": {"version": "3.x.x"},
  "cryptography": {"version": "..."},
  "idna": {"version": "..."},
  "implementation": {"name": "CPython", "version": "3.12.x"},
  "platform": {"release": "...", "system": "..."},
  "pyOpenSSL": {"openssl_version": "...", "version": "..."},
  "requests": {"version": "2.33.1"},
  "system_ssl": {"version": "..."},
  "urllib3": {"version": "..."},
  "using_charset_normalizer": true,
  "using_pyopenssl": false
}
```

Paste this into any GitHub bug report — the maintainers ask for it explicitly.

## 3. Configuration

Requests is a library, not an application: there is **no config file**, no
`~/.requestsrc`, no `--config` flag. You configure behavior either per-call
(keyword arguments on each request) or per-session (attributes on a `Session`).
The library does, however, honor a small, well-defined set of environment
variables.

### Environment variables Requests reads

| Variable | Read by | Effect |
|---|---|---|
| `REQUESTS_CA_BUNDLE` | `Session.merge_environment_settings()` | Path to a CA bundle used for TLS verification. Overrides the bundled `certifi` store. |
| `CURL_CA_BUNDLE` | `Session.merge_environment_settings()` | Same role as `REQUESTS_CA_BUNDLE`; consulted if `REQUESTS_CA_BUNDLE` is unset. Provides cURL compatibility. |
| `NETRC` | `requests.utils.get_netrc_auth()` | Path to an alternative `.netrc` file. If unset, Requests checks `~/.netrc` then `~/_netrc`. |
| `HTTP_PROXY` / `HTTPS_PROXY` / `ALL_PROXY` | via `urllib.request.getproxies()` | Default proxies per scheme. Lower-case forms (`http_proxy`, ...) are also picked up on POSIX. |
| `NO_PROXY` | via `should_bypass_proxies()` | Comma-separated list of hosts/CIDRs/suffixes that bypass proxies. Lower-case `no_proxy` also works. |

> [!NOTE]
> All of the above only fire when `Session.trust_env` is `True` (the default).
> Set `s.trust_env = False` to ignore environment variables entirely — useful
> in tests, sandboxed processes, or when you want strictly explicit config.

### Default headers

Every request, via the default Session, ships with these four headers (case
preserved by `CaseInsensitiveDict`):

```python
{
    'User-Agent':      'python-requests/2.33.1',
    'Accept-Encoding': 'gzip, deflate',  # exact list comes from urllib3.make_headers
    'Accept':          '*/*',
    'Connection':      'keep-alive',
}
```

You can override any of these per-request (`headers={'User-Agent': '...'}`)
or once on a session (`s.headers.update({'User-Agent': '...'})`).

### Session attributes you can set

| Attribute | Default | Purpose |
|---|---|---|
| `headers` | `default_headers()` | `CaseInsensitiveDict` merged into every request. |
| `auth` | `None` | `(user, pass)` tuple or `AuthBase` instance applied to every request. |
| `proxies` | `{}` | `{scheme: url}` mapping. |
| `params` | `{}` | Query-string params merged into every request URL. |
| `hooks` | `{'response': []}` | Response hooks (the only event Requests dispatches). |
| `verify` | `True` | TLS verification: `True`, `False`, or path to CA bundle. |
| `cert` | `None` | Client certificate: path to `.pem` or `(cert, key)` tuple. |
| `stream` | `False` | If `True`, response body is not preloaded. |
| `trust_env` | `True` | Honor environment proxies, netrc, and CA-bundle env vars. |
| `max_redirects` | `30` | Raises `TooManyRedirects` past this. |
| `cookies` | empty `RequestsCookieJar` | Persisted across requests in the session. |
| `adapters` | `{'http://': HTTPAdapter, 'https://': HTTPAdapter}` | Transport adapters keyed by URL prefix. |

Detailed configuration semantics — every kwarg, every adapter knob, every
proxy-resolution rule — live in the Admin Guide.

## 4. Core Usage

The Requests API is small. There are seven verb helpers and a generic
dispatcher. Every one of them ultimately constructs a `Session`, builds a
`Request`, prepares it into a `PreparedRequest`, dispatches through an
adapter, and returns a `Response`.

### 4.1 The seven verb helpers

```python
import requests

requests.get(url, params=None, **kwargs)
requests.options(url, **kwargs)
requests.head(url, **kwargs)         # allow_redirects=False by default
requests.post(url, data=None, json=None, **kwargs)
requests.put(url, data=None, **kwargs)
requests.patch(url, data=None, **kwargs)
requests.delete(url, **kwargs)
requests.request(method, url, **kwargs)  # generic — accepts any method name
```

`HEAD` is the only helper whose default differs from the others: it sets
`allow_redirects=False` because chasing redirects defeats the purpose of HEAD.

### 4.2 The kwargs every verb helper accepts

These pass through to `Session.request()` whichever helper you use:

| Keyword | Type | Purpose |
|---|---|---|
| `params` | dict / list of 2-tuples / bytes | Query-string parameters. |
| `data` | dict / list of 2-tuples / bytes / file-like / iterable | Request body (form-encoded if dict). |
| `json` | any JSON-serializable | Body — sets `Content-Type: application/json`, calls `json.dumps(...)`. |
| `headers` | dict | Extra request headers; merged with session headers. |
| `cookies` | dict / `CookieJar` | Cookies to send. |
| `files` | dict | Multipart form upload — see file uploads section. |
| `auth` | tuple / `AuthBase` | Basic-auth tuple or auth handler. |
| `timeout` | float / `(connect, read)` tuple | Per-phase timeout in seconds. |
| `allow_redirects` | bool | Defaults to `True` (`False` for HEAD). |
| `proxies` | dict `{scheme: url}` | Per-scheme proxy URLs. |
| `verify` | bool / str | TLS verify: `True`, `False`, or CA-bundle path. |
| `stream` | bool | If `True`, do not preload body. |
| `cert` | str / `(cert, key)` tuple | Client TLS certificate. |
| `hooks` | dict `{'response': [callable, ...]}` | Hook callbacks. |

### 4.3 Reading the Response

The object returned by every verb helper is a `requests.Response`.

```python
r = requests.get('https://api.github.com/repos/psf/requests')

r.status_code         # 200
r.reason              # 'OK'
r.url                 # final URL after redirects
r.ok                  # True if status_code < 400
r.headers             # CaseInsensitiveDict
r.headers['content-type']    # 'application/json; charset=utf-8'
r.encoding            # 'utf-8' (parsed from Content-Type, or detected lazily)
r.text                # body decoded to str
r.content             # body as bytes
r.json()              # body parsed as JSON, raises JSONDecodeError on bad JSON
r.cookies             # RequestsCookieJar
r.history             # list of Responses from prior redirect hops
r.elapsed             # datetime.timedelta from request send to response headers parsed
r.raw                 # underlying urllib3 HTTPResponse (only useful with stream=True)
r.links               # parsed Link header (for paginated APIs)
r.is_redirect         # True if status is in REDIRECT_STATI and Location header present
r.is_permanent_redirect  # True for 301 or 308
r.next                # PreparedRequest for the next redirect hop, if any
r.request             # PreparedRequest that produced this Response
```

`Response` is also a context manager (calls `.close()` on exit) and
truthy-iff-`ok` (so `if r:` works).

### 4.4 Verifying success

`raise_for_status()` is the canonical way to convert a 4xx/5xx into an
exception:

```python
r = requests.get('https://httpbin.org/status/404')
r.raise_for_status()
# requests.exceptions.HTTPError: 404 Client Error: NOT FOUND for url: https://httpbin.org/status/404
```

It raises `HTTPError` for 400-599; for 1xx-3xx it returns `None`. There is no
side effect.

### 4.5 Sending a JSON body

```python
import requests

payload = {'name': 'requests', 'stars': 50000}
r = requests.post('https://httpbin.org/post', json=payload)
print(r.json()['json'])
# {'name': 'requests', 'stars': 50000}
```

Pass `json=...` and Requests serializes with the standard `json` module (or
`simplejson` if installed) and sets `Content-Type: application/json`. If JSON
serialization fails, you get an `InvalidJSONError`.

### 4.6 Sending form data

```python
r = requests.post('https://httpbin.org/post', data={'key1': 'value1', 'key2': 'value2'})
```

When `data` is a dict (or list of 2-tuples), Requests form-urlencodes it and
sets `Content-Type: application/x-www-form-urlencoded`. When `data` is bytes
or a string, it is sent verbatim with no automatic Content-Type.

### 4.7 Query-string parameters

```python
payload = {'q': 'requests', 'page': 1}
r = requests.get('https://api.github.com/search/repositories', params=payload)
print(r.url)
# https://api.github.com/search/repositories?q=requests&page=1
```

Requests handles URL-escaping for you. List values become repeated keys
(`?key=a&key=b`) — that is, `params={'k': ['a', 'b']}` produces `?k=a&k=b`,
not `?k=a%2Cb`.

### 4.8 Custom headers

```python
r = requests.get(url, headers={'Accept': 'application/vnd.github+json'})
```

Headers are case-insensitive on read (`r.headers['content-type']` works
regardless of how the server cased it), but the case you write is the case
that goes on the wire.

> [!TIP]
> Requests validates header names and values before sending. Headers with
> leading whitespace, embedded `\r`/`\n`, or non-string non-bytes values raise
> `InvalidHeader` from `check_header_validity()`. This blocks the
> CRLF-injection class of bugs.

### 4.9 Timeouts

There is no default timeout. **A request without `timeout=` will hang forever
if the server stops responding.** Always set one for production code.

```python
# Single float — applies to both connect and read phases.
requests.get(url, timeout=5)

# Tuple — (connect_timeout, read_timeout) in seconds.
requests.get(url, timeout=(3.05, 27))
```

Read-timeouts raise `ReadTimeout`; connect-timeouts raise `ConnectTimeout`.
Both inherit from `Timeout`, which inherits from `RequestException`.

> [!WARNING]
> Passing an invalid timeout (e.g. a 3-tuple) raises
> `ValueError: Invalid timeout {timeout}. Pass a (connect, read) timeout
> tuple, or a single float to set both timeouts to the same value.`

### 4.10 Cookies

```python
# Send cookies
r = requests.get(url, cookies={'session': 'abc123'})

# Read cookies the server set
print(r.cookies['session_id'])

# Iterate
for c in r.cookies:
    print(c.name, '=', c.value, 'domain=', c.domain, 'path=', c.path)
```

`r.cookies` is a `RequestsCookieJar` — a `cookielib.CookieJar` subclass that
also speaks the dict interface (`['name']`, `.get('name')`, `.set('name',
'value')`). When two cookies share a name across domains/paths, dict access
raises `CookieConflictError` with the message
`"There are multiple cookies with name, '<name>'"` — disambiguate with
`.get('name', domain='...', path='...')`.

### 4.11 Redirects

Requests follows redirects automatically (except for `HEAD`). Each hop is
captured in `r.history`:

```python
r = requests.get('http://github.com')   # 301 to https://github.com
r.url            # 'https://github.com/'
r.history        # [<Response [301]>]
r.history[0].url # 'http://github.com/'
```

To opt out:

```python
r = requests.get(url, allow_redirects=False)
```

The redirect chain is bounded by `Session.max_redirects` (default `30`).
Exceeding the limit raises `TooManyRedirects` with the message
`"Exceeded {max} redirects."`

`Response.is_redirect` is `True` when the status is one of `301, 302, 303,
307, 308` AND a `Location` header is present. `is_permanent_redirect` is
`True` for `301`/`308`.

> [!NOTE]
> Requests intelligently strips `Authorization` and `Proxy-Authorization`
> headers when a redirect crosses hosts, downgrades from HTTPS to HTTP, or
> changes ports — this prevents leaking credentials to a third party. The
> `http→https` upgrade on the standard 80→443 port is the one allowlisted
> exception.

### 4.12 Streaming responses

For large downloads, set `stream=True` and consume the body in chunks. The
body is **not** preloaded into memory until you iterate or touch `.content`.

```python
with requests.get(url, stream=True) as r:
    r.raise_for_status()
    with open('file.bin', 'wb') as f:
        for chunk in r.iter_content(chunk_size=8192):
            f.write(chunk)
```

`iter_content(chunk_size=N, decode_unicode=False)` yields raw bytes (or str
if you ask). `iter_lines(chunk_size, decode_unicode, delimiter)` yields one
line at a time — useful for line-delimited APIs like Server-Sent Events.

> [!CAUTION]
> `iter_lines()` is **not reentrant-safe**. Do not call it twice concurrently
> on the same Response, and do not interleave it with `iter_content()`.

> [!WARNING]
> Once a streaming body has been fully read, accessing `.content` again
> raises `RuntimeError("The content for this response was already
> consumed")`. Reading via `iter_content` after consumption raises
> `StreamConsumedError`.

Responses are context managers; `with requests.get(..., stream=True) as r:`
ensures the underlying connection is released back to the pool when the block
exits, even on exception.

### 4.13 File uploads (multipart)

```python
files = {'report': open('report.csv', 'rb')}
r = requests.post(url, files=files)
```

Each value can be:

- A file-like object — Requests reads `.read()` and guesses a filename from
  `obj.name`.
- A 2-tuple `(filename, fileobj)`.
- A 3-tuple `(filename, fileobj, content_type)`.
- A 4-tuple `(filename, fileobj, content_type, custom_headers_dict)`.

```python
files = {
    'logo':   ('logo.png', open('logo.png', 'rb'), 'image/png'),
    'report': ('report.csv', csv_bytes, 'text/csv', {'X-Note': 'monthly'}),
}
r = requests.post(url, files=files, data={'caption': 'May 2026'})
```

Mixing `data=` and `files=` is supported — form fields ride alongside file
parts in the same multipart body.

> [!WARNING]
> Streamed bodies and files are mutually exclusive. Passing a generator/iterator
> as `data` *and* providing `files` raises `NotImplementedError("Streamed
> bodies and files are mutually exclusive.")`.

Opening a file in **text mode** for upload triggers `FileModeWarning` —
Requests measured the byte length using the file size, but the file is being
read as text, which may produce an incorrect Content-Length on non-ASCII
content. Open with `'rb'`.

### 4.14 Raw streaming bodies (chunked uploads)

If `data=` is an iterator, generator, or file-like object that is not a
string/list/tuple/Mapping, Requests treats it as a streamed body. If a length
can be determined (`super_len()` finds it via `__len__`, `len`, `fileno`, or
seek-tell), `Content-Length` is set; otherwise Requests sets
`Transfer-Encoding: chunked`.

```python
def gen():
    yield b'first chunk'
    yield b'second chunk'

requests.post(url, data=gen())
```

### 4.15 Response encoding

`r.text` decodes `r.content` using `r.encoding`. The encoding is determined
from the `Content-Type` header (e.g. `text/html; charset=utf-8`). If the
header has no charset, `r.encoding` is `None`, and `r.text` falls back to
`r.apparent_encoding` — `charset_normalizer.detect(...)["encoding"]` (or
`chardet.detect(...)`, or `'utf-8'` if neither library is installed).

Override the encoding manually if the server lies:

```python
r.encoding = 'utf-8'
print(r.text)
```

## 5. Sessions

Sessions are the single biggest performance and correctness lever in
Requests. **Use a `Session` whenever you make more than one request to the
same host.**

```python
import requests

with requests.Session() as s:
    s.headers.update({'User-Agent': 'my-bot/1.0'})
    s.auth = ('user', 'pass')
    s.params = {'api_version': 'v1'}

    r1 = s.get('https://api.example.com/users')
    r2 = s.get('https://api.example.com/orders')
```

### 5.1 What a Session gives you

- **Connection pooling.** The default `HTTPAdapter` keeps up to 10 pools, each
  holding up to 10 keep-alive connections. The next request to the same host
  reuses an existing TCP+TLS connection — typical 5-10x speed-up versus
  one-shot calls.
- **Cookie persistence.** Cookies set by the server on one response are
  automatically sent on the next request. The session-level `s.cookies` is
  a `RequestsCookieJar`.
- **Default settings.** Set `s.headers`, `s.auth`, `s.proxies`, `s.params`,
  `s.verify`, `s.cert`, `s.hooks`, `s.stream`, `s.trust_env`,
  `s.max_redirects` once and they apply to every subsequent request.
- **Hooks scoped to the session.** Register a response hook on the session
  and every response in that session goes through it.

### 5.2 The full Session method surface

| Method | Purpose |
|---|---|
| `Session.request(method, url, **kw)` | Generic dispatcher — verb helpers all delegate here. |
| `Session.get/options/head/post/put/patch/delete(url, **kw)` | Verb helpers. |
| `Session.send(prepared, **kw)` | Send an already-prepared request. |
| `Session.prepare_request(req)` | Build a `PreparedRequest` from a `Request` using session defaults. |
| `Session.merge_environment_settings(url, proxies, stream, verify, cert)` | Apply env-var fallbacks. |
| `Session.get_adapter(url)` | Return the adapter mounted for the given URL. |
| `Session.mount(prefix, adapter)` | Register a transport adapter for URLs starting with `prefix`. |
| `Session.close()` | Close every mounted adapter (releases pooled sockets). |

### 5.3 Per-request overrides

Settings on a session are *defaults* — every call can override them:

```python
s.verify = '/etc/corp/ca.pem'
s.get(url1)                 # uses corp CA
s.get(url2, verify=False)   # disables verification just for this call
```

The merge rules: dict-typed settings (`headers`, `params`, `proxies`, `hooks`,
`cookies`) merge per-request-on-top-of-session; scalar settings (`verify`,
`cert`, `stream`) replace.

### 5.4 The `requests.session()` shim

The lowercase `requests.session()` function returns a new `Session` for
backward compatibility. It is **deprecated since 1.0.0** in the docstring;
prefer the `Session` class directly.

> [!TIP]
> Always close your session, either with a `with` block (`with
> requests.Session() as s:`) or an explicit `s.close()`. Long-lived processes
> that forget to close sessions leak file descriptors for the pooled
> connections.

## 6. Authentication

Requests ships three built-in auth handlers and exposes a base class for
custom schemes.

### 6.1 Basic authentication

```python
from requests.auth import HTTPBasicAuth

# Long form
r = requests.get(url, auth=HTTPBasicAuth('user', 'pass'))

# Short form — a plain tuple is interpreted as Basic auth
r = requests.get(url, auth=('user', 'pass'))
```

Both produce `Authorization: Basic <base64(user:pass)>`. Non-string usernames
or passwords trigger a `DeprecationWarning` (they will be unsupported in
Requests 3.0.0).

### 6.2 Digest authentication

```python
from requests.auth import HTTPDigestAuth

r = requests.get(url, auth=HTTPDigestAuth('user', 'pass'))
```

Digest auth in Requests is *thread-local* — each thread keeps its own nonce
state, so the same `HTTPDigestAuth` instance is safe to share across threads.
Algorithms supported: `MD5`, `MD5-SESS`, `SHA`, `SHA-256`, `SHA-512`. The
`auth=md5` and `auth=auth` qop values are handled; `auth-int` is not
implemented.

> [!NOTE]
> The Digest implementation registers a response hook on the request to
> detect a 401 challenge, parse it, and retry with the proper `Authorization`
> header. Open issue #6102 reports that non-Latin credentials may misbehave;
> stick to ASCII credentials for safety.

### 6.3 Proxy authentication

```python
from requests.auth import HTTPProxyAuth

s = requests.Session()
s.auth = HTTPProxyAuth('user', 'pass')   # adds Proxy-Authorization
```

`HTTPProxyAuth` derives from `HTTPBasicAuth` and writes the
`Proxy-Authorization` header instead of `Authorization`.

### 6.4 Custom auth — subclass `AuthBase`

Any callable that takes a `PreparedRequest` and returns a `PreparedRequest`
qualifies as an auth handler. The cleanest pattern is to subclass `AuthBase`:

```python
from requests.auth import AuthBase

class TokenAuth(AuthBase):
    def __init__(self, token):
        self.token = token

    def __call__(self, r):
        r.headers['Authorization'] = f'Bearer {self.token}'
        return r

requests.get(url, auth=TokenAuth('eyJhbGc...'))
```

`AuthBase.__call__` is the only required method; the base class raises
`NotImplementedError("Auth hooks must be callable.")` if you forget.

### 6.5 `.netrc` auto-auth

If `auth` is not set explicitly and `Session.trust_env` is `True`, Requests
checks `~/.netrc` (or `~/_netrc` on Windows, or the `NETRC` env var) for
credentials matching the request hostname. If a match is found, Basic auth
is applied automatically.

```text
# ~/.netrc
machine api.example.com
  login alice
  password supersecret
```

Disable this behavior by setting `s.trust_env = False`.

## 7. Common Tasks

Real-world recipes you will hit in practice.

### 7.1 Talking to a JSON REST API

```python
import requests

API = 'https://api.example.com/v1'
TOKEN = 'sk-...'

with requests.Session() as s:
    s.headers.update({'Authorization': f'Bearer {TOKEN}'})

    # GET
    users = s.get(f'{API}/users', timeout=10).json()

    # POST
    new = s.post(f'{API}/users', json={'name': 'Ada'}, timeout=10)
    new.raise_for_status()

    # PATCH
    s.patch(f'{API}/users/{new.json()["id"]}', json={'role': 'admin'})

    # DELETE
    s.delete(f'{API}/users/{new.json()["id"]}')
```

### 7.2 Downloading a large file

```python
def download(url, dest, chunk=1 << 14):
    with requests.get(url, stream=True, timeout=(5, 60)) as r:
        r.raise_for_status()
        with open(dest, 'wb') as f:
            for block in r.iter_content(chunk_size=chunk):
                f.write(block)

download('https://example.com/big.iso', '/tmp/big.iso')
```

The `(5, 60)` timeout means: 5s to connect, 60s of inactivity allowed
between data packets.

### 7.3 Server-Sent Events / line-delimited JSON

```python
import json
with requests.get(url, stream=True, timeout=(5, None)) as r:
    for line in r.iter_lines(decode_unicode=True):
        if line:
            event = json.loads(line)
            handle(event)
```

`timeout=(5, None)` keeps the read timeout disabled — the connection should
stay open indefinitely as data dribbles in.

### 7.4 Posting a file

```python
with open('photo.jpg', 'rb') as f:
    r = requests.post(url,
                      files={'photo': ('photo.jpg', f, 'image/jpeg')},
                      data={'caption': 'sunset'})
```

### 7.5 Going through a proxy

```python
proxies = {
    'http':  'http://proxy.corp:3128',
    'https': 'http://proxy.corp:3128',
}
r = requests.get('https://api.example.com', proxies=proxies)
```

You can also point per-host:

```python
proxies = {'http://example.com': 'http://proxy1:3128'}
```

The longer the prefix match, the higher its precedence. SOCKS proxies use
`socks5://`, `socks4://`, `socks5h://`, or `socks4a://` schemes; install
`urllib3[socks]` (PySocks) first.

### 7.6 Mutual TLS (client certificate)

```python
# Single-file PEM with both cert and key
r = requests.get(url, cert='/etc/ssl/client.pem')

# Separate files
r = requests.get(url, cert=('/etc/ssl/client.crt', '/etc/ssl/client.key'))
```

### 7.7 Disabling TLS verification (testing only)

```python
r = requests.get('https://self-signed.example.com', verify=False)
```

> [!CAUTION]
> Setting `verify=False` makes your application vulnerable to
> man-in-the-middle (MitM) attacks. The library docstring states this
> explicitly: *"Only set this to `False` for testing."* In production,
> point `verify=` at your private CA bundle instead.

### 7.8 Session hooks

Hook every response through a callback for logging or retry logic:

```python
def log_response(r, *args, **kwargs):
    print(f'[{r.status_code}] {r.request.method} {r.url} ({r.elapsed.total_seconds():.2f}s)')

with requests.Session() as s:
    s.hooks['response'].append(log_response)
    s.get('https://httpbin.org/get')
```

`response` is the only hook event Requests dispatches; the registered list
runs in order, and any non-`None` return value replaces the response object
seen downstream.

### 7.9 Pickling a Response or Session

`Response` and `Session` both implement `__getstate__`/`__setstate__`. You
can pickle a response *only after* its content has been fully consumed —
`__getstate__` forces `.content` access first, then drops the live `raw`
socket. Sessions pickle their stable attributes (`headers`, `cookies`,
`auth`, `proxies`, ...) but rebuild adapters on load via `mount()`.

### 7.10 Using `Request` + `prepare()` directly

Useful when you want to inspect or mutate the wire bytes before sending:

```python
from requests import Request, Session

req  = Request('POST', url, json={'x': 1})
prep = req.prepare()
# inspect or modify
print(prep.headers, prep.body)

with Session() as s:
    resp = s.send(prep, timeout=5)
```

> [!WARNING]
> `Session.send()` only accepts a `PreparedRequest`. Passing a plain `Request`
> raises `ValueError("You can only send PreparedRequests.")` — use
> `s.prepare_request(req)` first, or simply call `s.request(method, url, ...)`.

### 7.11 Looking up status-code names

```python
>>> requests.codes.ok
200
>>> requests.codes.not_found
404
>>> requests.codes.teapot
418
>>> requests.codes['temporary_redirect']
307
```

`requests.codes` is a `LookupDict` where every HTTP status has at least one
human-readable alias; common codes have several (`requests.codes.OK`,
`requests.codes.okay`, `requests.codes.all_ok`, ... all return 200).

## 8. Troubleshooting

When you see a `requests.exceptions.*`, the message + status decision tree
below maps to the actual cause.

### Quick decision tree

- **`MissingSchema`** — your URL is missing `http://` or `https://`. The
  message will literally suggest `https://{url}`.
- **`InvalidURL`** — empty host, invalid IDNA label, bad percent-escape,
  starts with `*` or `.`, etc.
- **`InvalidSchema`** — no adapter is mounted for that URL prefix
  (`No connection adapters were found for {url!r}`), or a SOCKS proxy was
  requested without `urllib3[socks]` installed.
- **`ConnectTimeout`** — TCP/TLS handshake did not complete in time.
- **`ReadTimeout`** — connection succeeded but no bytes arrived in time.
- **`SSLError`** — certificate verification failed, or no acceptable cert
  bundle was found.
- **`ProxyError`** — the proxy itself rejected the request.
- **`TooManyRedirects`** — exceeded `Session.max_redirects` (default 30).
- **`HTTPError`** — only raised by `r.raise_for_status()` on 4xx/5xx.
- **`ChunkedEncodingError`** — server announced chunked transfer but sent
  malformed chunks.
- **`ContentDecodingError`** — `Content-Encoding` declared gzip/deflate but
  the body could not be decoded.

All of these inherit from `RequestException`, which inherits from `IOError`.
Catch the broadest you can tolerate.

### Error: `MissingSchema: Invalid URL '...': No scheme supplied. Perhaps you meant https://...?`

**Cause:** You passed `requests.get('example.com')` — no scheme.

**Solution:** Prefix with `https://` or `http://`. There is no implicit default.

**Prevention:** Validate URLs at config time; treat `urllib.parse.urlparse(u).scheme`
as required.

### Error: `SSLError: ... certificate verify failed`

**Cause:** The server's certificate chain does not validate against the
trust store Requests is using (usually `certifi`'s bundle).

**Solution:** If the server uses a private/internal CA, set
`verify='/path/to/ca.pem'` on the call or set `REQUESTS_CA_BUNDLE` /
`CURL_CA_BUNDLE` in the environment. **Never** ship `verify=False` to
production.

**Prevention:** Use a proper PKI; if you must add a corporate root,
distribute it via `REQUESTS_CA_BUNDLE`.

### Error: `OSError: Could not find a suitable TLS CA certificate bundle, invalid path: ...`

**Cause:** `verify=` points at a path that does not exist, or the bundled
`certifi` data is missing (broken install).

**Solution:** Reinstall `certifi`, or correct the path in `verify=`.

### Error: `ConnectTimeout` / `ReadTimeout`

**Cause:** Server unreachable, DNS slow, network issue, or actual server lag.

**Solution:** Try a longer `timeout=`. For unreliable networks, use a
custom `HTTPAdapter(max_retries=Retry(...))` — see the Admin Guide for the
full retry knobs.

**Prevention:** Always set `timeout=` on every external call. Never rely on
Python defaults (there is none).

### Error: `TooManyRedirects: Exceeded 30 redirects.`

**Cause:** A redirect loop, or a legitimately long chain.

**Solution:** Inspect `r.history` for the loop. Bump `s.max_redirects`
only if you are sure there is no loop.

**Prevention:** Set `allow_redirects=False` and follow manually if you do
not trust the upstream.

### Error: `ChunkedEncodingError`

**Cause:** Server announced `Transfer-Encoding: chunked` but the body framing
is malformed (truncated, bad chunk-size line, etc.).

**Solution:** Issue #7341 reports that the documentation around this error
is misleading; in practice it almost always indicates an upstream/proxy
problem. Retry the request; if it persists, capture the raw bytes with
a packet trace and report to the server operator.

### Error: `InvalidSchema: No connection adapters were found for 'ftp://...'`

**Cause:** Requests only ships HTTP/HTTPS adapters by default. There is no
FTP support. (Issue #6711 noted that some AWS S3 SDK paths broke after
2.32.0 changes — make sure you are using a current version.)

**Solution:** Use a protocol-appropriate library, or mount a custom adapter.

### Error: `JSONDecodeError` from `r.json()`

**Cause:** The response body is not valid JSON, or the encoding declaration
was wrong and the body could not be decoded into UTF-{8,16,32}.

**Solution:** Inspect `r.text` and `r.headers['content-type']` first.
Wrap in try/except:

```python
try:
    data = r.json()
except requests.exceptions.JSONDecodeError:
    print('Bad JSON:', r.text[:200])
```

### Error: `RuntimeError: The content for this response was already consumed`

**Cause:** You called `iter_content()` to drain a streaming response, then
asked for `.content`. The stream is one-shot.

**Solution:** Cache the body as you iterate, or skip `.content`/`.text`
after streaming.

### Error: `StreamConsumedError`

**Cause:** Calling `iter_content()` twice on the same Response after the
content was already consumed (typically because `stream=False` so the body
was loaded eagerly).

**Solution:** Don't iterate a non-streamed response. If you need the body,
use `r.content` or `r.text`.

### Error: `UnrewindableBodyError: Unable to rewind request body for redirect.`

**Cause:** The original request body was a non-seekable file or generator,
and a redirect required Requests to replay it.

**Solution:** Pass a seekable file (`open(..., 'rb')`) or buffer the body
into bytes before sending.

### Error: `InvalidProxyURL: Please check proxy URL. It is malformed and could be missing the host.`

**Cause:** The `proxies={'https': '...'}` value is missing a host (e.g.
`'http://:3128'`).

**Solution:** Fix the proxy URL to include the hostname.

> [!WARNING]
> If you build production code on Requests, treat `RequestException` (and
> ideally each subclass you handle distinctly) as part of your contract.
> Bare `except Exception:` will mask coding bugs alongside transient
> network issues.

## 9. FAQ

**Q: How do I disable SSL verification just for one request?**

Pass `verify=False`. Requests will print a single `urllib3` warning per
process. **Do not** use this in production code.

**Q: How do I send a list value as a repeated query parameter?**

Pass a list: `params={'tag': ['python', 'http']}` produces
`?tag=python&tag=http`. If you need a single comma-joined value, join the
list yourself first.

**Q: What is the default timeout?**

There is none. A request without `timeout=` waits forever on a stalled
server. Always set one — typical values are 5-30 seconds for connect+read
combined.

**Q: Why is my POST data being url-encoded when I want raw JSON?**

Use `json=...`, not `data=...`. `data=` form-urlencodes a dict; `json=`
serializes with `json.dumps()` and sets `Content-Type: application/json`.

**Q: Where does Requests get its CA bundle?**

From `certifi` — the path is returned by `requests.certs.where()`. You can
override with `REQUESTS_CA_BUNDLE` or `CURL_CA_BUNDLE` env vars, or with
`verify='/path/to/bundle.pem'` per call/session.

**Q: How do I see the exact bytes Requests sent?**

Build a `PreparedRequest` and inspect `prep.method`, `prep.url`,
`prep.headers`, `prep.body`. To see what comes back, set the standard logging
level on `urllib3` to DEBUG:

```python
import logging
logging.basicConfig(level=logging.DEBUG)
```

**Q: Can I share a Session across threads?**

Yes — but use one `Session` per thread for best performance. The connection
pools are thread-safe; the high-level Session machinery is also safe in
practice but contention reduces the value of pooling.

**Q: Can I use Requests with asyncio?**

Requests is fully synchronous. For async HTTP, use `httpx` (which has a
similar API) or `aiohttp`. The PSF maintains Requests as the synchronous
canonical Pythonic HTTP library; there is no `requests.aio` module.

**Q: How do I cancel an in-flight request?**

There is no native cancellation. The standard pattern is a short
`timeout=`. For long downloads, signal a flag and break out of your
`iter_content()` loop; the underlying socket will be released when the
Response goes out of scope.

**Q: Where do I report bugs?**

[`https://github.com/psf/requests/issues`](https://github.com/psf/requests/issues).
Include the output of `python -m requests.help` — the maintainers ask for it.

**Q: Does Requests retry failed connections automatically?**

By default, **no.** `HTTPAdapter` is constructed with `max_retries=0`. To
enable retries, mount a custom adapter:

```python
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

s = requests.Session()
retries = Retry(total=5, backoff_factor=0.5, status_forcelist=[502, 503, 504])
s.mount('https://', HTTPAdapter(max_retries=retries))
s.mount('http://',  HTTPAdapter(max_retries=retries))
```

See the Admin Guide for the full retry/backoff/status-list reference.

**Q: How do I use a custom `ssl.SSLContext`?**

Subclass `HTTPAdapter` and override `build_connection_pool_key_attributes()`
to set `ssl_context` in the pool kwargs. Open issues #5316 and #7040 cover
edge cases here. The Admin Guide walks through the full subclass example.

**Q: Why are my non-Latin Digest credentials failing?**

Open issue #6102 — `HTTPDigestAuth` does not handle non-Latin credentials
correctly today. Use Basic auth with explicit UTF-8 encoding, or hold
ASCII-only credentials.

**Q: How do I verify my install version at runtime?**

```python
>>> import requests
>>> requests.__version__
'2.33.1'
```

For a full diagnostic dump, `python -m requests.help`.

---

_Generated April 2026 from psf/requests at 514c162. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
