Your own model key
The agents drive a real browser. To read a page and decide what a person would do next, they need a model, and the key is yours. It stays on your machine, the call goes straight to the provider, and nothing hosted is involved.
Everything on this page works with no account, no control plane and no network except the one call to your provider.
af model set anthropic # asks for the key, without echoing itaf model test # one cheap call: does it work?af model show # what is configured, and where it came fromTwo ways to bring a key
Section titled “Two ways to bring a key”They are different arrangements and the right one depends on whether you have a control plane.
af model |
af provider |
|
|---|---|---|
| Where the key lives | this machine | the control plane, sealed |
| Who calls the provider | this machine | the control plane |
| Monthly spending cap | none | checked before the key is decrypted |
| Needs an account | no | yes |
If you have a control plane, prefer af provider. A cap is only a cap when
something you control checks it before the money is spent; a key handed to a
build machine is spent by that machine and you find out afterwards, if at all.
If you do not, this page is the whole story and nothing here is a lesser version of it.
Having both is the one combination to watch
Section titled “Having both is the one combination to watch”Nothing routes a run through your control plane on its own. Reaching the sealed key means pointing the base URL at the gateway yourself:
export ANTHROPIC_BASE_URL=https://your-control-plane/byok/anthropicexport ANTHROPIC_API_KEY=<your Antifailure token>So a local key and a capped key on a control plane can both exist, and the
local one wins, because it is the one the runner reaches without being told
anything. That is the right precedence: the base URL is an explicit instruction
and a stored key is a default. It is also the more expensive way round to be
wrong, since somebody who ran af provider budget anthropic 50 has a ceiling
they believe in and are not getting.
af model show and af doctor say so rather than leaving you to notice:
warn Model key anthropic/claude-sonnet-5 from the system keyring, not cappedThey check only whether this machine is signed in to a control plane, which is a local read and not a request, so the warning appears whenever a cap could have been in force and never on a machine that has no control plane at all.
With no key at all
Section titled “With no key at all”Runs work. This is worth saying plainly because it is the thing people assume is not true: without a key, workflows still run, still drive a real browser, still sign in, still capture evidence and still produce a verdict. The deterministic planner takes over, which follows a workflow by matching its expectations against the page.
What a model adds is tolerance for a workflow written as a sentence. “Sign up and confirm you land on a signed in page” is something the deterministic planner half understands and a model follows without being told every field.
af doctor reports this as a pass rather than a warning, because it is a
supported mode:
ok Model key none set, so agents use the deterministic plannerStoring a key
Section titled “Storing a key”The key is never an argument, and there is no --key flag. A secret on a
command line is written to your shell’s history file. It is visible in ps to
every other user on the machine. It is captured by any recording of the
terminal. That is three exposures before it is used once.
So there are three ways to give it, and none of them put it in the argument vector.
af model set anthropic # asks, without echoingaf model set anthropic --stdin < key.txt # reads one lineaf model set anthropic --from-env MY_KEY # reads that variableanthropic and openai are the providers the agents can use.
Where it goes
Section titled “Where it goes”Into the system keyring where the platform has one, which is the macOS keychain, the freedesktop Secret Service on Linux, and the Credential Manager on Windows. That is the only place on a workstation where a secret is protected by something other than file permissions.
Some machines have no keyring: a Linux server without libsecret, most
containers, and the platforms with no credential store at all. There the key
goes into the encrypted local store, a file under .antifailure. It is
sealed with AES-256-GCM under a key derived from a passphrase with Argon2id.
That store needs AF_SECRET_PASSPHRASE, and there is deliberately no default.
With no keyring and no passphrase there is nowhere to write a key. The command
says so rather than writing a file that only looks encrypted:
AF-SEC-004 The encrypted local store has no passphrase: no system keyringanswered and AF_SECRET_PASSPHRASE is not set.The command always says which of the two it used, because they do not have the same properties and “stored” for either would hide the difference that matters.
Where a key is looked up
Section titled “Where a key is looked up”The same order every other secret in this product uses, most specific first:
- This shell’s environment,
ANTHROPIC_API_KEYorOPENAI_API_KEY. .envin the repository.- The encrypted local store, under
.antifailure. - The system keyring, which is what
af model setwrites to.
The first source that has a key wins. An export beats a stored key, which is deliberate: somebody who typed one meant it and is usually trying a different key for one run. There is one precedence rule in this product rather than a separate one for models, which is the whole reason to reuse the chain.
With keys for both providers, Anthropic is used.
When the key you stored is not the key in use
Section titled “When the key you stored is not the key in use”This is the most common first-run surprise: a key exported months ago in a shell profile, a fresh one stored today, and every run quietly using the old one. Nothing is broken and nothing normally says anything, so both commands say it:
$ af model set anthropic
Stored the anthropic key in the system keyring.
It is not the key runs will use. ANTHROPIC_API_KEY is also set in this shell's environment, which is asked first. Unset it there, or storing this one has no effect.It names where the other key is rather than only that there is one, because “unset it” is not advice until you know which file to open.
When nothing shadows the key you just stored, that paragraph does not appear
and the command ends with Check it works: af model test.
af model show reports it from the other direction, naming the source that won
and the one being shadowed.
Proving it works
Section titled “Proving it works”$ af model test
Model Asking https://api.anthropic.com for one token as claude-sonnet-5...
The key works. api.anthropic.com answered as claude-sonnet-5. 412 ms, fingerprint 8f2c41ad.One real completion of a single token, which costs a fraction of a cent. A real call rather than a check of the key’s shape, because a well formed key that was revoked this morning passes every shape check there is.
Every failure it can tell apart, it tells apart, because they have different fixes and being told only that the call failed sends you to the wrong one:
| What happened | What it says |
|---|---|
| The key is revoked or wrong | Store the right one. A key that worked yesterday was revoked or rotated. |
| The account has no credit | The key is valid and there is nothing to spend. Retrying will not help. |
| The model name does not exist | Set AF_MODEL to a model this key can use, or unset it. |
| Rate limited | The key works. Wait and run it again; nothing needs changing. |
| The provider is down | This says nothing about the key. |
| Nothing answered | Check this machine can reach the endpoint. |
| It answered, but not with a completion | The endpoint is not speaking the provider’s API. |
The last one matters more than it looks. A reverse proxy in front of a model
that is not running answers 200 with an error page, and reporting that as a
working key would certify a setup that fails on the first real run.
A success is written down, and af model show and af doctor report it:
ok Model key anthropic/claude-sonnet-5 from the system keyring, verified 2026-08-30The note is tied to the exact key that was verified. Rotate the key and it is discarded rather than shown beside the new one, which would be a lie in exactly the situation where you are checking whether a rotation worked.
A key that is set and has never been tested is a warning rather than a pass. A revoked key and a working one are indistinguishable without making a call, and the difference costs a whole run to discover.
A local model, or a gateway
Section titled “A local model, or a gateway”Point the base URL somewhere else. This is a first class path: it is tested, and the failures it produces have their own advice.
export ANTHROPIC_BASE_URL=http://127.0.0.1:11434export OPENAI_BASE_URL=http://127.0.0.1:8080af model testThe endpoint has to speak the provider’s own API, because that is what the
runner and the sidecar send. Concretely, for anthropic it must accept
POST {base}/v1/messages with an x-api-key header and answer with the
provider’s response shape; for openai it must accept
POST {base}/v1/chat/completions with a bearer token and answer with
choices[].message.content. Most local servers and gateways offer an
OpenAI compatible mode, and that is the one to point OPENAI_BASE_URL at.
Set the base URL to the base, without the path on the end. A 404 from a
custom endpoint says so, because “your model name is wrong” would be the wrong
half of the message when the real problem is that the gateway does not serve
that path.
A local model loading its weights for the first time can take longer than any
hosted one ever does. A timeout there is not a sign anything is wrong; the
answer is af model test --timeout 5m.
af model show and af doctor both name a custom endpoint explicitly, so a
run that is quietly going somewhere unexpected is visible rather than something
you have to remember. A control plane gateway is named as that rather than as
an anonymous custom endpoint, because it is the one destination that changes
what the key means:
Endpoint https://your-control-plane/byok/anthropic (your control plane, where the monthly cap applies)Egress policy does not switch the model off
Section titled “Egress policy does not switch the model off”This is worth being explicit about, because this product’s whole job is intercepting and controlling outbound HTTP, and a model call is outbound HTTP.
A default: block manifest does not stop the agents planning with a model,
and you do not have to name your model provider in the manifest.
The policy applies to traffic through the sidecar. Services sit on a network with no route out and every name they resolve points at the sidecar, so their packets have nowhere else to go. Neither model caller is on that network:
- The runner is a subprocess of
afon your own machine, outside the environment entirely. - A synth rule’s model call originates in the sidecar, which is the one container with a route out. It is made with the sidecar’s own client, not through the engine that decides about everybody else’s traffic.
What the policy does govern is the application under test calling a model.
If your own code calls api.anthropic.com, that is traffic through the sidecar
like any other, and under default: block it is refused until a rule names it.
af net log shows the refusal. The same key in the same run can be reached
from two places for two reasons, so it is worth knowing which one you are
looking at.
If a model call does fail, af model test says whether this machine can reach
the endpoint, and it says in as many words that the manifest is not what is
stopping it.
What leaves your machine
Section titled “What leaves your machine”The request to the provider, and nothing else.
The model never sees the page’s HTML. It sees the accessibility snapshot, which is the page’s URL and title, the form fields and controls by their accessible names, and the rendered text of the body. That is what a person navigating with a screen reader gets, it is enough to decide from, and it keeps whatever is in the DOM out of somebody else’s logs. There is no cookie, no local storage, no request body and no markup in the prompt.
The model is also confined to what is on the page. It chooses from a fixed set of actions against names that are actually there, so it cannot invent a button; anything it names that is not on the page is refused rather than attempted.
Your key is not sent anywhere except the provider. It is never written to an event, an artifact, a log line or a support bundle. It is registered with the redactor before it is handed to any subprocess, so output that quotes it is scrubbed on the way back.
Nothing prints a key back. There is no af model get, no --show flag and no
scope that would grant one. What any screen can read is the provider, the
endpoint, the source, and a short non-reversible fingerprint. That is enough to
answer the question this is usually asked: whether the key here is the one you
think it is.
If a key ever ends up in a place that will not answer, a custom endpoint is still somebody else’s code. A gateway that echoes your key back in an error message cannot get it onto your terminal: provider text is redacted before it is printed.
Rotating
Section titled “Rotating”Store the new key. It replaces whatever was there.
af model set anthropic --stdin < new-key.txtaf model testRotating discards the previous verification, so af model show will say the
new key has never been tested until you test it.
This does not reach the provider. Storing a key here does not create one and removing one does not revoke one. If a key leaked, revoke it at Anthropic or OpenAI as well.
Removing
Section titled “Removing”af model rm anthropicIt clears the key from both places this can write, not from the first that answers. A key left in the encrypted store after the keyring entry was removed is a key the next run silently uses, which is the exact failure you are trying to prevent.
It cannot reach a key you exported in a shell or wrote into a .env. It says
so rather than reporting a removal that changed nothing:
Removed the anthropic key from the system keyring.
A ANTHROPIC_API_KEY is still supplied by this shell's environment, so runs will keep using one. This command cannot reach there; unset it yourself.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.
CI has no keyring and no terminal. Use the platform’s own secret store and export the variable, which is the first source in the chain:
- run: af test env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}af model set anthropic --from-env ANTHROPIC_API_KEY is there for a runner that has the key in a variable
and wants it in the store as well. With neither a terminal nor --stdin the
command refuses rather than reading. 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.
Choosing a model
Section titled “Choosing a model”AF_MODEL picks the model for whichever provider’s key is in use.
export AF_MODEL=claude-opus-5Unset, it is claude-sonnet-5 for Anthropic and gpt-4.1 for OpenAI.