Docs

Oh My Pi

Oh My Pi keeps its providers in one YAML file. Three lines under a name you choose — baseUrl, api, apiKey — and omp is routing to Claude, GPT and Gemini through a single key, with the model list fetched rather than typed.

A provider block in ~/.omp/agent/models.yml — baseUrl, api, apiKey — points Oh My Pi at Kunavo, and discovery fills the model list from GET /v1/models.

~/.omp/agent/models.yml
providers:
  kunavo:
    baseUrl: https://api.kunavo.com/v1
    api: openai-completions
    apiKey: KUNAVO_API_KEY       # an env-var name; literal text also works
    discovery:
      type: openai-models-list   # reads GET /v1/models

# Prefer a fixed list to a discovered one? Drop the discovery block and
# declare ids instead. Omitted metadata defaults to a 128,000-token context
# window and a 16,384-token output limit, so set the real numbers from
# /models when they differ.
#
#     models:
#       - id: claude-sonnet-5
#         name: Claude Sonnet 5
#         contextWindow: ...
#         maxTokens: ...
The base URL keeps its /v1. omp documents baseUrl as the “Endpoint root” and openai-completions as “OpenAI-compatible Chat Completions”, and its own 404 troubleshooting entry reads “Generic OpenAI-compatible base URLs commonly end in /v1”. Every custom-provider example on both pages ends the same way. “Commonly” is their hedge, not a guarantee — but Kunavo serves /v1/chat/completions, so https://api.kunavo.com/v1 is the root that produces it. This is the opposite of the Anthropic-style clients, which want the bare origin.
Leave authHeader out on this route. omp documents it as being for a gateway that “specifically needs Authorization: Bearer injected as an ordinary header”, and notes that “standard provider clients already apply their normal authentication scheme” — which the OpenAI-compatible client does, and Kunavo accepts. Add it only if you see a 401 that the curl below does not reproduce.
This configuration was read off omp's own documentation on the date below. Kunavo has not run omp against its endpoint — not a session, not a streamed turn, not a tool round-trip, not a routing check. A published setup page is not a test, and nothing here should be read as one. The curl below is the part you can settle in ten seconds; the rest is between you and omp.
Kunavo serves no embedding, text-to-speech or speech-to-text model, so this provider answers chat and nothing else. Anything in your setup that transcribes audio or builds a vector index keeps the provider key it already has.

Step by step

  1. Create a key at /app/keys and copy it — it is shown once.
  2. Export it as KUNAVO_API_KEY in the shell that starts omp. omp resolves apiKey as an environment-variable name first and falls back to treating the text as the literal key, so a typo in the variable name loads quietly and fails at the first request. A value beginning with ! is run as a shell command instead — that is the 1Password-style route.
  3. Put the block above in ~/.omp/agent/models.yml. The provider id — kunavo here — is yours to choose and becomes the first half of every selector.
  4. Run omp models kunavo to load the file and list just this provider. A YAML or schema problem prints models.yml validation failed and the field that failed; omp models refresh kunavo forces a fresh discovery call instead of using the cached catalog.
  5. Exercise it with an exact selector: omp -p --model kunavo/claude-sonnet-5 "Reply with only OK". Then start omp, type /model, and assign the id you want to Default — the model hub reloads models.yml when it opens. /switch changes only the current session.

Checked against omp's Providers page on September 21, 2026. Third-party settings move; if a field name here no longer matches what you see, that page is the authority, not this one.

This is the short version. The full walkthrough — model choice, what a real session costs, and the failure modes — is in the Oh My Pi vs OpenCode comparison.

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 Oh My 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 idKunavo in / outWhere it fits in Oh My Pi
claude-sonnet-5$2.00 / $10.00the default role — the model most sessions actually run on
claude-opus-5$2.00 / $10.00the plan role, where a wrong plan costs more than the tokens
claude-haiku-4-5$0.40 / $2.00the smol role: triage, summaries, the calls that never stop
gpt-5-6-sol$2.00 / $12.00a second opinion from another family, same provider block
Billing is per token from a prepaid balance with no monthly fee — see billing. On repeated context — which is most of what an editor or a chat client sends — prompt caching moves the bill more than the model choice does.

