Docs
Pi
Pi — Earendil's terminal coding agent, not the Inflection chatbot and not the coin — takes a custom provider as one block in models.json: a baseUrl, an api, a key and the model ids you want. Four fields and it is talking to Claude and GPT through one key.
A custom provider block in ~/.pi/agent/models.json — baseUrl, api and the model ids — puts Earendil's Pi terminal coding agent on Claude and GPT through one key.
{
"providers": {
"kunavo": {
"baseUrl": "https://api.kunavo.com/v1",
"api": "openai-completions",
"apiKey": "$KUNAVO_API_KEY",
"models": [
{
"id": "claude-sonnet-5",
"name": "Claude Sonnet 5",
"reasoning": true,
"input": ["text", "image"],
"contextWindow": 1000000,
"maxTokens": 128000
},
{
"id": "claude-haiku-4-5",
"name": "Claude Haiku 4.5",
"input": ["text", "image"],
"contextWindow": 200000,
"maxTokens": 64000
}
]
}
}
}/v1 on an openai-completions provider. The compatible-endpoint example on Pi's model page pairs that value with http://localhost:11434/v1, and before the docs were rewritten on September 22 its OpenRouter, Vercel AI Gateway and llama.cpp examples carried the same version path. No sentence states the rule outright, so the examples are what settles it. A base URL without the suffix shows up as a 404, not an authentication error.cost defaults to all zeros— that default sits in Pi's source (v0.99.2), the docs no longer spell it out — so the provider you just added reports $0 in the footer and in /session until you type the rates in yourself. The two silent defaults below it bite harder: contextWindow falls back to 128000 and maxTokens to 16384, so a model you left blank gets compacted and truncated far short of what it can actually take. The block above sets both from the catalog; do the same for any id you add from the table below.--api-key first, then a stored auth.json credential, an apiKey from models.json, and finally the provider's environment variables”. So a stale key saved by /login wins over the one in your file, which is the usual reason a freshly-edited block still authenticates as something else. The same page adds that custom models “can load from models.json but remain unavailable in /model until Pi can resolve credentials” — a model that will not appear in the picker is a credential fault, not a syntax one.api values — off Pi's source at v0.99.2 the same day. Kunavo has not run Pi against its endpoint — not a session, not a streamed turn, not a tool round-trip, not a check of which model a request ended up on. A published setup page is a configuration reference, not a compatibility test, and nothing here should be read as one. The curl below is the part you can settle in ten seconds; the client's behaviour is between you and Pi.sk-kn-) and add credit from $10 — calls are paid from that balance, and failed calls are not billed. The dashboard then opens on the Pi setup.Step by step
- Create a key at
/app/keysand copy it — it is shown once. - Put it in the environment as
KUNAVO_API_KEY. Pi resolves"$NAME"or"${NAME}"in theapiKeyfield, as well as a literal value or a leading!command. Use the braced form when literal text follows the variable name. - Create or edit
~/.pi/agent/models.jsonand paste the block above. A non-built-in provider needsbaseUrland anapivalue at either provider or model level — Pi's source refuses to load the model without them — and everything else is optional. Opening/modelreloads the file. - Start
pi, run/modeland pick one of the ids you declared. If they are not listed, check the key before the JSON — see the resolution-order note above. - Give it a task that reads and edits a real file. Pi leans on tool calling for nearly everything it does, so a first run that touches the filesystem tells you much more than a greeting does — and it is the run that would surface a streaming or tool-schema mismatch, which is exactly the class of thing Kunavo has not tested for you.
Checked against Pi's Choose a Model documentation on October 1, 2026. Third-party settings move; if a field name here no longer matches what you see, that page is the authority, not this one.
Verify before you debug the client
One request settles whether a failure is the endpoint, the key, or the configuration file. If this returns JSON, the same base URL and key work in Pi.
# Settles whether a failure is the endpoint, the key, or the client.
curl -sS https://api.kunavo.com/v1/models \
-H "Authorization: Bearer sk-kn-..."Which model id to put in the field
Every text model is reachable as a model id — the live list is GET /v1/models, and the catalog with prices is on the models page. Rates are USD per 1M tokens, input / output.
| Model id | Kunavo in / out | Where it fits in Pi |
|---|---|---|
claude-sonnet-5 | $1.40 / $7.00 | the default working model for sessions that edit files |
claude-opus-5 | $3.50 / $17.50 | planning a change that would be expensive to get wrong |
claude-haiku-4-5 | $0.70 / $3.50 | cheap turns — triage, summaries, the loop that runs all day |
gpt-5-6-sol | $2.00 / $12.00 | a second opinion from another family, same key and same baseUrl |
The other route: anthropic-messages
Until its September 22, 2026 docs refresh, Pi documented four values for a custom provider's api — openai-completions, openai-responses, anthropic-messages and google-generative-ai. The refreshed models page lists none: it shows openai-completions in one example and describes the case as “An OpenAI-, Anthropic-, or Google-compatible endpoint”. Pi's source at v0.99.2 types the field as a free string and dispatches it to whichever of ten built-in implementations matches — the four plus openai-codex-responses, azure-openai-responses, google-vertex, mistral-conversations, bedrock-converse-stream and pi-messages. Only the four were ever documented for a custom provider, and none of the other six was tested here; nothing on this page says any of them works against a third-party endpoint.
anthropic-messages is one of the documented four, and Kunavo answers the Anthropic Messages surface as well as the OpenAI-compatible one. So the route is expressible. What this page will not do is write a base URL for it into a block you would paste: Pi's documentation has never settled that field for this api. Until September 22 it showed it two ways — https://proxy.example.com/v1 in one example, a bare https://proxy.example.com in another — and the docs refresh that day removed both rather than choosing. If you take this route, try one, and if the first call comes back 404 rather than 401, that line is the one to change.
Three fields in Pi's compat schema for this api are worth knowing before you get there (source, v0.99.2), and the docs' one rule about all of them is worth quoting first: compatibility settings “should describe verified differences in the endpoint's request or response behavior. Do not enable them based only on an endpoint advertising OpenAI or Anthropic compatibility.”
compat.supportsEagerToolInputStreaming— for a backend that rejects per-tool eager input streaming.compat.supportsStrictTools— whether the endpoint accepts strict JSON-schema tool definitions; a custom model does not inherit what a built-in Anthropic model declares.compat.supportsMidConvoEffort— changing reasoning effort mid-conversation. Whether this endpoint qualifies is a runtime question, and Kunavo has not answered it by running anything.
The openai-completions block at the top of this page avoids all three, which is the honest reason to start there rather than a claim that it performs better.
Frequently asked questions
How do I point the Pi coding agent at a custom API provider?
Add a provider block to ~/.pi/agent/models.json. Pi's model page says to use models.json "when an endpoint speaks an API Pi already supports", and its schema (source, v0.99.2) takes baseUrl, apiKey, api, headers, authHeader, models and modelOverrides at provider level. A non-built-in provider needs baseUrl and an api value at either provider or model level. The schema types api as a free string, not a list: until the September 22, 2026 docs refresh Pi documented four values for a custom provider — openai-completions, openai-responses, anthropic-messages and google-generative-ai — and its source at v0.99.2 dispatches the field to any of ten built-in implementations, the other six never documented for this use and untested here. For an OpenAI-compatible endpoint, openai-completions is the value the refreshed docs still show. Each entry in models needs at least an id, which is passed straight to the endpoint, so the same shape covers a gateway, a local Ollama or vLLM server, and any other compatible host.
Where does the Pi coding agent get its API key from?
From one of four places, and Pi's model page publishes the order: a runtime --api-key first, then a stored auth.json credential, then an apiKey from models.json, and finally the provider's environment variables. A key saved earlier with /login therefore wins over the one you just edited into models.json. The apiKey field itself takes environment interpolation — "$NAME" or "${NAME}" — a literal value, or shell command output with a leading "!", so the secret need not sit in the file. Without usable credentials, the docs say custom models load from models.json but remain unavailable in /model.
Does Pi's baseUrl need /v1 at the end?
For an openai-completions provider, yes. Pi's documentation never states the rule in a sentence, but its compatible-endpoint example uses http://localhost:11434/v1 for Ollama, and before the September 22, 2026 docs refresh its OpenRouter, Vercel AI Gateway and llama.cpp examples carried the same version path. So the value for an OpenAI-compatible endpoint is the /v1 root, for example https://api.kunavo.com/v1. The anthropic-messages case is genuinely unsettled: the old docs showed it both with and without /v1, and the refresh removed both examples without choosing.
Why does my custom Pi provider show $0 in the footer?
Because Pi defaults a custom model's cost object to all zeros (source, v0.99.2), and the footer reports what the catalog says rather than what the endpoint charges. Nothing is free; the number simply has no source until you fill in the per-million input, output, cacheRead and cacheWrite rates, plus any tiers, from your provider's own price list. Two neighbouring defaults deserve the same treatment while you are in the file: contextWindow falls back to 128000 and maxTokens to 16384, so a model with a larger window gets compacted early and its replies cut short unless both are set explicitly.
Has Kunavo tested the Pi coding agent?
No. The configuration on this page was read from Pi's own documentation and, where the September 22 docs refresh dropped a detail, from Pi's released source, on the date shown; but no session, streamed turn, tool round-trip or model-routing check has been run against this endpoint with this client — and the same is true of every client in this section. Treat the setup block as a reference for what Pi's schema accepts, verify the endpoint and key with the curl above, and keep a working route available while you try it.