Skip to content

Authentication

Two ways to send a Bearer Authorization header — provide at most one:

async with HTTPClient.build(
    base_url="https://api.example.com/", bearer_token="my-static-token"
) as client:
    await client.get("items/7")

bearer_token is a static string, sent as-is on every request. For a token that expires or rotates, pass bearer_auth instead — an async callable (sync callable on SyncHTTPClient) resolved fresh on every request, not just once at build() time:

async def get_current_token() -> str:
    return await token_store.get_access_token()  # e.g. refreshed from a cache or auth server


async with HTTPClient.build(
    base_url="https://api.example.com/", bearer_auth=get_current_token
) as client:
    await client.get("items/7")  # calls get_current_token() for this request
    await client.get("items/8")  # calls it again — always the latest token

bearer_token/bearer_auth both only ever produce a Bearer Authorization header. For a Basic-auth username/password pair instead, pass basic_auth — provide at most one of the three:

async with HTTPClient.build(
    base_url="https://api.example.com/", basic_auth=("my-username", "my-password")
) as client:
    await client.get("items/7")

password may be None for a username with no password. There's no other custom-auth-scheme option today.

Every verb also takes skip_auth, which omits the Authorization header for that one call — useful when a client configured with bearer_token/bearer_auth/basic_auth also needs to hit a differently-authenticated target through the same instance, e.g. a presigned S3 URL that must never see your API's own token:

async with HTTPClient.build(
    base_url="https://api.example.com/", bearer_token="my-static-token"
) as client:
    await client.get("items/7")  # gets the Authorization header
    await client.get("https://presigned-bucket.example.com/file", skip_auth=True)  # doesn't

skip_auth=True also skips calling bearer_auth for that request — if refreshing the token is expensive (a network round-trip to an auth server, say), that cost isn't paid on a skipped call.

Default headers

For anything that isn't a Bearer token — an API key header, a custom user-agent, whatever your API needs on every request — pass default_headers at build() time. Unlike bearer_auth, these are fixed for the client's whole lifetime, resolved once, not per-request:

async with HTTPClient.build(
    base_url="https://api.example.com/",
    default_headers={"x-api-key": "my-api-key"},
) as client:
    await client.get("items/7")  # sent with every request through this client

Combine freely with bearer_token/bearer_auth/basic_auth — they set different headers (Authorization vs. whatever you name here).

Timeouts

timeout (seconds, default 30.0) applies to the whole client, covering every request made through it:

async with HTTPClient.build(base_url="https://api.example.com/", timeout=5.0) as client:
    await client.get("items/7")  # raises HTTPTimeoutError if this takes longer than 5s

Pass timeout=None to disable it and fall back to pyreqwest's own default. See Error handling for HTTPTimeoutError.

Every verb also takes its own timeout, overriding the client's for that one call only:

async with HTTPClient.build(base_url="https://api.example.com/", timeout=5.0) as client:
    await client.get("items/7")  # uses the client default, 5s
    await client.get("exports/large-file.csv", timeout=60.0)  # this call gets 60s instead

There's no way to make a single call wait forever when the client itself has a finite timeout — pyreqwest's own per-request .timeout() only ever accepts a duration, never a sentinel meaning "no timeout." To remove the limit entirely, build the client with timeout=None instead.