Guide 23 of 30
For your agent: .md · all guides
An LLM API key
Runs on: your browser (the provider’s console). The key then goes to your server.
Used by: every AI feature in your API.
Your API calls a model provider with a secret key. You need one only if the app has an AI feature. The key lives on the server, never in the app.
What it costs
The provider bills you per token: text in (input), text out (output), and a cheaper rate for input it has seen before (cached input). Photos count as input by size.
The onebox templates speak the OpenAI Chat Completions format, which many providers offer. Pick one:
| Provider | Good for | Base URL |
|---|---|---|
| OpenRouter | one key for many vendors (Claude, GPT, Gemini, open models); easy to compare | https://openrouter.ai/api/v1 |
| OpenAI | GPT models directly | https://api.openai.com/v1 |
| Google Gemini | cheap, fast Flash models | https://generativelanguage.googleapis.com/v1beta/openai/ |
| Anthropic | Claude directly. Its OpenAI-compatible layer is for testing; for production use OpenRouter or the native API (see the harness’s providers.md) | https://api.anthropic.com/v1/ |
Prices change often and differ by model by 10x or more. Checked on 2026-09-28, examples per million tokens (input / output): Claude Sonnet 5 $2 / $10, Claude Haiku 4.5 $1 / $5, on OpenRouter’s public list. Treat these as a ballpark and check the provider’s pricing page.
All of them bill a card or prepaid credit. Set a monthly spending limit in the provider’s billing settings before you ship. It still protects you when your own budget code has a bug.
Steps
- Create an account with the provider you picked.
- Add a payment method or prepaid credit.
- Set a monthly usage limit on the billing or limits page.
- Create an API key on the API keys page. Name it after the app and the
environment (
myapp-prod). Make a second key for development. - Copy the key once. Do not paste it into a prompt, a commit, a chat or an issue.
- Data policy. Check the provider’s API data policy: retention, and whether API data is used for training. On OpenRouter, the privacy settings let you allow only providers that do not train on your data, and turn on zero data retention. Your consent text (ai-consent.md) must match what you choose here.
Where the values go
The running API reads environment variables. backend.md (“Secrets”) shows how the box gets them from your secrets tool:
Llm__BaseUrl=https://openrouter.ai/api/v1
Llm__ApiKey=<the key>
Llm__Model=anthropic/claude-sonnet-5
For a Node API: LLM_BASE_URL, LLM_API_KEY, LLM_MODEL.
Check it works
curl -sS "$BASE/chat/completions" -H "Authorization: Bearer $LLM_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"model":"'"$MODEL"'","messages":[{"role":"user","content":"Say ok"}],"max_completion_tokens":5}' \
| jq '.choices[0].message.content, .usage'
You see "ok" (or similar) and a usage object with token counts.
Common errors
- 401: wrong key, or a key for a different provider than the base URL.
- 404 model not found: the model name is the provider’s own. OpenRouter
names have a vendor prefix (
anthropic/…); direct APIs do not. - 400 on
max_completion_tokens: an older or local server wantsmax_tokens. - 429: rate limit or no credit left. Check the billing page.
- It works locally and not on the box: the variable is set but empty in
the container. Compose turns
KEY: ${VAR}withVARunset intoKEY="", andconfig["KEY"] ?? "default"keeps the"". Write${VAR:?required}in the compose file, and treat a blank value as unset in code.