Skip to content

Add api_key: false to omit x-api-key header on ChatAnthropic#585

Merged
brainlid merged 2 commits into
brainlid:mainfrom
jersearls:anthropic-api-key-false-skip-header
Jul 14, 2026
Merged

Add api_key: false to omit x-api-key header on ChatAnthropic#585
brainlid merged 2 commits into
brainlid:mainfrom
jersearls:anthropic-api-key-false-skip-header

Conversation

@jersearls

Copy link
Copy Markdown
Contributor

Motivation

Some deployments front the Anthropic Messages API with their own authentication rather than an Anthropic key — a corporate egress proxy, or an AWS SigV4-signed gateway such as Bedrock "Mantle". On the direct (non-BedrockConfig) ChatAnthropic path, headers/1 unconditionally injects x-api-key, which those endpoints reject or ignore. There was no supported way to suppress it, so the only workaround was monkeypatching the dep at build time.

Change

api_key: false now omits the x-api-key header entirely, letting the caller's own auth take over (for example, SigV4 signing supplied through req_opts). nil and string API keys behave exactly as before.

# key fronted by SigV4-signed gateway — send no x-api-key
ChatAnthropic.new!(%{
  endpoint: "https://my-gateway.example.com/anthropic/v1/messages",
  model: "claude-sonnet-4-5",
  api_key: false,
  req_opts: [aws_sigv4: [...]]
})

Because :api_key is an Ecto :string field, cast/3 would reject the boolean, so the false is pulled out of the attrs before casting and set via put_change/3. The x-api-key/get_api_key behavior is unchanged for every other value.

Tests

Added a describe "api_key: false" block covering: new!/1 and string-keyed new/1 accepting false, the header being omitted when false, and still sent for a string key. Full chat_anthropic_test.exs suite passes (147 tests, 0 failures), formatted, no new compiler warnings.

Notes

Happy to adjust the shape (naming, docs, or an alternative like an explicit skip_api_key field) if you'd prefer a different API — the goal is just a supported escape hatch for self-authenticated endpoints.

Some deployments front the Anthropic Messages API with their own
authentication — a corporate proxy, or an AWS SigV4-signed gateway
such as Bedrock "Mantle" — where the `x-api-key` header is rejected
or ignored. There was previously no way to suppress it on the direct
(non-`BedrockConfig`) path: `headers/1` always injected
`x-api-key`.

Setting `api_key: false` now omits the header entirely, letting the
caller's own auth (e.g. SigV4 signing supplied via `req_opts`) take
over. `nil` and string API keys behave exactly as before.

Because `:api_key` is an Ecto `:string` field, the boolean is pulled
out of attrs before `cast/3` and set with `put_change/3`, so casting
doesn't reject it.
@jersearls
jersearls marked this pull request as ready for review July 9, 2026 14:28
@brainlid

brainlid commented Jul 9, 2026

Copy link
Copy Markdown
Owner

@jersearls Will your situation work with just not setting the api_key at all? It would be nil then. It's not required as part of the struct. It can be overridden per-request on the struct like that. But otherwise, it can fallback to a ENV that is shared across all requests.

What happens if you don't set the API key at all? If you have a separate auth channel, is there something in the library preventing you from doing it?

With Elixir moving more into a fully typed language, I don't like the idea of defining field :api_key, :string and then making the boolean false be expected and supported. Type warnings will result.

@jersearls

Copy link
Copy Markdown
Contributor Author

Hey Mark — thanks for taking a look, and I really appreciate the pushback on the typing angle. You're right that stuffing false into a :string field is awkward as Elixir leans harder into types, and I don't want to make that worse.

I tried the “just leave api_key unset / nil” path you suggested, and unfortunately it doesn't work for the proxy / SigV4-gateway case. On the non-BedrockConfig path, headers/1 always injects x-api-key via:

api_key || Config.resolve(:anthropic_key, "")

So with api_key: nil:

  • if :anthropic_key is configured → the ENV/config key is still sent
  • if it isn't → we still send x-api-key: ""

Either way the header is present, and those self-authenticated endpoints reject or ignore it. (api_key: "" also still emits the header, since "" is truthy for ||.) Today api_key: false is also rejected by Ecto's :string cast, so there's no supported way to omit it.

Totally happy to reshape this so we don't overload a string field. Would you prefer something like:

  1. a dedicated skip_api_key: true (or omit_api_key) boolean field, or
  2. an explicit auth mode / sentinel that stays out of the :string type?

I'll go with whatever fits the direction you want for the library — the only goal on my side is a supported escape hatch so callers with their own auth (e.g. SigV4 via req_opts) don't have to monkeypatch.

@brainlid

brainlid commented Jul 9, 2026

Copy link
Copy Markdown
Owner

Thanks @jersearls for the additional detail. I lean towards a new, dedicated boolean key like suppress_api_key. Just a tweak on the other name options. 🙂

Per maintainer feedback, replace the overloaded `api_key: false` (which
stuffed a boolean into the :string field and required a put_change hack) with
a dedicated `suppress_api_key` boolean field. Keeps :api_key strictly a
string, so no type warnings as Elixir leans into typing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jersearls

Copy link
Copy Markdown
Contributor Author

Sounds good — went with suppress_api_key. Pushed as a new commit.

:api_key stays a plain :string (no more boolean cast hack / put_change), and there's now a dedicated field :suppress_api_key, :boolean, default: false. When it's true, headers/1 omits x-api-key entirely regardless of api_key or the globally configured key; default false preserves existing behavior exactly.

ChatAnthropic.new!(%{
  endpoint: "https://my-gateway.example.com/anthropic/v1/messages",
  model: "claude-sonnet-4-5",
  suppress_api_key: true,
  req_opts: [aws_sigv4: [...]]
})

Tests updated (default is false, accepts true via atom- and string-keyed maps, header omitted when suppressed, header still sent otherwise) and the CHANGELOG entry reworded. Full chat_anthropic_test.exs suite passes (148 tests, 0 failures), formatted, no new warnings.

@brainlid brainlid left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @jersearls!
❤️💛💙💜

@brainlid
brainlid merged commit b6d80cd into brainlid:main Jul 14, 2026
2 checks passed
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