Skip to content

Type to search pages.

View .md

Your own provider keys

Runs use your Anthropic and OpenAI keys, not ours. You store one, you cap what it may spend in a month, and you rotate it when you want to. This page is about both places you can do that: the console, and af provider.

This is the hosted arrangement, and it needs a control plane. To keep a key on your own machine instead, with no account and no hosted anything, see your own model key. That is the free and self-hosted path and it has no monthly cap, which is the trade: a cap is only a cap when something you control checks it before the money is spent.

The key is sealed with AES-256-GCM under a secret held outside the database, bound to your organization and to the provider. A row copied to another tenant does not open. A row edited by one bit does not open.

Beside the ciphertext there are three things a screen may read: the provider, the last four characters, and a fingerprint. That is deliberately everything a screen needs, which is what makes the rule keepable rather than aspirational: nothing has a reason to ask for the key.

The plaintext exists in one function, the one putting it into a request to the provider. It is not in an event, an artifact, a log line, or a support bundle.

There is no way to read a key back. Not in the console, not in the API, not in the CLI. There is no scope that grants it and no route that returns it. Storing a secret and retrieving one are different capabilities, and nothing here needs the second. If you have lost a key, make a new one at the provider.

A provider with no budget cannot spend anything. A missing cap reads as zero rather than as unlimited, because the alternative on somebody else’s key is an unbounded bill.

The cap is checked before the key is decrypted. A run with no allowance never causes the key to exist in the control plane’s memory at all, which is the difference between a cap and a suggestion.

A cap of zero is allowed and means what it says: refuse everything for this provider. It has to be asked for, though. A blank field or a missing value is refused rather than read as zero, because a silent cap of zero looks exactly like a working setup until every run is refused for having no allowance.

Provider keys in the navigation, or /keys on your control plane. Paste a key, store it, set a cap.

Storing, rotating, removing and capping are for owners and admins. Everybody else sees the same page without the forms: the last four and the fingerprint, which is enough to tell whether a run was refused for want of a key, and nothing that would let them change one.

af provider does the same things. It needs a token that asked for the capability, which a plain af login does not have:

af login --scope providers.write

The scope appears on the screen where you approve the login, so nobody grants this without seeing the words. See Signing in from a terminal.

$ af provider list
PROVIDER KEY MONTHLY CAP SPENT
anthropic ********7777 50.00 USD 12.50 USD
openai not set none, so nothing may be spent not tracked

The key is masked with plain asterisks rather than bullet characters, because this output is read in CI logs and pasted into pull request comments as often as it is read on a terminal, and neither of those is guaranteed to have the character. A provider with no cap says so in words for the same kind of reason: a dash in that column reads as unlimited and it means the opposite.

The key is never an argument. There is no --key flag and there will not be one: a secret on a command line is written to your shell’s history file, is visible in ps to every other user on the machine, and is captured by any recording of the terminal. It is exposed three times before it is sent anywhere.

So there are three ways to give it, and none of them put it in the argument vector.

Asked for on the terminal, not echoed:

af provider set anthropic

Piped, for a password manager or a file:

af provider set anthropic --stdin

Out of a named environment variable:

af provider set anthropic --from-env ANTHROPIC_API_KEY

With no terminal and no --stdin, the command refuses and says so. It does not try to read: a read from a stdin nobody is typing into either blocks forever or returns nothing at once, and both look like a network problem in a CI log.

af provider budget anthropic 50

Dollars per month, for the current month. af provider list shows what has been spent against it.

A call is charged to the month whose cap allowed it out, not to the month it finished in. A long completion that starts at 23:59 on the last day of a month is checked against that month’s cap, so that is where the money lands. Set the next month’s cap before it starts: a month with no cap cannot spend at all, which is the safe direction for somebody else’s key.

Store the new key. Rotating replaces the old one and revokes it in the same transaction, so there is never a moment with two live keys and no way to say which one a run charged.

af provider set anthropic --stdin < new-key.txt

If the key you give is the one already stored, the command says so rather than reporting a rotation that did not happen. That is the mistake people make at the exact moment they believe they have replaced a leaked key.

The old fingerprint stays in the audit log, so it is always possible to say which key was in use when, without either key being readable.

af provider rm anthropic

Runs that need the provider are refused afterwards, with a message saying why, rather than falling back to a key of ours.

This does not reach the provider. Removing a key here stops us using it and stops nobody else. If it leaked, revoke it at Anthropic or OpenAI as well.

Removing a key that is not there is not an error. This is a command people run in a hurry, and a retry after a timeout must not report failure for reaching the state you asked for.

Runs do not hold your key. They ask the control plane, which holds it, and the control plane makes the call to Anthropic or OpenAI.

That is the only arrangement in which a cap is a cap. A key handed to a build machine is spent by that machine, and this would find out afterwards if it found out at all. Here the budget is checked before the key is decrypted, so a run with no allowance never causes the key to exist in memory, let alone reach a provider.

Point the runner at the control plane and give it a token where the provider key used to go:

export ANTHROPIC_BASE_URL=https://app.dev.antifailure.dev/byok/anthropic
export ANTHROPIC_API_KEY=<your Antifailure token>

or, for OpenAI:

export OPENAI_BASE_URL=https://app.dev.antifailure.dev/byok/openai
export OPENAI_API_KEY=<your Antifailure token>

Nothing else changes. The request body, the response body and the error shapes are the provider’s own, so a client library that knows how to read an Anthropic error keeps working.

What comes back carries one extra header, x-antifailure-cost-usd, so a run can say what it spent without asking again.

No allowance left: 402, and the request never reaches the provider. A 402 rather than a 401 because retrying with a different token will not help, and a client that treats it as an authentication problem will loop.

A model with no configured price: 400, before the call. Discovering an unpriced model afterwards means the money is already spent and the only choice left is whether to lie about it. The message names the model and how to price it.

A streamed request: 400. Neither caller in this product streams today, and a pass-through that could not read usage out of the stream would cost money and record nothing, which is worse than refusing.

Console af provider
See which keys are set any member providers.view
Store, rotate, remove, cap owner, admin providers.write and owner or admin
Read a key back nobody nobody

Scope and role are both checked, and they answer different questions. Scope is what this token may do, so a laptop signed in months ago cannot touch a key. Role is what this person may do, so somebody who cannot change a key in the console cannot change one from a shell either.

Every store, rotation, removal and cap is an entry, and each one records where it came from: web for the console, cli for a terminal. “Somebody rotated the key while signed in to the console” and “a token on a build machine rotated it” are the same event without that and different incidents with it.

The entry carries the fingerprint and the last four. It never carries a key, because it is a record an operator reads, sometimes over somebody’s shoulder.

Storing a key needs AF_PROVIDER_KEY_SECRET, thirty-two bytes, base64. Without it the control plane serves normally and says it cannot store keys, in the console and in af provider list, rather than accepting one and failing later.

Generate one with:

openssl rand -base64 32

Keep it outside the database. It is the whole point: somebody with a copy of the database and no copy of this secret has nothing.