Skip to content

Omit Authorization header when api_key is empty - #795

Draft
fern-support wants to merge 2 commits into
mainfrom
fern/optional-auth-empty-api-key
Draft

Omit Authorization header when api_key is empty#795
fern-support wants to merge 2 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.

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>
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.

2 participants