Skip to content

Omit Authorization header when api_key is empty - #795

Open
fern-support wants to merge 4 commits into
mainfrom
fern/optional-auth-empty-api-key
Open

Omit Authorization header when api_key is empty#795
fern-support wants to merge 4 commits into
mainfrom
fern/optional-auth-empty-api-key

Conversation

@fern-support

@fern-support fern-support commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

This PR comes from Fern.

Motivation

Some users point the Python SDK at a proxy or a self-hosted deployment that performs its own authentication upstream. In that setup there is no Cohere API key to supply, but the client always sends an Authorization header. Passing an empty key produces a literal Authorization: Bearer header, which some proxies reject outright rather than ignore.

This makes an empty api_key mean "don't send the header at all":

client = cohere.Client(api_key="", base_url="https://my-proxy.example.com")

Changes

Adds omit_authorization_header_when_api_key_is_empty() to src/cohere/overrides.py, wired into run_overrides(). It patches BaseClientWrapper.get_headers and AsyncClientWrapper.async_get_headers so that an empty resolved api_key results in no Authorization header.

Both patches work by inspecting the header that get_headers() has already built, rather than re-resolving the key. That keeps the api_key supplier resolved exactly once per request, which matters because api_key is documented as accepting a callable: a supplier is invoked a single time, and one whose value changes between calls cannot produce a header that disagrees with the token actually used.

The patch is idempotent, guarded by an _omits_empty_authorization marker, so repeated run_overrides() calls do not stack wrappers.

Behaviour is unchanged whenever a key is present: a real key still yields Authorization: Bearer <key>, and api_key=None still falls back to the CO_API_KEY environment variable.

No .fernignore change is needed — src/cohere/overrides.py and tests are both already listed there, so regeneration will not clobber these files.

Testing

Adds tests/test_optional_auth.py — 5 tests, no network access or API key required:

  • an empty api_key omits the header for Client, ClientV2, AsyncClient and AsyncClientV2
  • a provided key is still sent as Bearer <key>
  • a callable returning "" omits the header
  • a callable supplier is invoked exactly once per request
  • a supplier whose value changes between calls cannot strip a header built from a valid token

Verified locally: test_optional_auth.py, test_overrides.py, test_client_init.py, test_embed_streaming.py and test_embed_utils.py pass (22 passed, 1 skipped), and mypy is clean on both changed files. The live-API test modules were not run locally as they require credentials; CI covers those.


Note

Medium Risk
Changes how every HTTP request builds auth headers for all client types; behavior for non-empty keys is unchanged but empty-key and callable-supplier paths are security-sensitive.

Overview
Bumps the SDK to 7.0.9 (metadata, pyproject.toml, and client User-Agent / Fern headers).

Empty api_key no longer sends Authorization. A new override in overrides.py patches sync and async header builders so Authorization: Bearer is removed when the resolved key is empty—supporting proxies/self-hosted endpoints that authenticate elsewhere via cohere.Client(api_key=""). Patches inspect the already-built header (not a second token resolution) so callable api_key suppliers run once per request. The patch is idempotent via _omits_empty_authorization.

Adds tests/test_optional_auth.py covering Client/V2 and async clients, callable keys, and single-invocation behavior.

Reviewed by Cursor Bugbot for commit f0602c0. Bugbot is set up for automated code reviews on this repo. Configure here.

willkendall01 and others added 2 commits August 14, 2026 15:27
patched_get_headers called _get_token() a second time, after get_headers()
had already called it. For the documented callable api_key form this invoked
the supplier twice per request, and a supplier whose value changed between the
two calls produced the wrong header: returning "real-token" then "" stripped
the Authorization header despite a valid token, and the reverse sent
"Bearer " while a valid token was available. Both yield a 401.

Inspect the header get_headers() already built instead, matching what
patched_async_get_headers has been doing.

Co-Authored-By: Claude <noreply@anthropic.com>
@jasonozuzu-cohere
jasonozuzu-cohere marked this pull request as ready for review August 18, 2026 10:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants