Docs

Jan Agent

Jan Agent ships no inference engine, so it always calls an endpoint you name. One jan config set line writes Kunavo into ~/.jan/config.toml, and the terminal agent runs on Claude, GPT and Gemini through a single key.

One `jan config set --base-url https://api.kunavo.com/v1` line writes Kunavo into ~/.jan/config.toml, and the Jan Agent preview CLI — which ships no inference engine — runs on that key.

jan config set — writes ~/.jan/config.toml
# Jan Agent is a preview on a nightly channel — check your build first.
jan --version

jan config set \
  --provider kunavo \
  --api-key sk-kn-... \
  --base-url https://api.kunavo.com/v1 \
  --model claude-sonnet-5 \
  --model claude-haiku-4-5 \
  --api-type openai

jan config list   # configured providers as JSON, keys redacted
The base URL keeps its /v1, on both wire types. Jan appends only the route: the contributing-a-provider page says sign-in “validates the key against GET {base_url}/models”, and the providers page says a configured entry is queried for its GET /models the first time you open /model in a session. Every base URL printed in those docs ends in /v1 — including the Anthropic one in the jan cli models list sample. So --api-type anthropic takes https://api.kunavo.com/v1 as well, which is the opposite of Claude Code and the official Anthropic SDKs, where the same suffix produces /v1/v1/messages and a 404. A doubled path in the error is the tell for which convention you are on.
Jan Agent is a preview, and it says so itself. Its quickstart warns that the installer on dev pulls from the agent-nightly channel — “expect nightly-quality builds”. There is no tagged release to cite, so run jan --version and write that string down next to this config: the flags below were read from the documentation on the date at the bottom of this page, and a nightly can rename one. A source build (scripts/install-jan-agent.sh --source) does not self-update, which is one way to hold a version still.
This configuration was read off Jan's own documentation. Kunavo has not run Jan Agent against its endpoint — not a session, not a streamed turn, not a tool round-trip, and the same is true of every client in this family. A published setup page is not a compatibility test. Keep whichever route you have working available while you try this one, and remember that jan config unset --provider kunavo is the whole undo.
Kunavo serves no embedding, text-to-speech or speech-to-text model, so a Kunavo provider entry answers chat and nothing else. Jan Agent asks for nothing more: its memory is plain files under <project>/.jan/agent/memory/ rather than a vector store, so nothing in the agent's own loop needs a second kind of model.

Step by step

  1. Create a key at /app/keys and copy it — it is shown once.
  2. Check what you are configuring: jan --version. Jan Desktop ships a CLI invoked as jan too, with a different command set, so confirm jan config set --help lists --base-url before you type the rest.
  3. Run the jan config set line above. --provider is an id you choose, not a name from a fixed list — the docs' own local-hardware example uses --provider local — and --model is repeatable, replacing any existing list rather than adding to it.
  4. Confirm it landed with jan config list (keys redacted) or jan config path for the file itself, then jan cli models list to see what every provider is offering. A model id typed by hand stays even when the endpoint stops listing it; jan cli models refresh --provider kunavo takes the endpoint's list as the truth instead.
  5. Change into a project and run jan, then pick the model with /model. Prefer jan --plan for the first run: read-only, so a protocol mismatch shows up before anything is written to disk.
  6. Give it a task that edits a file. Jan Agent is an agent, so tool calling and streaming are what a first run should exercise — they are what would fail first against an endpoint that only half fits, and a greeting exercises neither.

Checked against Jan Agent'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 Jan models and API cost guide, which covers Jan Desktop as well.

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 Jan Agent.

# 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 Jan Agent
claude-sonnet-5$2.00 / $10.00the default working model — the id to put first under --model
claude-opus-5$2.00 / $10.00a plan that would be expensive to get wrong; pair it with jan --plan
claude-haiku-4-5$0.40 / $2.00cheap turns: triage, summaries, the loop that runs all day
gpt-5-6-sol$2.00 / $12.00a second opinion from another family, same key and same base URL
gemini-3-1-pro$0.70 / $4.20long-context reading, still on the openai api-type
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.

Two different things are called a Jan API key

They live in the same file and mean opposite things, which is why jan config list can look wrong to someone expecting the other one.

WhatWhere it comes fromWhat it authenticates
jan loginSigning in to Tokamak, the self-hosted backend, from the shell or with /login in the consoleYour own Tokamak deployment. Jan Agent writes the key it receives into ~/.jan/config.toml for you
jan config set --api-keyA credential you already hold for some endpoint — Kunavo's sk-kn- key hereThat endpoint, which is billed per request. This is the row this page is about

Jan Desktop issues neither: it has no account, so there is nothing there to generate. Its local API server takes a key you invent yourself, which is a third meaning again and belongs to a different host.

What Jan Desktop hands down, and where that stops

