# Model Endpoints

Endpoints for registered models, routing, tiers, quota and audio models.

| Method | Path | Description |
|---|---|---|
| `GET` | `/v1/models` | List registered models (OpenAI `{data:[...]}` shape, `auto` included) |
| `GET` | `/v1/models/*id` | Read one registered model |
| `POST` `DELETE` | `/v1/models` · `/v1/models/*name` | **local** — add / remove a model |
| `GET` `POST` | `/v1/model` | **local** — model routing in one object: `dispatcher`, `dispatcher_beta`, `summary`, `image`, `stt`, `tts`, plus `image_options`, `image_providers` and `audio_providers` on read. The fields hold two different kinds of value. `dispatcher`, `summary`, `stt` and `tts` name a registered model (`prefix@model`) — `stt` and `tts` are drawn from `GET /v1/model/audio`, not from the session model registry. `image` names an image model as `provider@model` since v1.0.13 — `image_options` is now probed from each credentialed provider (cached for 15 minutes since v1.0.18) (`openai`, `grok`, `grok-oauth`, `gemini`, and `openrouter` since v1.0.17) with an image-only model filter, so the choice is a model rather than an endpoint. `codex` is the exception and still appears as the bare provider name, and a bare provider name stored from an earlier version is still accepted. `image_providers` and `audio_providers` remain the full provider catalogs (`image_providers` is `openai`, `codex`, `grok`, `grok-oauth`, `gemini`, `openrouter`; `audio_providers` is `openai`, `gemini`, `openrouter`). `dispatcher_beta` is a boolean added in v1.0.18 that routes through the TypeSafe dispatcher instead of `dispatcher`; setting it to `true` without a `TYPESAFE_API_KEY` in the keychain returns 400 with `missing_key`. The global `auto_reasoning` toggle was removed in v1.1.0 — set a session's `reasoning` to `auto` instead, and the model selector picks the level per request from the kind of work. `POST` is a partial update — a field left out (or `null`) is untouched, `""` clears it, and `off` is an alias of `""` for `image`. An unregistered model, an unknown provider, or a provider with no credentials is rejected and nothing is written |
| `GET` | `/v1/model/quota` | **local** — added in v1.1.0. `?model=<prefix>@<model>` returns `{quota}`: that model's provider quota as a display string (`"42%"` for `codex`, `grok-oauth`, `copilot`, `ollama-cloud`, `claude-code`; `"$12.34"` for `openrouter`, `deepseek`), or `""` when the provider reports no quota, has no credential, or the read fails. Read live (15 s ceiling, no cache). The dashboard fetches it after a completion instead, since quota was removed from completion events and channel footers in v1.1.0 |
| `GET` | `/v1/model/audio` | **local** — `stt_options` and `tts_options`: the speech-to-text and text-to-speech models reachable with the credentials stored right now, fetched from OpenAI, Gemini and OpenRouter (added in v1.0.17) and returned as `provider@model`; since v1.0.18 each provider's answer is cached for 15 minutes, with the same invalidation as `/v1/provider/:provider/models`. These are the only values `POST /v1/model` accepts for `stt` / `tts` |
| `GET` `POST` | `/v1/model/priority` | **local** — fallback order of the registered models: after the selected model fails, the others are tried top to bottom. Since v1.1.0 `pass`-tier models are skipped (v1.0.20 through v1.0.27 still tried them at their place in the order). `GET` returns `models` (names in order), `tiers` (the `model_tag` map, model → tier) and `tier_options` (`{tier, detail}` rows, `""` first for no tier). `POST` `{models}` moves the listed names to the front in that order and keeps the rest after them; an unknown name returns 400 |
| `POST` | `/v1/model/tier` | **local** — `{model, tier}`. Sets one registered model's tier: `S`, `A`, `B`, `C`, or `pass` (since v1.1.0 never picked by auto routing or subagents, even when the request names it, and skipped by fallback; it still runs when set as a session's own model); `""` clears it. An unregistered model or unknown tier returns 400 |
