Skip to Content
Getting startedProviders & API keys

Providers & API keys

Six providers are built in. Each one implements the same interface and registers itself, so switching provider or model is a setting — never a code change, and never a different agent. All six go through the Vercel AI SDK with real streaming, native tool calling, and usage accounting.

ProviderProvider idAPI key variableDefault model
AnthropicanthropicANTHROPIC_API_KEYclaude-sonnet-4-5
OpenAIopenaiOPENAI_API_KEYgpt-4o
GeminigeminiGEMINI_API_KEYgemini-3.6-flash
DeepSeekdeepseekDEEPSEEK_API_KEYdeepseek-chat
MiniMaxminimaxMINIMAX_API_KEYMiniMax-M2
Z.aizaiZAI_API_KEYglm-5.2

The default model only applies when nothing else specifies one; in practice your pick from /model is pinned to the session from its first turn.

Setting a key

The way you are meant to do it is /model in the TUI: pick a provider, pick a model, paste the key into the masked field. That writes both halves of what a session needs — the credential and the choice — to ~/.freecode/config.json:

{ "providers": { "anthropic": { "apiKey": "sk-ant-…" } }, "current": { "provider": "anthropic", "model": "claude-sonnet-4-5" } }

You can write that file by hand. Keep it to yourself: it is the one file in FreeCode that holds a secret, which is exactly why permissions and hooks live somewhere else that is safe to commit.

Lookup order

getApiKey (providers/config.ts) tries these in order:

  1. the provider’s environment variable
  2. config.json → providers.<id>.apiKey

so the environment wins over the stored key, like every other configuration surface here. A -coding-plan variant is the one exception: the catalogue declares the base provider’s env var for it, so the generic env var is checked after the variant’s own config.json entry (or a MINIMAX_API_KEY export would hijack a deliberately-configured plan key that bills a different plan), and the base provider’s stored key is the final fallback.

If everything misses it throws a message naming both the config path and the variable.

An environment variable alone is enough when it is the only credential: with no current.provider set, a sole configured credential (say, just ANTHROPIC_API_KEY exported) selects that provider, interactively and headlessly alike. Several credentials with no choice stay an error naming them — picking among them would be a guess — so run /model once, or pass the provider explicitly: freecode run --model anthropic/claude-sonnet-4-5 "…".

Switching models

WhereHow
TUI/model, any time — the mode line shows what is active
Headlessfreecode run --model <provider>/<model> (no slash changes only the model)
Everywhereedit current in ~/.freecode/config.json

The model is pinned to the session at creation and persisted with it (server.ts:269). That is deliberate: leaving it unset used to let each turn fall through to the provider’s default model, which is how a session configured for one MiniMax model quietly ran on a smaller one and overflowed a window the meter said was 20% full.

Where the model list comes from

FreeCode does not ship a model table. Model ids, context windows, output limits, and whether a model accepts images all come from models.dev , fetched once and cached in ~/.freecode/cache/models-dev.json with a 5-minute TTL and an indefinite on-disk fallback (models-dev.ts:9). Offline, you keep working from the cache.

This matters more than a model picker: the context window it reports is what compaction budgets against, and the vision flag is what decides whether an attached image is sent at all.

Failing over to another provider

When a provider exhausts its retry budget or hits a fatal error, FreeCode can move the run to another one:

{ "recovery": { "fallbackProviders": ["openai", "deepseek"] } }

Tried in order, each with its own retry budget (agent/recovery/manager.ts:381). A 429 that means quota exhausted is never retried against the same provider — waiting cannot help, and each retry re-sends the whole conversation for a guaranteed rejection — so a fallback chain is the only thing that keeps a long run alive when your plan runs out mid-task.

There is no UI for this; it is a hand-edit in config.json.

What differs between providers

Not much reaches the agent loop. The one real fork is how the system prompt is shaped: Anthropic-shaped providers (Anthropic, MiniMax, Z.ai — the latter two speak the Anthropic Messages format at a different base URL) receive system blocks that can carry individual prompt-cache breakpoints, while OpenAI-shaped providers get those blocks joined into one string because their caching is automatic and there is nothing to mark.

Expect vendor quirks below that line. MiniMax stringifies numeric tool arguments, which is why every tool schema declares an explicit type and the orchestrator coerces on the way in. The full picture is in the provider layer.

Known gaps

Found while writing this page; each is also tracked in TODO.md.

  • Nothing surfaces which source a key came from. The environment now overrides the stored key, but when both are set nothing reports which one a request actually used — diagnosing a wrong-key 401 still means checking both by hand.