Jan Agent reads four sources for provider settings, each beating the ones above it, and the second is the one that surprises people.

  1. ~/.jan/config.toml — the base, and the only file jan config set writes.
  2. Jan Desktop's settings.json inherit-only. It adds providers you have not configured in the Agent, never overwrites one you have, and is never written back to.
  3. A [provider] block in a project's agent.toml — an explicit per-project choice, so it beats both. That file usually gets committed, so keep api_key out of it.
  4. --provider / --api-key on the command line, or JAN_API_KEY / <PROVIDER>_API_KEY — the most explicit and the most ephemeral.

Two consequences worth knowing before you debug the wrong thing. jan config list can report no providers while jan cli models list returns plenty — the second includes the inherited Desktop ones, which are not stored in ~/.jan/config.toml. And an inherited provider is never refreshed, because there is no entry here to rewrite: if you want Kunavo's model list kept current, it has to be a jan config set entry of its own, which is what the block above creates.

When two providers offer the same model id

Kunavo serves ids like claude-sonnet-5, and so does a provider entry pointed straight at the vendor. Jan Agent has to pick one, and the documented order is: an exact match in a provider's models list first, then a <provider>/<model> prefix naming a configured provider, and when several offer the same id a credentialed provider wins over a keyless twin. So kunavo/claude-sonnet-5 is how you say which one you meant. The qualifier is for Jan only — it is stripped before the request goes out, because upstreams reject a provider-qualified id.

The same entry, from inside the console

If you would rather not type flags: /settings > providers manages the same ~/.jan/config.toml entries, and a opens an add form whose fields are name, base url, api key and space-separated models. Two things the form does that the flags do not: the base URL must be https:// (or http:// for a localhost endpoint), so a key is never sent over a plaintext remote connection — Kunavo is https://, so this is no obstacle — and on an edit the API key field shows (unchanged) and keeps what is stored unless you type into it. Blanking that field clears the key rather than leaving it alone.

FAQ

How do I point Jan Agent at a custom API endpoint?

With one command: jan config set --provider <id> --api-key <key> --base-url <url> --model <model> --api-type openai. The provider id is one you choose rather than a name from a fixed list, --model is repeatable and replaces any existing list, and --api-type defaults to OpenAI-compatible so it can be left off for an OpenAI-shaped endpoint. The entry is written to ~/.jan/config.toml, which you can also edit through /settings > providers inside the console. Jan's own contributing-a-provider page states that a plain OpenAI-compatible endpoint needs no code at all, only this config surface.

Does the Jan Agent base URL need /v1 on the end?

Yes, and for the Anthropic wire type too. Jan appends only the route: the contributing-a-provider page says sign-in validates the key against GET {base_url}/models, and the providers page says a configured entry is queried for its GET /models when you first open /model in a session. Since the appended path is /models and not /v1/models, the stored base URL has to be the /v1 root already — https://api.kunavo.com/v1 for Kunavo. Every base URL printed in Jan's documentation ends the same way, including the Anthropic entry in its jan cli models list sample. That is the opposite of Claude Code and the official Anthropic SDKs, where adding /v1 yields /v1/v1/messages and a 404.

What is the difference between jan login and jan config set --api-key?

They authenticate different things. jan login signs in to Tokamak, the self-hosted backend, and saves the key it gets back into ~/.jan/config.toml — it is a sign-in to your own deployment. jan config set --api-key stores a credential you already hold for some endpoint, which is what you use for a third-party provider such as Kunavo. Jan Desktop itself issues neither, because it has no account to issue one from; the key its local API server asks for is a string you invent, which is a third meaning of the phrase again.

Why does jan config list show nothing when jan cli models list shows models?

Because the two commands read different sets. jan config list reports only what is stored in ~/.jan/config.toml, while jan cli models list also includes providers inherited from Jan Desktop, which are not stored there. Jan's documentation calls this out directly. The practical consequence is that an inherited provider is never refreshed — there is no entry to rewrite — so if you want a provider's model list kept current, add it with jan config set first.

Can Jan Agent run Claude models without an Anthropic account?

Yes. Jan Agent ships no inference engine, so a model always runs at whatever endpoint you configure, and --api-type names the wire protocol rather than the vendor. A Claude id is resolved at the base URL you set, which means the credentials you hold are that endpoint's. Kunavo serves Claude, GPT and Gemini ids over its OpenAI-compatible surface on one key. This configuration was published from Jan's own documentation rather than from a test run against the client, and Jan Agent is itself a preview on a nightly channel, so treat the flags as true of the build you check with jan --version.

Jan Agent returns 404 on every request. What is wrong?

Almost always the base URL. A missing /v1 makes Jan request /models and /chat/completions at the origin, which 404s rather than failing authentication; a doubled /v1/v1 in the error means the suffix was added to a base that already had it. Settle it outside the client first by calling GET /v1/models on the endpoint with a plain curl and the same key: JSON back means the endpoint and key are fine and the problem is the configured entry, a 401 means the key, a 404 means the URL. Then check jan config path and read the stored base_url directly.