Discovery: which type, and what lands in the picker

omp offers six discovery.type values and two of them look right for a gateway. Only one is. proxy is documented for “a mixed OpenAI/Anthropic proxy whose model rows advertise supported_endpoint_types”, and it derives each model's wire from that field. Kunavo's GET /v1/models does not publish it, so under proxy every row would fall back to the provider-level api — or, with none set, be dropped. openai-models-list, documented as “a generic OpenAI-compatible GET /v1/models endpoint”, is the one to use, and it is why the block above keeps api: openai-completions: omp's rule is that “except for proxy, discovery requires provider-level api”.

One consequence worth knowing before you open the picker. Kunavo's model list is the whole enabled catalog, so a discovered provider surfaces image, video and music ids alongside the chat ones, and the chat transport cannot call those. Kunavo publishes context_length on chat rows — which is the field omp's generic discovery reads after max_model_len, per its models documentation — but omits it on the media rows, so those arrive with omp's 128,000-token default rather than a real number. If you want a short, correct picker, drop the discovery block and declare the three or four ids you actually use.

omp can also speak anthropic-messages, and Kunavo answers /v1/messages. This page does not print a config block for that pairing: the two pages cited here settle the base-URL form for the OpenAI-compatible route and say nothing about how a trailing /v1 is treated on the Anthropic one, and the whole point of a config block is that a reader can paste it. If you go that way,disableStrictTools: true is the documented answer to tool calls failing with a 400 on an Anthropic-compatible endpoint.

FAQ

How do I add a custom API provider to Oh My Pi?

Everything happens in ~/.omp/agent/models.yml. Add a key under `providers:` — the name is yours and becomes the provider half of the selector — and give it baseUrl, api and apiKey, which is the order omp's own "Add a custom provider" example uses. Either list the models by hand under `models:` or add a `discovery:` block and let omp fetch them. Then run `omp models <your-provider-id>` to confirm the file loaded, and select a model with `omp --model <provider>/<model-id>` or the /model hub inside a session.

Does the Oh My Pi baseUrl need /v1 at the end?

For an OpenAI-compatible endpoint, yes. omp calls baseUrl the endpoint root and appends the route for the api family you declare, so `api: openai-completions` means it asks for chat completions under whatever root you gave it. Its own troubleshooting note for a 404 says generic OpenAI-compatible base URLs commonly end in /v1, and every custom-provider example in its documentation does. Kunavo serves /v1/chat/completions, so the root to write is https://api.kunavo.com/v1 — a missing /v1 shows up as a 404 or an "unsupported endpoint", not as an authentication error.

Where does Oh My Pi look for the API key, and what wins?

An apiKey in models.yml is resolved in three steps: a value starting with ! is run as a shell command and its trimmed stdout is used, otherwise omp looks for an environment variable with exactly that name, and if there is none it treats the text itself as the key. That last fallback is the trap — a misspelled variable name loads without complaint and fails at the first request. In the wider precedence order a models.yml key beats stored OAuth, which omp documents as deliberate, so a key supplied for a gateway is not overwritten by an upstream login.

Which discovery type should a gateway use in omp?

openai-models-list, not proxy, unless the gateway advertises supported_endpoint_types on each model row — that field is what proxy reads to decide whether a model goes to /v1/messages or /v1/chat/completions, and without it models fall back to the provider-level api or are dropped. Kunavo's /v1/models does not publish that field, so the generic OpenAI list is the right type, and omp requires a provider-level api for every discovery type except proxy. Run `omp models refresh <provider>` to force a fresh fetch rather than using the cached catalog.

Has Kunavo tested Oh My Pi against its endpoint?

No. What was checked, on September 21 2026, is omp's own documentation — the field names, their order, the key-resolution rule and the discovery types are quoted from it, and two details were checked against Kunavo's own /v1/models route rather than assumed. No omp session has been run against api.kunavo.com here, and no claim is made about streaming, tool round-trips or model routing inside the client. The one thing that can be settled in isolation is whether the endpoint and key work at all, which the curl on this page does.