Merged via /merge-batch (2026-10-17, round 4). Validated as submitted on a combined board of 33 PRs over the current release/v3.8.52 tip: typecheck:core, open-sse and API-route typechecks, eslint on changed files, file-size, complexity, cognitive-complexity, dead-code, boundary 774/774, changelog-integrity, env-doc-sync, docs-sync, agent-skills-sync (Generated 0), provider-consistency and i18n new-key coverage all green; focused tests 401/406 node:test + 11/11 vitest (the only reds were the #16988 fixture, fixed separately). Remaining CI reds are the inherited base-red #15306. Thanks @PrinceThummar011 for the contribution!
146 KiB
| title | version | lastUpdated |
|---|---|---|
| API Reference | 3.8.52 | 2026-10-05 |
API Reference
🌐 Languages: 🇺🇸 English | 🇪🇹 አማርኛ | 🇸🇦 العربية | 🇦🇿 Azərbaycan dili | 🇧🇬 Български | 🇧🇩 বাংলা | 🇧🇦 Bosanski | 🇨🇿 Čeština | 🇩🇰 Dansk | 🇩🇪 Deutsch | 🇬🇷 Ελληνικά | 🇪🇸 Español | 🇪🇪 Eesti | 🇮🇷 فارسی | 🇫🇮 Suomi | 🇫🇷 Français | 🇮🇪 Gaeilge | 🇮🇳 ગુજરાતી | 🇳🇬 Hausa | 🇮🇱 עברית | 🇮🇳 हिन्दी | 🇭🇷 Hrvatski | 🇭🇺 Magyar | 🇦🇲 Հայերեն | 🇮🇩 Bahasa Indonesia | 🇳🇬 Igbo | 🇮🇹 Italiano | 🇯🇵 日本語 | 🇬🇪 ქართული | 🇰🇭 ខ្មែរ | 🇮🇳 ಕನ್ನಡ | 🇰🇷 한국어 | 🇱🇹 Lietuvių | 🇱🇻 Latviešu | 🇮🇳 മലയാളം | 🇮🇳 मराठी | 🇲🇾 Bahasa Melayu | 🇲🇹 Malti | 🇲🇲 မြန်မာ | 🇳🇵 नेपाली | 🇳🇱 Nederlands | 🇳🇴 Norsk | 🇮🇳 ଓଡ଼ିଆ | 🇮🇳 ਪੰਜਾਬੀ | 🇵🇭 Filipino | 🇵🇱 Polski | 🇵🇹 Português (Portugal) | 🇧🇷 Português (Brasil) | 🇷🇴 Română | 🇷🇺 Русский | 🇱🇰 සිංහල | 🇸🇰 Slovenčina | 🇸🇮 Slovenščina | 🇷🇸 Српски | 🇸🇪 Svenska | 🇰🇪 Kiswahili | 🇮🇳 தமிழ் | 🇮🇳 తెలుగు | 🇹🇭 ไทย | 🇹🇷 Türkçe | 🇺🇦 Українська | 🇵🇰 اردو | 🇺🇿 Oʻzbekcha | 🇻🇳 Tiếng Việt | 🇳🇬 Yorùbá | 🇨🇳 中文 (简体) | 🇹🇼 中文 (繁體)
Core reference for the OmniRoute API. It covers the public /v1 surface and the most-used management endpoints; the machine-readable docs/openapi.yaml and the route tree under src/app/api/ are the exhaustive sources.
For the focused OpenAI-compatible protocol and provider capability matrix, see
OPENAI_COMPATIBILITY.md.
Table of Contents
- Chat Completions
- Exclusive Managed Session Leases
- Embeddings
- Image Generation
- Document OCR
- List Models
- Provider Plugin Manifest
- Compatibility Endpoints
- Files API
- Batches API
- Search API
- WebSocket Streaming
- Quotas & Issues Reporting
- Semantic Cache
- Dashboard & Management
- Combo Management
- Webhooks
- Registered Keys (Auto-Management)
- Agents Protocol
- Management Proxies
- Resilience (extended)
- Skills
- Memory
- MCP Server
- A2A Server
- Cloud, Evals & Assess
- Request Processing
- Authentication
Chat Completions
POST /v1/chat/completions
Authorization: Bearer your-api-key
Content-Type: application/json
{
"model": "cc/claude-opus-4-6",
"messages": [
{"role": "user", "content": "Write a function to..."}
],
"stream": true
}
Custom Headers
| Header | Direction | Description |
|---|---|---|
X-OmniRoute-No-Cache |
Request | Set to true to bypass cache |
x-omniroute-no-memory |
Request | Set to true to skip memory + skills injection for this request (mirrors no-cache; avoids the per-call token/cost overhead) |
X-OmniRoute-Progress |
Request | Set to true for progress events |
X-Session-Id |
Request | Sticky session key for external session affinity |
x_session_id |
Request | Underscore variant also accepted (direct HTTP) |
X-OmniRoute-Session-Id |
Request | Caller-supplied session/conversation tag (also feeds memory). When present, persisted verbatim to call_logs.session_tag for per-session cost attribution (#8249) — never synthesized when absent |
Idempotency-Key |
Request | Dedup key (5s window) |
X-Request-Id |
Request | Alternative dedup key |
x-omniroute-connection |
Request | Pin a single-model request to one provider connection (account) by its id — see Account pinning per request |
X-OmniRoute-Cache |
Response | HIT or MISS (non-streaming) |
X-OmniRoute-Idempotent |
Response | true if deduplicated |
X-OmniRoute-Progress |
Response | enabled if progress tracking on |
X-OmniRoute-Session-Id |
Response | Effective session ID used by OmniRoute |
X-OmniRoute-Request-Id |
Response | Request correlation id (when known) |
X-OmniRoute-Version |
Response | OmniRoute build version (always present) |
X-OmniRoute-Cost-Saved |
Response | USD the cache avoided on a HIT (cache hits only) |
X-OmniRoute-Decision |
Response | Routing trace: strategy=<name>; provider=<alias>; latency_ms=<n> (<name> is the combo strategy, or single for a non-combo request) — always present on completion responses |
Nginx note: if you rely on underscore headers (for example
x_session_id), enableunderscores_in_headers on;.
Account pinning per request
When a provider has several connections (accounts or keys), OmniRoute normally picks one
through its account-selection strategy. A request can instead pin one specific connection
with the x-omniroute-connection header:
curl http://localhost:20128/v1/chat/completions \
-H "Authorization: Bearer <your OmniRoute API key>" \
-H "Content-Type: application/json" \
-H "x-omniroute-connection: <connection id>" \
-d '{"model": "codex/gpt-5.5", "messages": [{"role": "user", "content": "hi"}]}'
- Where to find the id. Open the provider page in the dashboard
(Dashboard → Providers → ) and click the
xxxxxxxx…id chip on a connection row — it copies the full connection id. - Which requests honor it. Single-model requests (
provider/model) on/v1/chat/completions,/v1/messagesand/v1/responses. Combos ignore the header: pin an account per combo step through the step's own connection instead. - The pin is strict, not a preference. The pinned connection is used exclusively. A failure inside the request records the account's cooldown but does not rotate to a sibling account of the same provider.
- Cooldown and quota release the pin. While the pinned connection is cooling down, quota-exhausted or blocked by a quota policy, later requests fall back to the provider's other connections, exactly like unpinned traffic.
- Unknown or disabled ids fail. An id that is not in the provider's active pool (deleted, deactivated, or belonging to another provider) leaves no eligible account, so the request fails instead of silently routing elsewhere.
Claude Code sends custom headers through ANTHROPIC_CUSTOM_HEADERS. Put it in the env block
of ~/.claude/settings.json, or in a project-level .claude/settings.json to pin a different
account per project:
{
"env": {
"ANTHROPIC_BASE_URL": "http://<your OmniRoute>:20128",
"ANTHROPIC_AUTH_TOKEN": "<your OmniRoute API key>",
"ANTHROPIC_CUSTOM_HEADERS": "x-omniroute-connection: <connection id>",
},
}
Source: src/sse/handlers/chat.ts (header read), src/sse/services/auth.ts and
src/sse/services/sessionAffinityPin.ts (pin release and the missing-from-pool check).
Cost telemetry headers: non-streaming success responses also carry the
X-OmniRoute-*cost-telemetry set —X-OmniRoute-Response-Cost(USD, fixed 10 decimals;0.0000000000for free/unpriced),X-OmniRoute-Tokens-In/X-OmniRoute-Tokens-Out,X-OmniRoute-Model,X-OmniRoute-Provider,X-OmniRoute-Latency-Ms,X-OmniRoute-Cache-Hit, andX-OmniRoute-Fallback-Attempts(only when > 0), plusX-OmniRoute-Request-IdandX-OmniRoute-Version. These are emitted by chat completions,/v1/responses,/v1/messages, and the media endpoints —/v1/embeddings,/v1/images/generations,/v1/audio/speech,/v1/audio/transcriptions,/v1/rerank,/v1/videos/generations,/v1/music/generations, and/v1/moderations(always cost0). Media cost is computed per modality (per-image, per-second, per-character, per search-unit) when pricing is available, otherwise0(fail-open).
Cache-hit cost semantics: on a semantic-cache HIT (
X-OmniRoute-Cache-Hit: true) no upstream call is made, soX-OmniRoute-Response-Costis0.0000000000(the incremental cost of serving the hit). The original/would-have-been cost is reported separately inX-OmniRoute-Cost-Saved. Billing consumers should sumX-OmniRoute-Response-Cost(hits cost nothing); cache analytics can aggregateX-OmniRoute-Cost-Saved.
Exclusive Managed Session Leases
Exclusive managed session leasing is an opt-in, client-neutral routing contract: one active owner holds one eligible OmniRoute connection. It does not lease a model, require OAuth, identify a particular client, or require a particular provider.
The authenticating API key must have scope lease:exclusive and an explicit non-empty
allowedConnections list. The database mutation boundary enforces both fields together on key
creation and partial updates.
POST /api/v1/session-leases
Authorization: Bearer <managed-api-key>
Content-Type: application/json
X-OmniRoute-Lease-Owner: vlo_<43-base64url-characters>
{"action":"acquire","model":"glm/glm-4.6"}
Successful acquire, renew, and release responses expose timestamps, state, and the exact positive
generation, but never the selected connection or credentials. Renew and release supply the
generation in the JSON body:
{ "action": "renew", "generation": 1 }
{ "action": "release", "generation": 1, "reason": "OWNER_EXIT" }
An active lease owner can explicitly request privacy-safe display metadata for its current binding:
{ "action": "status", "generation": 1 }
{
"state": "ACTIVE",
"generation": 1,
"acquiredAt": "2026-08-28T12:00:00.000Z",
"renewedAt": "2026-08-28T12:00:30.000Z",
"expiresAt": "2026-08-28T12:02:30.000Z",
"connection": {
"displayName": "Primary Codex",
"provider": "codex"
}
}
This opt-in status action is fenced by the opaque owner, authenticated managed API key, and exact
active generation in one database transaction. displayName is only the trimmed configured
connection name; it is null when no safe configured name exists. OmniRoute never substitutes an
email or generated account identity. The provider value is a non-sensitive display label and never
a generated compatible-provider identifier. Credentials, tokens, cookies, raw connection or API
key ids, owner hashes, fencing secrets, and internal routing data are excluded.
Wrong-key, wrong-owner, stale-generation, missing, expired, released, and invalidated lookups all
return the same 409 LEASE_FENCE_STALE error without connection metadata. A client that received the capacity-wait response has no active binding to inspect. When routing transitions an active lease,
the same generation remains valid and status atomically returns the new binding, never the old one.
Existing clients remain unchanged because acquire, renew, release, and waiting responses retain
their previous shapes.
This server contract does not change stock OpenAI Codex /status. Stock Codex currently reports its
model provider and built-in authentication/account state but does not render arbitrary custom
provider account metadata; a later client integration must call this action and decide how to
display connection.displayName.
Every managed inference request then supplies both control headers:
X-OmniRoute-Lease-Owner: vlo_<43-base64url-characters>
X-OmniRoute-Lease-Generation: 1
The exact owner, generation, active connection, and authenticated API key are fenced immediately before each supported upstream attempt. Replaying owner and generation with another key fails even when that key permits the same connection. Raw owners are not persisted, logged, retained in the request snapshot, or forwarded upstream.
Temporary contention returns HTTP 429 with Retry-After and:
{
"state": "WAITING_FOR_CAPACITY",
"error": { "type": "lease_error", "code": "LEASE_CAPACITY_UNAVAILABLE" },
"reason": "NO_FREE_ELIGIBLE_CONNECTION",
"retryAfter": 30
}
This response only means that the ordinary eligible set was non-empty and every free candidate was held by a foreign active lease. Unsupported models/providers, policy mismatch, cooldown, quota, health, and other ordinary eligibility failures retain their existing OmniRoute responses.
x-omniroute-compression
Per-request override of the compression plan. Highest precedence — beats the routing-combo override, the active profile, auto-trigger, and the panel Default. Values:
| Value | Effect |
|---|---|
off |
No compression for this request. |
default |
The panel-derived Default profile (ignores the active profile). Lossy engines are left off. |
safe |
Dedup and whitespace folding only. |
allow-lossy |
Keep the operator plan for this request, including summaries and style rewrites. |
engine:<id> |
A single engine when enabled, e.g. engine:rtk. Per-request opt-in for that engine. |
<combo> |
A named combo, matched by name (case-insensitive) first, then by id. |
Notes:
- Unknown values are ignored (the request is never rejected); resolution falls through to the normal operator precedence.
- If multiple combos share a name, pass the combo id for a deterministic match.
- A combo whose name is
offordefaultcannot be selected by name (those keywords are interpreted first); reference such a combo by its id. - The master compression switch is a hard gate: when compression is disabled globally, this header cannot enable it.
The applied plan is echoed back in the response header:
X-OmniRoute-Compression: <mode>; source=<source>
where <source> is one of request-header, routing-override, active-profile, auto-trigger, default, or off.
Embeddings
POST /v1/embeddings
Authorization: Bearer your-api-key
Content-Type: application/json
{
"model": "nebius/Qwen/Qwen3-Embedding-8B",
"input": "The food was delicious"
}
Available providers: Nebius, OpenAI, Mistral, Together AI, Fireworks, NVIDIA, OpenRouter, Jina AI.
Catalog ids are provider/model (example: jina-ai/jina-embeddings-v5-omni-small). Bare Jina model ids that appear in the registry (for example jina-embeddings-v5-text-small, jina-reranker-v3.5) also resolve. Jina embed/rerank/classify/segment use dashboard jina-ai credentials first; JINA_AI_API_KEY is a fallback only when no dashboard key exists. The jina-reader card is Reader / r.jina.ai only (POST /v1/web/fetch) and never serves embeddings or rerank.
Registry models that advertise multimodal support also accept up to 32 provider-neutral structured
items. Media item types are text, image, audio, video, and document. Their media source
is either {"type":"url","url":"https://..."} or
{"type":"base64","data":"...","media_type":"..."}.
Jina v5 Omni (jina-ai/jina-embeddings-v5-omni-small, jina-ai/jina-embeddings-v5-omni-nano,
and the family alias jina-ai/jina-embeddings-v5-omni → omni-small) also accepts Jina's native
EmbeddingsV5Request docs and forwards them intact to https://api.jina.ai/v1/embeddings:
{
"model": "jina-ai/jina-embeddings-v5-omni-small",
"task": "retrieval.query",
"normalized": true,
"input": [
{ "text": "a red bicycle" },
{ "image": "https://example.com/bike.png" },
{ "content": [{ "text": "caption" }, { "image": "data:image/png;base64,..." }] }
]
}
Native { image | audio | video | pdf } values may be a public HTTPS URL, a data: URI, or raw
base64. OmniRoute does not stringify those objects or fetch native image URLs — Jina retrieves
public media itself. Extra Jina fields (task, normalized, truncate, embedding_type) are
forwarded. Text-only Jina SKUs still reject non-text docs.
Security and transport bounds:
- Remote media URLs must be public HTTPS. Canonical
{type,source:url}items are fetched server-side (redirect revalidation, timeout, size limits, public DNS, connection pinning) and inlined before the provider call. Jina-native{image:"https://..."}items are forwarded as-is after the same public-HTTPS check; Jina fetches the URL. - Inline base64 media is limited to 8 MiB decoded per item and 16 MiB decoded across the request.
Provider translation (canonical items are never forwarded unchanged):
- Jina multimodal models: each top-level item becomes one modality-keyed object
(
text/image/audio/video/pdf) using data URIs for inline media; one vector per top-level item. - Gemini Embedding 2 family: one top-level array becomes a single native
models/{model}:embedContentrequest withcontent.parts(textorinline_data). - llama.cpp (
llama-cpp/<model>, any model the local server loaded): canonicaltextitems become plain strings, andimage/audio/videobecome one{"content": [part]}object each, using llama-server's chat content parts (image_url,input_audiowith formatwav/mp3/flac,input_video) with inline data; one vector per top-level item. The server must run with--embedding --mmproj …; without a projector it rejects media itself.documentis not supported. - Unknown/dynamic models without explicit modality metadata reject structured input with HTTP 400.
{
"model": "jina-ai/jina-embeddings-v5-omni-small",
"input": [
{ "type": "text", "text": "A red bicycle" },
{
"type": "image",
"source": { "type": "url", "url": "https://example.com/bicycle.png" }
}
],
"dimensions": 512,
"encoding_format": "float"
}
Unsupported model/modality combinations return HTTP 400 rather than coercing the item. Non-input extension fields on legacy string/token requests continue to pass through unchanged.
# List all embedding models
GET /v1/embeddings
Image Generation
POST /v1/images/generations
Authorization: Bearer your-api-key
Content-Type: application/json
{
"model": "openai/gpt-image-2",
"prompt": "A beautiful sunset over mountains",
"size": "1024x1024"
}
Available providers include OpenAI (GPT Image 2), xAI (Grok Image), Together AI (FLUX), Fireworks AI, Nebius (FLUX), Hyperbolic, NanoBanana, OpenRouter, ZenMux, SD WebUI (local), ComfyUI (local).
n returns n images in data[]. Providers whose upstream API returns one image per call
(Black Forest Labs, Stability AI, Leonardo, Ideogram, Haiper, Hyperbolic, Magnific/Freepik,
NVIDIA NIM, Cloudflare Workers AI) get n separate upstream calls, at most 4 in flight. A
fixed seed is offset by call index (seed, seed + 1, …), so the images differ but stay
reproducible. n is capped at 10. The request fails as a whole if any call fails. The list
of fan-out formats is SINGLE_IMAGE_FANOUT_FORMATS in
open-sse/handlers/imageGeneration/fanOut.ts.
ZenMux reuses the existing API-key connection and accepts zenmux/ or zm/ prefixes:
zenmux/openai/gpt-image-2uses ZenMux's OpenAI Images API. Options includesize,quality,n,output_format,output_compression,background, andresponse_format.- Other publishers, such as
zm/meta/muse-image-1.0, use ZenMux's Vertex AI:predictendpoint.nmaps tosampleCount,aspect_ratiotoaspectRatio, andimage_size(1K,2K,4K) tosampleImageSize. A pixelsizesupplies only an aspect ratio, not guaranteed pixel dimensions. Supported ratios, resolutions, and counts vary by model. zm/inclusionai/ming-image-0.1-designchooses its own dimensions. Omitsize,aspect_ratio, andimage_size; explicit values return HTTP 400. PNG, JPEG, and WebP can be requested withoutput_format.
This integration supports text-to-image generation, not reference-image editing. Vertex
output is normalized to data[].b64_json; response_format: "url" returns an upstream
HTTPS URL or a base64 data URL when only image bytes are available. Empty/filtered outputs
return an error rather than an empty success. Model access depends on the ZenMux account.
See ZenMux's Vertex API and
OpenAI Images API.
# List all image models
GET /v1/images/generations
Document OCR
POST /v1/ocr
Authorization: Bearer your-api-key
Content-Type: application/json
{
"model": "mistral/mistral-ocr-latest",
"document": {
"type": "document_url",
"document_url": "https://example.com/invoice.pdf"
}
}
model selects the OCR provider via a provider/model prefix; a bare model id (e.g.
mistral-ocr-latest) resolves to its registered provider, and an omitted model defaults to
Mistral (mistral-ocr-latest). Registered providers (open-sse/config/ocrRegistry.ts):
| Provider id | Model id | model value |
Notes |
|---|---|---|---|
mistral |
mistral-ocr-latest |
mistral/mistral-ocr-latest (or bare mistral-ocr-latest) |
Synchronous — the response is returned directly from the single upstream call. |
azure-document-intelligence |
prebuilt-read |
azure-document-intelligence/prebuilt-read |
Asynchronous upstream (analyze + poll) — see below. |
vertex-deepseek-ocr |
deepseek-ocr-maas |
vertex-deepseek-ocr/deepseek-ocr-maas |
Synchronous, via Vertex AI's openapi/chat/completions partner endpoint — see below for auth/URL. |
All three providers respond in the same Mistral-shaped body:
{
"pages": [{ "index": 0, "markdown": "# Extracted text..." }],
"model": "mistral-ocr-latest",
"usage_info": { "pages_processed": 1 }
}
Azure Document Intelligence poll flow
Azure Document Intelligence's analyze API is asynchronous: the initial request returns an
Operation-Location header instead of a body, and the result must be polled for. The handler
(open-sse/handlers/ocr.ts) polls that URL every second for up to 30 attempts, fails fast (does
not keep polling) on a non-ok poll response or a "failed" status, and returns 504 if the
operation is still running after the attempt budget is exhausted. The final Azure response is
normalized into the same pages/markdown shape used by Mistral before being returned to the
caller, so client code does not need to special-case the provider.
Vertex AI DeepSeek OCR auth and endpoint resolution
vertex-deepseek-ocr reuses the same Vertex AI authentication OmniRoute already supports for
chat/image traffic (open-sse/executors/vertex.ts): the connection's API key is either a
Service Account JSON credential (exchanged for a short-lived OAuth access token via the JWT-bearer
flow) or an already-minted OAuth access token used as-is. The upstream endpoint URL is Vertex's
generic openapi/chat/completions partner endpoint, built from the connection's project and
region — an explicit providerSpecificData.project/providerSpecificData.region always wins;
otherwise the project is derived from the Service Account JSON's project_id and the region
defaults to us-central1. Both resolutions happen in open-sse/handlers/ocr.ts
(resolveVertexOcrAccessToken, resolveVertexOcrBaseUrl), consumed by
src/app/api/v1/ocr/route.ts before dispatching to handleOcr.
List Models
GET /v1/models
Authorization: Bearer your-api-key
→ Returns all chat, embedding, and image models + combos in OpenAI format
Model id prefixes (?prefix=)
Most models are advertised under a provider prefix. Which prefix you get is controlled by
the MODELS_CATALOG_PREFIX_MODE feature flag, and can be overridden per request with a
query parameter — useful for a client that wants a clean list without changing the server-wide
setting for everyone else:
GET /v1/models?prefix=alias # one id per model — the short alias prefix
GET /v1/models?prefix=dual # both forms (server default)
GET /v1/models?prefix=canonical # only the full provider-id prefix
| Mode | Emits | Notes |
|---|---|---|
dual |
cc/claude-sonnet-4-6 and claude/claude-sonnet-4-6 |
Default. Both ids route to the same model; kept so client configs that hardcoded either form keep working. Roughly doubles the catalog. |
alias |
cc/claude-sonnet-4-6 |
One entry per model. Providers without a distinct alias still emit their entry, so nothing is lost. |
canonical |
claude/claude-sonnet-4-6 |
One entry per model under the full provider-id prefix. Providers without a distinct alias (e.g. antigravity/…, agy/…) emit their single id here too, so nothing is lost. |
A dual-mode mirror can also be recognised without the query parameter: it carries a parent
field pointing at the primary id.
Clients that render a model picker should request ?prefix=alias — this is what the
OmniCopilot VS Code extension does.
Discovery filters (?capability=, ?min_context=, ?kind=, ?q=)
GET /v1/models accepts opt-in discovery filters. Without any of them the response is
unchanged. They narrow the catalog the caller already sees (API-key permissions apply first),
and they run before the OpenAI-style limit / after paging:
GET /v1/models?capability=vision&capability=tool_calling # every listed capability must be true
GET /v1/models?capability=vision,reasoning&min_context=200000
GET /v1/models?kind=embedding # catalog `type`; rows without one are `chat`
GET /v1/models?q=sonnet&limit=10 # case-insensitive match on id or name
| Param | Values |
|---|---|
capability |
attachment, reasoning, structured_output, temperature, thinking, tool_calling, vision |
min_context |
non-negative integer, compared with context_length |
kind |
chat, audio, embedding, image, moderation, music, rerank, video |
q |
1–200 characters |
An unknown value, or a min_context that is not a non-negative integer (for example 1.5),
returns HTTP 400 and the message lists the valid values. Implementation:
src/app/api/v1/models/catalogFilters.ts.
Model shortlist (POST /v1/models/query)
POST /v1/models/query returns a short, deterministic list from the same catalog, with the same
auth and per-key visibility as GET /v1/models:
POST /v1/models/query
Authorization: Bearer your-api-key
Content-Type: application/json
{ "capabilities": ["vision", "tool_calling"], "min_context": 128000, "max_output": 16000,
"kind": "chat", "q": "gpt", "owned_by": "openai", "exclude": ["openai/gpt-4o"], "limit": 5 }
→ { "object": "list", "data": [ ...models ] }
Every field is optional. capabilities and exclude take an array or a comma-separated string;
max_output compares with max_output_tokens. Results are sorted by owned_by, then id.
limit defaults to 20 and values above 100 are capped at 100. Invalid JSON, an unknown
field or an invalid value returns HTTP 400.
Individually hidden chat models
A model marked Hidden on its provider page is excluded from the catalog and rejected
with HTTP 404 / model_not_found when requested explicitly. The check uses the resolved
provider and model, including provider aliases, compatible-provider node prefixes, and
connection defaults. A combo skips hidden targets and can use a visible sibling; when no
executable target remains it returns the same error code. Unhiding takes effect on the next
request. Image-only visibility overrides do not hide the chat model with the same ID.
This individual model setting is separate from the
model exposure allow/deny lists, which filter catalog
advertisement and auto-routing candidates while retaining explicit dispatch. API-key model
permissions continue to apply independently. The default catalog prefix mode remains dual.
No-thinking model variants
For thinking-capable Claude models, /v1/models also advertises a no-thinking variant whose id is prefixed with claude-3-omniroute-no-thinking/:
claude-3-omniroute-no-thinking/<provider>/<model>
Selecting this id (e.g. in a Claude Code config that always attaches a thinking block) resolves back to the real <provider>/<model> with reasoning suppressed — thinking:{type:"disabled"} on the /v1/messages path, or the reasoning/reasoning_effort fields dropped on the /v1/chat/completions path. The variant is only listed for Claude-family models that support thinking and honor disabled (so e.g. adaptive-only models that reject disabled are excluded). Operators can force the variant on or off per model via ModelSpec.noThinkingAlias.
Provider Plugin Manifest
GET /api/v1/provider-plugin-manifest
Returns the JSON-safe provider plugin manifest used by Bifrost, CLIProxyAPI, and future sidecar routers. The response is generated from the TypeScript provider registry and intentionally excludes OAuth client secrets, runtime environment resolution, executor functions, request headers, and account data.
Use this endpoint when a sidecar runs out-of-process and cannot import
open-sse/config/providerPluginManifestRegistry.ts directly.
Compatibility Endpoints
| Method | Path | Format |
|---|---|---|
| POST | /v1/chat/completions |
OpenAI |
| POST | /v1/messages |
Anthropic |
| POST | /v1/responses |
OpenAI Responses |
| POST | /v1/embeddings |
OpenAI |
| POST | /v1/images/generations |
OpenAI Images |
| POST | /v1/images/edits |
OpenAI Images (edit/inpaint) |
| POST | /v1/videos/generations |
OpenAI-style video generation |
| POST | /v1/music/generations |
OpenAI-style music generation |
| POST | /v1/audio/transcriptions |
OpenAI Audio (STT) |
| POST | /v1/audio/speech |
OpenAI TTS (returns audio body) |
| POST | /v1/rerank |
Cohere/Voyage-style rerank |
| POST | /v1/classify |
Jina classify (api.jina.ai) |
| POST | /v1/segment |
Jina segmenter (segment.jina.ai) |
| POST | /v1/moderations |
OpenAI Moderations |
| GET | /v1/models |
OpenAI |
| POST | /v1/models/query |
Filtered model shortlist |
| POST | /v1/messages/count_tokens |
Anthropic |
| GET | /v1beta/models |
Gemini |
| POST | /v1beta/models/{...path} |
Gemini generateContent |
| POST | /v1/api/chat |
Ollama |
| GET | /api/v1/vscode/{token}/ |
OpenAI catalog alias |
| GET | /api/v1/vscode/{token}/models |
OpenAI models alias |
| POST | /api/v1/vscode/{token}/chat/completions |
OpenAI tokenized alias |
| POST | /api/v1/vscode/{token}/responses |
OpenAI Responses tokenized alias |
| POST | /api/v1/vscode/{token}/api/chat |
Ollama tokenized alias |
| GET | /api/v1/vscode/{token}/api/tags |
Ollama tags tokenized alias |
All POST routes follow the same shape: Bearer your-api-key + Zod-validated JSON body (v1RerankSchema, v1ModerationSchema, v1AudioSpeechSchema, etc., see src/shared/validation/schemas.ts). 4xx is returned on schema failure.
For clients that cannot attach Authorization: Bearer ..., OmniRoute also accepts API keys in the URL via either query-string compatibility (?token=..., ?apiKey=..., ?api_key=..., ?key=...) or the dedicated /api/v1/vscode/{token}/... endpoints documented below.
# Rerank (cloud registry provider, or an OpenAI-compatible provider node as "<prefix>/<model>")
POST /v1/rerank { "model": "jina-ai/jina-reranker-v3.5", "query": "...", "documents": ["..."] }
# Jina classify (Foundation API credentials)
POST /v1/classify { "model": "jina-embeddings-v5-text-small", "input": ["..."], "labels": ["a", "b"] }
# Jina segmenter
POST /v1/segment { "content": "...", "return_chunks": true }
# Jina search (s.jina.ai; provider aliases: jina-search, jina-ai, jina)
POST /v1/search { "query": "...", "provider": "jina-search" }
# Moderations
POST /v1/moderations { "model": "omni-moderation-latest", "input": "..." }
# TTS — returns audio/mpeg (or requested format) body
POST /v1/audio/speech { "model": "openai/tts-1", "input": "Hello", "voice": "alloy" }
# Soniox TTS requires a language and a voice: `language` defaults to "en"; a missing
# voice or an OpenAI stock voice name (alloy, nova, …) becomes "Adrian"
POST /v1/audio/speech { "model": "soniox/tts-rt-v1", "input": "Xin chào", "voice": "Adrian", "language": "vi" }
# Image edit (multipart)
POST /v1/images/edits -F image=@input.png -F prompt="..." -F mask=@mask.png
# Video / music generation (provider-prefixed model id)
POST /v1/videos/generations { "model": "runway/gen-3", "prompt": "..." }
POST /v1/music/generations { "model": "kie/suno-v4.0", "prompt": "..." }
Rerank provider nodes:
POST /v1/rerankalso routes to OpenAI-compatible provider nodes (oMLX, vLLM, Infinity, TEI behind a gateway, …) addressed as<node-prefix>/<model>. Loopback nodes (localhost,127.0.0.1,172.16.0.0/12) are always eligible, and so are hostnames the operator lists inOMNIROUTE_LOCAL_PROVIDER_NODE_HOSTS(e.g. a Docker/Compose service name such ashttp://reranker:8080/v1; these are called directly, never throughHTTP(S)_PROXYor a connection's pinned proxy). Nodes on any other host — a LAN box or Tailscale peer — are eligible only when the operator enables theRERANK_REMOTE_PROVIDER_NODESfeature flag and the node's base URL passes the provider outbound URL policy (OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS/OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS). The memory engine's rerank step calls this route over loopback, so the same rule governsrerankProviderModelin the Memory settings.Local server shapes: the node is called at
<base>/v1/rerankand, on 404, at<base>/rerank(Infinity, TEI). The upstream body carries both the Cohere/OpenAI spelling (documents,return_documents) and the TEI spelling (texts,return_text), and the upstream response is normalized to the Cohere envelope: TEI's bare[{index, score, text}],{results: [{index, score}]}from thin gateways, and Voyage-style{data: [...]}all come back to the client as{results: [{index, relevance_score, document?}]}, sorted by score and capped attop_n.
Provider-node discovery: models on an OpenAI-compatible provider node appear in
GET /v1/modelsunder the node prefix. Rows that carry no endpoint metadata (typical for local/v1/modelslistings) inherit the node'sapiType, so anembeddingsnode's models aretype: "embedding"and areranknode's models aretype: "rerank"instead of defaulting to chat; an explicitsupportedEndpointson a synced or manually added row still takes precedence.
Dedicated Provider Routes
POST /v1/providers/{provider}/chat/completions
POST /v1/providers/{provider}/embeddings
POST /v1/providers/{provider}/images/generations
The provider prefix is auto-added if missing. Mismatched models return 400.
Files API
OpenAI-compatible files endpoint for batch input/output and file-purpose uploads.
| Method | Path | Description |
|---|---|---|
| POST | /v1/files |
Upload a file (multipart: file, purpose, expires_after[anchor], expires_after[seconds]) — 512 MiB max |
| GET | /v1/files |
List files for the authenticated API key |
| GET | /v1/files/[id] |
Retrieve a file's metadata |
| DELETE | /v1/files/[id] |
Delete a file |
| GET | /v1/files/[id]/content |
Stream the raw file body back |
Auth: Bearer API key — files are scoped per-API-key via getApiKeyRequestScope. A key
sees, downloads and deletes its own files only; a dashboard session without a key reads the
whole instance; a file with no owner (anonymous or dashboard-session upload) is denied to every
non-session caller. GET /v1/files rejects an anonymous caller — and a presented key that does
not resolve — with 401 even when REQUIRE_API_KEY=false, instead of listing every tenant's
files (GHSA-m3hp-hq9g-fpmv, GHSA-2jm2-mpx8-6523).
Batches API
OpenAI-compatible batch processing.
| Method | Path | Description |
|---|---|---|
| POST | /v1/batches |
Create batch — body validated by v1BatchCreateSchema (input_file_id, endpoint, completion_window) |
| GET | /v1/batches |
List batches |
| GET | /v1/batches/[id] |
Retrieve batch status + request_counts |
| DELETE | /v1/batches/[id] |
Delete a finished/failed batch |
| POST | /v1/batches/[id]/cancel |
Cancel an in-progress batch |
Auth: Bearer API key. Batches are scoped per-API-key under the same three-way rule as
files: own key only, dashboard session instance-wide, null-owner records denied to every
non-session caller (retrieve, delete, cancel, and the input_file_id check on create).
GET /v1/batches rejects an anonymous caller with 401 even when REQUIRE_API_KEY=false.
Search API
Web/search provider abstraction (Tavily, Brave, Exa, Serper, etc.).
| Method | Path | Description |
|---|---|---|
| GET | /v1/search |
List configured search providers + capabilities |
| POST | /v1/search |
Run a search query — body validated by v1SearchSchema, supports caching/coalescing |
| GET | /v1/search/analytics |
Per-provider hit/latency/cache stats |
Auth: Bearer API key (extractApiKey + isValidApiKey). Search policy enforced via enforceApiKeyPolicy.
Exa and Tavily provider_options
Exa and Tavily read provider-specific fields from a namespaced bag:
provider_options.exa (or provider_options["exa-search"]) and provider_options.tavily
(or provider_options["tavily-search"]). Only the keys below are forwarded. Each one is
checked against its own Zod schema in open-sse/handlers/search/providerOptionsPassthrough.ts,
and a key with the wrong type is dropped. Any other key is ignored, including auth, base URL,
stream and outputSchema. The flat keys used by other providers (cx, depth, ...) work
as before.
| Provider | Top-level fields | Nested |
|---|---|---|
| Exa | type, category, userLocation, moderation, additionalQueries, objective, systemPrompt, compliance (flat primitives), startPublishedDate, endPublishedDate |
contents.{text, highlights, summary, extras, subpages, subpageTarget, livecrawlTimeout, maxAgeHours} merged over the default { text: true, highlights: true } |
| Tavily | search_depth, chunks_per_source, time_range, start_date, end_date, include_published_date, filter_by_published_date, include_answer, include_raw_content, include_images, include_image_descriptions, include_favicon, include_domains_mode, language, filter_by_language, auto_parameters, exact_match, include_usage, safe_search |
— |
The generic fields always win: query, max_results, filters (domains), country and the
topic derived from search_type. On Exa, search_type: "news" keeps category: "news".
Tavily also accepts search_type: "finance" (topic: "finance").
The generic time_range now reaches both providers: Exa gets startPublishedDate (ISO
timestamp) and Tavily gets start_date (YYYY-MM-DD). An explicit date in provider_options
overrides the mapped one.
Exa results keep id, highlight_scores, summary, subpages, entities and extras in
provider_raw. Tavily results keep images and favicon.
Cost accounting is still the flat registry costPerQuery. Tavily bills
search_depth: "advanced" at more credits per call, and that difference is not reflected in
usage.search_cost_usd.
Web Fetch API
Extract content from a URL via a configured web-fetch provider (Firecrawl, Jina Reader, Tavily Extract, TinyFish Fetch, Nimble Extract, Parallel Extract).
Parallel Extract (parallel-search, same API key as Parallel Search) returns Markdown
only — any other format is a 400 — and caps content at 100,000 characters. When
the target page itself cannot be extracted, the upstream reports it inside an HTTP 200;
OmniRoute surfaces that as a 422 with a fixed message.
| Method | Path | Description |
|---|---|---|
| POST | /v1/web/fetch |
Fetch/scrape a URL — body validated by v1WebFetchSchema |
Auth: Bearer API key (extractApiKey + isValidApiKey). Policy enforced via enforceApiKeyPolicy.
Quota-aware fallback (#8297): when no explicit provider is given, the pool
(firecrawl → jina-reader → tavily-search → tinyfish → nimble-search →
parallel-search) is
walked in fixed
priority order (fill-first) — a rate-limited-but-configured provider is skipped
instead of short-circuiting the request, and a retryable/quota upstream failure
(HTTP 429 always; 402/403 for Firecrawl/Tavily/TinyFish quota-style free tiers —
not for Jina Reader, and never for a plain 400 bad request) falls through to the
next untried credentialed provider at request time. When every provider in the
pool is exhausted, the endpoint returns a single 429 (with a Retry-After
header) instead of the previous generic 400. When an explicit provider is
requested, there is no silent fallback — a rate-limited or failing explicit
provider surfaces its own error (429 if rate-limited, otherwise the upstream
status).
WebSocket Streaming
GET /v1/ws?handshake=1
Validates a WebSocket upgrade handshake and returns the wire protocol example messages (request, cancel). Actual WS frames are handled by the bundled WS server outside the Next.js route table.
Auth: Bearer API key during handshake.
Responses API over WebSocket (codex only)
# Same host:port as the HTTP API (default 20128); upgrade the connection:
wscat -c "ws://localhost:20128/v1/responses?api_key=<OMNIROUTE_API_KEY>"
# (or: -H "Authorization: Bearer <OMNIROUTE_API_KEY>")
# First frame MUST be response.create:
{ "type": "response.create", "model": "gpt-5.5", "input": [ { "role": "user", "content": "hi" } ] }
A Responses-API-over-WebSocket proxy is wired exclusively to codex (ChatGPT
backend). It listens on the same port as the API/dashboard at paths /v1/responses,
/responses, and /api/v1/responses. On the first response.create frame it
authenticates + prepares via the internal codex-responses-ws bridge, selects a
codex OAuth connection, and tunnels to wss://chatgpt.com/backend-api/codex/responses
via the wreq-js transport. Non-codex models are rejected (codex_ws_provider_required).
For quota-share routing use model: "qtSd/<group>/codex/<model>". Implemented in
app/server-ws.mjs + scripts/dev/responses-ws-proxy.mjs + src/app/api/internal/codex-responses-ws/route.ts.
Auth: Bearer API key during handshake. The bundled HTTP server (server-ws.mjs)
must be the active entrypoint (it is, by default, when app/server-ws.mjs exists).
Model id: use the bare ChatGPT id (no codex/ prefix)
The OpenAI Codex CLI validates the model name client-side when
supports_websockets = true and rejects provider-prefixed ids like
codex/gpt-5.5 (The 'codex/gpt-5.5' model is not supported when using Codex with a ChatGPT account). Send the bare id (e.g. gpt-5.5). OmniRoute's bridge is
codex-only, so it re-resolves a bare id as a codex model
(resolveCodexWsModelInfo) before tunneling upstream — even though a bare
gpt-5.5 would otherwise route to another provider over HTTP.
Configuring the OpenAI Codex CLI
Point the Codex CLI at OmniRoute by adding a custom provider with WebSocket
support to ~/.codex/config.toml (use a separate CODEX_HOME to avoid touching
an existing config):
model = "gpt-5.5" # bare id — NOT "codex/gpt-5.5"
model_provider = "omniroute"
[model_providers.omniroute]
name = "OmniRoute (WS)"
base_url = "http://localhost:20128/v1" # no trailing slash; the WS URL is derived (use https/wss in production)
wire_api = "responses" # only supported value since Feb 2026
supports_websockets = true # enables the Responses-over-WS transport
env_key = "OMNIROUTE_API_KEY" # holds the OmniRoute API key (Bearer)
export OMNIROUTE_API_KEY=sk-... # an OmniRoute API key (any key if REQUIRE_API_KEY=false)
codex exec "Responda apenas: PONG"
The CLI upgrades base_url + /responses to a WebSocket and OmniRoute tunnels it
to the selected codex OAuth connection. Validated end-to-end against the local
server: ChatGPT returns codex.rate_limits + response.created and streams the
completion.
Quotas & Issues Reporting
| Method | Path | Description |
|---|---|---|
| GET | /v1/quotas/check |
Pre-validate quota for a provider + accountId before issuing a registered key |
| POST | /v1/issues/report |
Report a quota/key issuance failure to GitHub (requires GITHUB_ISSUES_REPO + token) |
Auth: Bearer API key (isAuthenticated).
Self-service usage (/api/usage/om-usage)
Any API key can read its own usage and quotas — no management auth. This is the endpoint a client (CLI, the OmniCopilot panel) uses to show a key holder their spend.
# Text form (the historical contract — plain text for a terminal)
curl -H "Authorization: Bearer <your-api-key>" \
http://localhost:20128/api/usage/om-usage
# Structured form — what a UI consumes
curl -H "Authorization: Bearer <your-api-key>" \
"http://localhost:20128/api/usage/om-usage?format=json"
The key must have allowUsageCommand enabled (off by default — the dashboard's API-key
manager toggles it per key). Without it the endpoint answers 403.
?format=json returns a discriminated shape so a caller never reads a data field off a
refusal. On success:
{
"allowed": true,
// present only when the key opted into per-key usage limits (daily/weekly USD):
"personal": {
"dailySpentUsd": 1.25,
"dailyLimitUsd": 5,
"dailyResetAtIso": "…",
"weeklySpentUsd": 8,
"weeklyLimitUsd": 20,
"weeklyResetAtIso": "…" /* … */,
},
// the selected provider quota snapshot, or null when nothing is cached yet:
"provider": { "connectionId": "…", "provider": "claude", "plan": "…", "quotas": {/* … */} },
// every connection's snapshot, so a UI can render several providers side by side:
"providers": [
{ "connectionId": "…", "provider": "claude" /* … */ },
{ "provider": "codex" /* … */ },
],
}
On refusal (401 bad key / 403 not allowed) the same route returns
{ "allowed": false, "error": { "message": "…" } } — a present-but-empty personal/provider
(key allowed, nothing learned yet) is a different state from a refusal, and only the JSON form
distinguishes them.
Auth: the caller's own Bearer API key, validated with isValidApiKey — this is not the
management surface (/api/keys/…), which stays behind requireManagementAuth.
Raw vs. Cutoff-Adjusted Quota Percentages
When consuming /api/usage/om-usage, callers should note the distinction between the
percentages presented by the plain-text output and the structured JSON output:
| Aspect | Plain-text format (/api/usage/om-usage / @@om-usage) |
JSON format (?format=json) |
|---|---|---|
| Output field | NN% left (e.g., 20% left) |
provider.quotas / providers[].quotas |
| Measurement | Cutoff-adjusted usable allowance: remaining capacity above the cutoff threshold | Raw upstream quota: unadjusted provider measurement |
| Cutoff scaling | Scaled via effectiveRemainingPercent() so the protected reserve reads as 0% left |
None (raw snapshot values as reported by the provider) |
| Threshold data | Incorporated into the displayed percentage | Stored in quotaWindowThresholds per connection (or server defaults) |
| Primary consumer | Interactive CLIs, terminal prompts, human operators | Programmatic integrations, dashboards, analytics, billing trackers |
The Cutoff Adjustment Formula
OmniRoute supports proactive quota cutoffs to prevent upstream exhaustion (configured via connection
quotaWindowThresholds, provider defaults, or global resilience settings). When a cutoff threshold
(e.g., cutoff = 25%) is active on a quota window, the text renderer calculates remaining allowance as:
- If
remaining <= cutoff: returns0%. - If
remaining > cutoff: returns((remaining - cutoff) / (100 - cutoff)) * 100.
The formatted text output rounds this to the nearest integer (Math.round(...)).
Illustrative Example
Consider a provider window with 60% raw usage (hence 40% raw remaining quota) and a configured 25% remaining-quota cutoff threshold:
| Representation | Calculation | Remaining | Implied Used |
|---|---|---|---|
Raw JSON (?format=json) |
Upstream snapshot (quotas[window]) |
40% | 60% |
Text format (NN% left) |
((40 - 25) / (100 - 25)) * 100 = (15 / 75) * 100 |
20% | 80% (if calculating 100 - remaining) |
- Why text shows 20% left: Out of the 40% physical quota left on the account, 25% is reserved by the
cutoff policy. The caller has only 15% out of 75% usable headroom remaining before the router stops
routing to this connection. Scaling ensures the connection signals exhaustion (
0% left) exactly when routing will cease. - Why JSON preserves raw values: The JSON payload returns the exact provider snapshot (
UsageSnapshot) so external tools have access to ground-truth upstream utilization and connection-levelquotaWindowThresholdswithout server-side lossy scaling.
Consumer Migration Guidance
If an external integration (such as an editor plugin or agent extension) previously scraped the text
endpoint (NN% left) and computed used percentage as 100 - NN, migrating to ?format=json means:
- Reading raw JSON values will reflect actual upstream consumption (60% used in the example above).
- If the integration wishes to preserve the text endpoint's usable-allowance semantics, it can evaluate
the effective remaining formula client-side using
quotaWindowThresholdsfor connection-level overrides.
Semantic Cache
# Get cache stats
GET /api/cache/stats
# Clear all caches
DELETE /api/cache/stats
Response example:
{
"semanticCache": {
"memorySize": 42,
"memoryMaxSize": 500,
"dbSize": 128,
"hitRate": 0.65
},
"idempotency": {
"activeKeys": 3,
"windowMs": 5000
}
}
Latency impact
A semantic cache HIT serves the response from cache without an upstream
call, so the reported X-OmniRoute-Response-Latency is near-zero
(regardless of the original upstream latency). Latency-sensitive clients
(benchmarking, p50/p99 monitoring) should check the
X-OmniRoute-Cache-Latency response header:
| Value | Meaning |
|---|---|
synthetic |
Response served from cache; latency is not real upstream time |
| (absent) | Response from real upstream call |
Per-key cache bypass
API keys can opt out of semantic cache reads via cacheDefaultMode:
| Value | Behavior |
|---|---|
legacy |
Normal cache behavior (default) |
bypass |
Skip cache lookup entirely; always hit upstream |
Set at key creation (POST /api/keys) or update (PATCH /api/keys/[id]):
{ "cacheDefaultMode": "bypass" }
Per-request bypass
Any request can bypass the cache regardless of key settings:
X-OmniRoute-No-Cache: true
Dashboard & Management
Management routes (/api/* except public auth/login) are not authorized by
ordinary inference API keys. Credential families, scopes, and curl examples:
Management Authentication.
Authentication
| Endpoint | Method | Description |
|---|---|---|
/api/auth/login |
POST | Login |
/api/auth/logout |
POST | Logout |
/api/settings/require-login |
GET/POST | Login status (GET, public). POST sets the password / toggles login: operator-only once a password exists (dashboard session, local CLI, or login disabled — API keys and oma_ tokens get 403); a dashboard session sends currentPassword to replace the password |
Provider Management
| Endpoint | Method | Description |
|---|---|---|
/api/providers |
GET/POST | List / create providers |
/api/providers/[id] |
GET/PUT/DELETE | Manage a provider |
/api/providers/[id]/test |
POST | Test provider connection |
/api/providers/[id]/models |
GET | List provider models |
/api/providers/validate |
POST | Validate provider config |
/api/providers/bulk |
POST | Bulk-add API keys for ONE provider |
/api/providers/import |
POST | Import a heterogeneous provider LIST from a parsed CSV/JSON file (#6836); per-row partial-failure results |
/api/provider-nodes* |
Various | Provider node management |
/api/provider-models |
GET/POST/PUT/PATCH/DELETE | Custom models and per-model overrides (add, update, hide/show, delete) |
/api/provider-models/validate-and-add |
POST | Management-authenticated, opt-in strict-connection validation and atomic custom-model registration; see Model validation |
For synced/imported models, PUT /api/provider-models accepts provider, modelId, and
maxOutputTokenOverride: a positive integer sets the manual output-token cap, and null
clears it to restore the default. GET /api/provider-models?provider=<provider> returns these
values in modelOutputOverrides, including models without a custom-model row. The override
uses the runtime max_output_tokens capability and survives a model re-sync. The OpenAI-compatible
provider page offers the same edit/clear controls and marks models with explicit vision support.
Custom Chat Completions nodes adapt explicit reasoning opt-outs to the upstream backend. A
successful connection test automatically selects chat-template controls for each exact model ID
whose /models entry proves a recognized owned_by value: vllm, sglang, or llamacpp.
Transparent OpenAI-compatible wrappers may preserve the original model entry inside a nested
openai object; detection follows up to three such envelopes. Models with missing, unknown, or
conflicting ownership keep ordinary OpenAI behavior. Detection reuses the existing catalog request,
generates no completion tokens, and is invalidated when the connection endpoint changes.
To pin the behavior for a backend that does not expose that metadata, use the existing partial provider update API:
{
"providerSpecificData": {
"reasoningControl": "chat-template"
}
}
Send that body with PUT /api/providers/<connection-id>. On that connection, an explicit
reasoning effort of none is sent as chat_template_kwargs.thinking=false and
chat_template_kwargs.enable_thinking=false. Explicit native template values remain authoritative
unless a server-side reasoning rule forces an effort. The setting applies only when a custom
OpenAI-compatible connection dispatches a Chat Completions body; Responses requests and ordinary
providers keep their native request shape. Set reasoningControl to openai to force ordinary OpenAI
reasoning_effort passthrough, or omit it/set it to null to use automatic detection.
Claude Code auto-mode classifier requests default native thinking to disabled when they contain no explicit reasoning controls. Detection uses the classifier's system marker in Claude-format requests, not model names or completion limits. Explicit body controls, supported effort/thinking headers, routing rules, and resolved model effort keep their existing priority. Both classifier stages retain their prompts, completion limits, stop sequences, and real upstream permission verdicts; the second stage can still produce its requested visible reasoning as ordinary text.
OAuth Flows
| Endpoint | Method | Description |
|---|---|---|
/api/oauth/[provider]/[action] |
Various | Provider-specific OAuth |
Routing & Config
| Endpoint | Method | Description |
|---|---|---|
/api/models/alias |
GET/POST | Model aliases |
/api/models/catalog |
GET | All models by provider + type |
/api/combos* |
Various | Combo management |
/api/keys* |
Various | API key management |
/api/pricing |
GET | Model pricing |
Usage & Analytics
| Endpoint | Method | Description |
|---|---|---|
/api/usage/history |
GET | Usage history |
/api/usage/logs |
GET | Usage logs |
/api/usage/request-logs |
GET | Request-level logs |
/api/usage/[connectionId] |
GET | Per-connection usage |
/api/usage/token-limits |
GET/POST/DELETE | Per-API-key token-limit budgets |
/api/usage/model-latency-stats |
GET | Rolling per-provider/model latency aggregate (avg/p50/p95/p99, success rate); filters: windowHours/minSamples/maxRows/provider/model (#6873) |
/api/usage/cache-health |
GET | Prompt-cache health summary over call_logs — write/read ratio, p50/p90/p99 write-size distribution, heavy-write concentration, per-model split, and a healthy/degraded/thrash/no-data verdict; query params range (1h|24h|7d|30d, default 24h) and optional model (#8827) |
API key permissions
PATCH /api/keys/{id} updates an existing key's permissions. Like every /api/keys* route it needs management authorization (see Management Authentication), not an inference key. Send only the fields you want to change; a request with none of them is rejected with No valid fields to update. The accepted fields are defined by updateKeyPermissionsSchema in src/shared/validation/schemas/keys.ts.
| Field | Type | Notes |
|---|---|---|
name |
string, 1-200 chars | |
isActive |
boolean | |
isBanned |
boolean | |
expiresAt |
ISO 8601 datetime or null |
null clears the expiry |
modelAccessMode |
all | restricted |
allowedModels must be empty when the mode is all |
allowedModels, blockedModels |
array of strings, up to 1000 | |
allowedCombos |
array of strings, up to 500 | Gates which combos the key may call; direct models are governed by modelAccessMode / allowedModels |
connectionAccessMode |
all | restricted |
allowedConnections must be non-empty when restricted and empty when all |
allowedConnections |
array of UUIDs, up to 100 | |
allowAutoCombos |
boolean | false rejects requests for auto/* models with this key; keys that never set it are allowed |
catalogScope |
all | combos | models |
What GET /v1/models lists for this key (combos only, models only, or both); it does not change what the key may call |
forcedModel |
string up to 300 chars, or null |
Routes chat requests to this model/combo, over X-Route-Model and body.model; the key's limits still apply |
noLog, autoResolve |
boolean | |
throttleDelayMs |
integer, 0-300000 | |
maxSessions |
integer, 0-10000 | |
rateLimits |
array of { limit, window } (positive integers, up to 50) or null |
null clears the limits |
accessSchedule |
schedule object or null |
null clears the schedule |
scopes |
array of strings, up to 32 | |
allowedEndpoints |
array of strings, up to 20 | |
streamDefaultMode |
legacy | json |
|
cacheDefaultMode |
legacy | bypass |
See Per-key cache bypass |
compressionEnabled |
boolean | |
codexServiceMode |
one of the Codex service modes | |
disableNonPublicModels |
boolean | |
allowUsageCommand |
boolean | |
usageLimitEnabled |
boolean | |
dailyUsageLimitUsd, weeklyUsageLimitUsd |
number >= 0 or null |
|
chaosModeEnabled |
boolean |
curl -X PATCH "$OMNIROUTE_URL/api/keys/$KEY_ID" \
-H "Authorization: Bearer <management-credential>" \
-H "Content-Type: application/json" \
-d '{ "allowAutoCombos": false, "catalogScope": "combos" }'
Settings
| Endpoint | Method | Description |
|---|---|---|
/api/settings |
GET/PUT/PATCH | General settings. Security-policy keys (requireLogin, newPassword, oidc*, corsOrigins, bruteForceProtection) need currentPassword and an operator credential — API keys and oma_ tokens get 403; every mutation needs the admin access-token scope |
/api/settings/proxy |
GET/PUT | Network proxy config |
/api/settings/proxy/test |
POST | Test proxy connection |
/api/settings/ip-filter |
GET/PUT | IP allowlist/blocklist |
/api/settings/thinking-budget |
GET/PUT | Thinking/reasoning request rewrite mode (passthrough / auto-strip / custom / adaptive). Independent of compression. See THINKING_BUDGET.md. |
/api/settings/system-prompt |
GET/PUT | Global system prompt |
/api/settings/compression |
GET/PUT | Global compression config |
/api/settings/purge-request-history |
POST | Clear request log rows and local call-log artifacts |
Context & Compression
| Endpoint | Method | Description |
|---|---|---|
/api/compression/preview |
POST | Preview off/lite/standard/aggressive/ultra/RTK/stacked compression |
/api/compression/language-packs |
GET | List available Caveman language packs |
/api/compression/rules |
GET | List Caveman rule metadata |
/api/context/caveman/config |
GET/PUT | Caveman-specific settings alias |
/api/context/rtk/config |
GET/PUT | RTK-specific settings, including custom filters and raw-output retention |
/api/context/rtk/filters |
GET | RTK filter catalog and custom-filter diagnostics |
/api/context/rtk/test |
POST | Run RTK preview/test against a text payload |
/api/context/rtk/raw-output/[id] |
GET | Read retained redacted raw output by pointer id |
/api/context/combos |
GET/POST | Compression combo list/create |
/api/context/combos/[id] |
GET/PUT/DELETE | Compression combo detail/update/delete |
/api/context/combos/[id]/assignments |
GET/PUT | Assign compression combos to routing combos |
/api/context/analytics |
GET | Compression analytics alias |
Monitoring
| Endpoint | Method | Description |
|---|---|---|
/api/sessions |
GET | Active session tracking |
/api/rate-limits |
GET | Per-account rate limits |
/api/monitoring/health |
GET | Health check + provider summary (catalogCount, configuredCount, activeCount, monitoredCount). Management view includes credentialHealth: probe-cache scalars, failedConnections when failed>0, and staleDbNonOkCount (SQLite sticky test_status, not the gauge). See MONITORING_GUIDE.md. |
/api/cache/stats |
GET/DELETE | Cache stats / clear |
/api/modality-bridge/stats |
GET | In-memory attempts, successes/bridged, failures, cache hits, totalLatencyMs, latencySamples, sample-denominated averageLatencyMs, and last-use time (reset on restart; management auth) |
/api/modality-bridge/video/runtime |
GET | Strict trusted-loopback check before management auth/probe; sanitized FFmpeg/ffprobe availability and versions (no-store) |
/api/modality-bridge/video/extract |
POST | Internal authenticated trusted-loopback byte broker; 50 MiB input, bounded queue/32 MiB output, 503 capacity, 499 disconnect, 504 deadline; not a public upload API |
Backup & Export/Import
| Endpoint | Method | Description |
|---|---|---|
/api/db-backups |
GET | List available backups |
/api/db-backups |
PUT | Create a manual backup |
/api/db-backups |
POST | Restore from a specific backup |
/api/db-backups |
PATCH | Save backup retention settings (operator-only) |
/api/db-backups |
DELETE | Prune stored backups to the retention settings (operator-only) |
/api/db-backups/export |
GET | Download database as .sqlite file |
/api/db-backups/import |
POST | Upload .sqlite file to replace database (?dryRun=true validates only) |
/api/db-backups/exportAll |
GET | Download full backup as .tar.gz archive |
/api/db-backups/export, /api/db-backups/exportAll and /api/settings/export-json hand out
every stored credential, so they require the dashboard session or the local CLI (or login
disabled). API keys of any scope and oma_ access tokens get 403. The restores hold the same
boundary: POST /api/db-backups/import (dryRun=true included), POST /api/db-backups (restore
by id) and POST /api/settings/import-json replace the live database or settings, so a machine
credential gets 403 there too; omniroute sync import sends the local CLI credential. So do
DELETE /api/db-backups (prune stored backups) and PATCH /api/db-backups (retention settings:
the persisted values drive the automatic pruning after every backup). Listing (GET) and creating
a manual backup (PUT) keep ordinary management auth.
Cloud Sync
| Endpoint | Method | Description |
|---|---|---|
/api/sync/cloud |
Various | Cloud sync operations |
/api/sync/initialize |
POST | Initialize sync |
/api/cloud/* |
Various | Cloud management |
Tunnels
| Endpoint | Method | Description |
|---|---|---|
/api/tunnels/cloudflared |
GET | Read Cloudflare Quick Tunnel install/runtime status for the dashboard |
/api/tunnels/cloudflared |
POST | Enable or disable the Cloudflare Quick Tunnel (action=enable/disable) |
/api/tunnels/ngrok |
GET | Read ngrok Tunnel runtime status for the dashboard |
/api/tunnels/ngrok |
POST | Enable or disable the ngrok Tunnel (action=enable/disable) |
CLI Tools
| Endpoint | Method | Description |
|---|---|---|
/api/cli-tools/claude-settings |
GET | Claude CLI status |
/api/cli-tools/codex-settings |
GET | Codex CLI status |
/api/cli-tools/droid-settings |
GET | Droid CLI status |
/api/cli-tools/openclaw-settings |
GET | OpenClaw CLI status |
/api/cli-tools/runtime/[toolId] |
GET | Generic CLI runtime |
CLI responses include: installed, runnable, command, commandPath, runtimeMode, reason.
ACP Agents
| Endpoint | Method | Description |
|---|---|---|
/api/acp/agents |
GET | List all detected agents (built-in + custom) with status |
/api/acp/agents |
POST | Add custom agent or refresh detection cache |
/api/acp/agents |
DELETE | Remove a custom agent by id query param |
GET response includes agents[] (id, name, binary, version, installed, protocol, isCustom) and summary (total, installed, notFound, builtIn, custom).
Resilience & Rate Limits
| Endpoint | Method | Description |
|---|---|---|
/api/resilience |
GET/PATCH | Get/update request queue, connection cooldown, provider breaker, and wait settings |
/api/resilience/reset |
POST | Reset provider circuit breakers |
/api/resilience/model-cooldowns |
GET | List active per-(provider, connection, model) lockouts, sorted by remaining time |
/api/resilience/model-cooldowns |
DELETE | Clear a model lockout — body {provider, model} or {all: true} to wipe everything |
/api/rate-limits |
GET/POST | Per-account rate limit status (GET) or toggle rate-limit protection for a connection (POST {connectionId, enabled}). Supported write path for rateLimitProtection (never modified via PUT /api/providers/[id]). Note: enabled: false disables explicit protection but does not opt an active API-key connection out of the global auto-enable safety net (autoEnableApiKeyProviders). |
/api/rate-limit |
GET | Global rate limit configuration (deprecated; alias to /api/rate-limits) |
All four
/api/resilience/*routes require management auth (requireManagementAuth). See Resilience (extended) for a full breakdown of provider breaker vs connection cooldown vs model lockout.
Evals
| Endpoint | Method | Description |
|---|---|---|
/api/evals |
GET/POST | List eval suites / run evaluation |
Policies
| Endpoint | Method | Description |
|---|---|---|
/api/policies |
GET/POST/DELETE | Manage routing policies |
Compliance
| Endpoint | Method | Description |
|---|---|---|
/api/compliance/audit-log |
GET | Compliance audit log (last N) |
v1beta (Gemini-Compatible)
| Endpoint | Method | Description |
|---|---|---|
/v1beta/models |
GET | List models in Gemini format |
/v1beta/models/{...path} |
POST | Gemini generateContent endpoint |
These endpoints mirror Gemini's API format for clients that expect native Gemini SDK compatibility.
Internal / System APIs
| Endpoint | Method | Description |
|---|---|---|
/api/init |
GET | Application initialization check (used on first run) |
/api/tags |
GET | Ollama-compatible model tags (for Ollama clients) |
/api/restart |
POST | Trigger graceful server restart |
/api/shutdown |
POST | Trigger graceful server shutdown |
/api/system/env/repair |
POST | Repair OAuth provider environment variables |
/api/system/version |
GET | Current/latest version, update status, release channel |
/api/system/version |
POST | Start a deployment-aware update to the latest version |
Note: These endpoints are used internally by the system or for Ollama client compatibility. They are not typically called by end users.
OAuth Environment Repair (v3.6.1+)
POST /api/system/env/repair
Content-Type: application/json
{
"provider": "claude-code"
}
Repairs missing or corrupted OAuth environment variables for a specific provider. Returns:
{
"success": true,
"repaired": ["CLAUDE_CODE_OAUTH_CLIENT_ID", "CLAUDE_CODE_OAUTH_CLIENT_SECRET"],
"backupPath": "/home/user/.omniroute/backups/env-repair-2026-04-11.bak"
}
Version and Release Channel
GET /api/system/version
Loopback-only management route (admin auth). Returns the running version, the latest
published version and the auto-update status. releaseChannel and channels are additive
fields (rail 3.8.54); channel keeps its meaning — the deployment mode the dashboard updater
uses (npm, source or docker-compose).
{
"current": "3.8.52",
"latest": "3.8.52",
"updateAvailable": false,
"channel": "npm",
"autoUpdateSupported": true,
"autoUpdateError": null,
"news": null,
"releaseChannel": "latest",
"channels": { "latest": "3.8.52", "next": "3.8.53-rc.1" }
}
releaseChannel— npm channel of the running build:nightlyfor-nightly.*versions,nextfor other pre-releases (-rc.*,-beta.*,-alpha.*),ltsfor a stable version of an older major thanchannels.latest, otherwiselatest. Same rules asscripts/release/dist-tag.mjs, which picks the npm dist-tag at publish time.channels— published head of each dist-tag, fromnpm view omniroute dist-tags(registry HTTP fallback), cached with the same 10-minute TTL aslatest.latestis always present (falls back to thelatestfield, then"unavailable");next,nightlyandltsappear only when that dist-tag exists. ACache-Control: no-cacherequest refreshes both lookups.
The channel model (latest = v3 until the 4.0 GA, next = rc, nightly = develop builds,
lts = v3 patches after the 4.0 GA) is described in docs/ops/RELEASE_STRATEGY.md.
Audio Transcription
POST /v1/audio/transcriptions
Authorization: Bearer your-api-key
Content-Type: multipart/form-data
Transcribe audio files using any configured STT provider. The first path
segment selects the native provider (openai/…, deepgram/…). Gateways that
re-export another vendor's model use a qualified id
(openrouter/deepgram/nova-3).
Request:
curl -X POST http://localhost:20128/v1/audio/transcriptions \
-H "Authorization: Bearer your-api-key" \
-F "file=@recording.mp3" \
-F "model=openai/whisper-1"
Response:
{
"text": "Hello, this is the transcribed audio content.",
"task": "transcribe",
"language": "en",
"duration": 12.5
}
Example model ids: openai/whisper-1 (requires an OpenAI key),
openrouter/deepgram/nova-3 (requires an OpenRouter key),
deepgram/nova-3 (requires a native Deepgram key). A bare
deepgram/nova-3 request does not use OpenRouter.
Supported formats: mp3, wav, m4a, flac, ogg, webm.
Ollama Compatibility
For clients that use Ollama's API format:
# Chat endpoint (Ollama format)
POST /v1/api/chat
# Model listing (Ollama format)
GET /api/tags
Requests are automatically translated between Ollama and internal formats.
Tokenized VS Code / Headerless Aliases
Use these aliases when an integration cannot inject an Authorization header and needs the API key embedded in the base URL.
# OpenAI-style catalog alias
GET /api/v1/vscode/{token}/
GET /api/v1/vscode/{token}/models
# OpenAI-style chat aliases
POST /api/v1/vscode/{token}/chat/completions
POST /api/v1/vscode/{token}/responses
# Ollama-style aliases
POST /api/v1/vscode/{token}/api/chat
GET /api/v1/vscode/{token}/api/tags
Example:
curl https://your-host.example/api/v1/vscode/YOUR_API_KEY/models
curl -X POST https://your-host.example/api/v1/vscode/YOUR_API_KEY/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"auto","messages":[{"role":"user","content":"hello"}]}'
Notes:
- The tokenized aliases reuse the same handlers as
/v1/*and/api/tags; response shapes stay identical. - Prefer
Authorization: Bearer ...whenever the client supports custom headers. - URL-based tokens may appear in reverse-proxy logs, browser history, and telemetry outside OmniRoute. Treat them as a compatibility option, not the default authentication mode.
Telemetry
# Get latency telemetry summary (p50/p95/p99 per provider)
GET /api/telemetry/summary
Response:
{
"providers": {
"claudeCode": { "p50": 245, "p95": 890, "p99": 1200, "count": 150 },
"github": { "p50": 180, "p95": 620, "p99": 950, "count": 320 }
}
}
Budget
# Get budget status for all API keys
GET /api/usage/budget
# Set or update a budget
POST /api/usage/budget
Content-Type: application/json
{
"apiKeyId": "key-123",
"dailyLimitUsd": 5.00,
"weeklyLimitUsd": 30.00,
"monthlyLimitUsd": 100.00,
"warningThreshold": 0.8,
"resetInterval": "monthly"
}
Schema notes (
setBudgetSchema):apiKeyIdis required; at least one ofdailyLimitUsd,weeklyLimitUsd, ormonthlyLimitUsdmust be greater than zero. Optional fields:warningThreshold(0–1),resetInterval(daily|weekly|monthly),resetTime(HH:MM). The legacy{keyId, limit, period}shape returns400 Bad Request.
Token Limits
Per-API-key token budgets (distinct from the USD-based Budget above). Enforced inline on the request path: when a key's current window usage reaches its limit, requests are rejected with 429 Too Many Requests. Limits can be scoped to a specific model, a provider, or applied globally across the key; when several limits match a request, the most restrictive one wins.
# List a key's token limits (includes live window usage)
GET /api/usage/token-limits?apiKeyId=key-123
# Create or update a token limit
POST /api/usage/token-limits
Content-Type: application/json
{
"apiKeyId": "key-123",
"scopeType": "model",
"scopeValue": "openai/gpt-4o",
"tokenLimit": 1000000,
"resetInterval": "monthly",
"enabled": true
}
# Delete a token limit by id
DELETE /api/usage/token-limits?id=tl-abc
Schema notes (
setTokenLimitSchema):apiKeyIdandscopeType(model|provider|global) are required.scopeValueis required unlessscopeTypeisglobal(e.g. a model id formodelscope, a provider id forproviderscope).tokenLimitmust be a positive integer (coerced from string). Optional:id(omit to create, supply to update),resetInterval(daily|weekly|monthly, defaultmonthly),resetTime(HH:MM),enabled(defaulttrue).GETresponses enrich each limit withtokensUsed,remaining,windowStart,periodStartAt, andnextResetAt. This is a management-class endpoint (auth enforced centrally by the authz pipeline).
Request Processing
- Client sends request to
/v1/* - Route handler calls
handleChat,handleEmbedding,handleAudioTranscription, orhandleImageGeneration - Model is resolved (direct provider/model or alias/combo)
- Credentials selected from local DB with account availability filtering
- For chat:
handleChatCorechecks semantic/signature cache and resolves combo compression settings - Proactive compression runs before provider translation when enabled (
lite, Caveman, RTK, or stacked) - Provider executor sends upstream request
- Response translated back to client format (chat) or returned as-is (embeddings/images/audio)
- Usage, compression analytics, and request logs are recorded
- Fallback applies on errors according to combo rules
Full architecture reference: ARCHITECTURE.md
Combo Management
Higher-level routing combos (already summarized under /api/combos*) can also be mapped 1:1 from a model id pattern, allowing transparent redirection of an OpenAI-style model id to a combo.
| Method | Path | Description |
|---|---|---|
| GET | /api/model-combo-mappings |
List all model→combo mappings |
| POST | /api/model-combo-mappings |
Create mapping — body: {pattern, comboId, priority?, enabled?, description?} |
| GET | /api/model-combo-mappings/[id] |
Retrieve a single mapping |
| PUT | /api/model-combo-mappings/[id] |
Update fields of an existing mapping |
| DELETE | /api/model-combo-mappings/[id] |
Remove a mapping |
Auth: management session/API key (requireManagementAuth).
Webhooks
Outbound webhook subscriptions for OmniRoute events (request completion, quota exhaustion, key rotation, etc.).
| Method | Path | Description |
|---|---|---|
| GET | /api/webhooks |
List webhooks (secrets are masked to <prefix>...) |
| POST | /api/webhooks |
Create webhook — body: {url, events?: ["*"], secret?, description?} |
| GET | /api/webhooks/[id] |
Retrieve a webhook |
| PUT | /api/webhooks/[id] |
Update url/events/secret/description |
| DELETE | /api/webhooks/[id] |
Remove a webhook |
| POST | /api/webhooks/[id]/test |
Send a test payload to the webhook URL and return delivery status |
Auth: management session/API key (requireManagementAuth).
Registered Keys (Auto-Management)
Used by the auto-key management subsystem to issue and rotate API keys against a backing provider/account, with daily/hourly quotas.
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/registered-keys |
List registered keys (masked prefix only) |
| POST | /api/v1/registered-keys |
Issue a new registered key — body: {name, provider?, accountId?, idempotencyKey?, expiresAt?, dailyBudget?, hourlyBudget?}. Returns the raw key once. Returns 429 on quota refusal. |
| GET | /api/v1/registered-keys/[id] |
Retrieve a registered key's metadata (no raw material) |
| DELETE | /api/v1/registered-keys/[id] |
Revoke a registered key |
| POST | /api/v1/registered-keys/[id]/revoke |
Explicit revoke endpoint (same effect as DELETE) |
Auth: management session/API key (requireManagementAuth) — the same guard as /api/keys: a dashboard session, the local CLI token, or an API key with manage/admin scope; inference-only API keys get 403. See also /v1/quotas/check and /v1/issues/report.
Agents Protocol
Cloud agent tasks (Claude Code, Codex Cloud, OpenHands, etc.) executed remotely on behalf of OmniRoute users.
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/agents/tasks |
List tasks — optional ?provider=, ?status=, ?limit= (1–500, default 50) |
| POST | /api/v1/agents/tasks |
Create task — body validated by CreateCloudAgentTaskSchema (providerId, prompt, source, options?). Returns 201 with task envelope |
| DELETE | /api/v1/agents/tasks?id=... |
Delete a task |
| GET | /api/v1/agents/tasks/[id] |
Read task — synchronously refreshes status from the upstream cloud agent when an external_id is set |
| POST | /api/v1/agents/tasks/[id] |
Discriminated action: {action: "approve"}, {action: "message", message}, or {action: "cancel"} |
| DELETE | /api/v1/agents/tasks/[id] |
Delete a specific task by id |
Auth: management auth required on every method (
requireCloudAgentManagementAuth). Prior to v3.8.0 these were unauthenticated — see commit588a0333for the breaking change.
# Create a Claude Code cloud task
curl -X POST http://localhost:20128/api/v1/agents/tasks \
-H "Authorization: Bearer your-management-key" \
-H "Content-Type: application/json" \
-d '{"providerId":"claude-code-cloud","prompt":"Fix the failing test","source":{"repo":"...","branch":"..."}}'
Management Proxies
Outbound HTTP(S)/SOCKS proxies that can be assigned to providers, accounts, or globally.
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/management/proxies |
List proxies (with ?id= returns one; with ?id=&where_used=1 returns the assignment graph) |
| POST | /api/v1/management/proxies |
Create proxy — body validated by createProxyRegistrySchema |
| PATCH | /api/v1/management/proxies |
Update proxy — body validated by updateProxyRegistrySchema (requires id) |
| DELETE | /api/v1/management/proxies?id=...&force=1 |
Delete proxy (use force=1 to detach assignments) |
| GET | /api/v1/management/proxies/assignments |
List assignments — filterable by proxy_id, scope, scope_id; pass resolve_connection_id=<id> to resolve the active proxy for a connection |
| PUT | /api/v1/management/proxies/assignments |
Assign — body validated by proxyAssignmentSchema ({scope, scopeId?, proxyId?}). Clears dispatcher cache |
| PUT | /api/v1/management/proxies/bulk-assign |
Bulk-assign — body validated by bulkProxyAssignmentSchema ({scope, scopeIds[], proxyId?}) |
| GET | /api/v1/management/proxies/health?hours=24 |
Aggregate proxy health (success/fail counts, latency) over a window |
Auth: management session/API key on every route (requireManagementAuth).
The task description's
POST /api/v1/management/proxies/[id]/assignmentsandPOST /api/v1/management/proxies/[id]/healthare served by the flat/assignmentsand/healthroutes shown above — there are no per-id subroutes in the codebase.
Resilience (extended)
OmniRoute exposes three independent temporary-failure mechanisms; the management endpoints below let operators read and override them:
| Scope | State storage | Read | Reset / clear |
|---|---|---|---|
| Provider breaker | domain_circuit_breakers + in-memory |
/api/monitoring/health |
POST /api/resilience/reset |
| Connection cooldown | rateLimitedUntil on provider connections |
/api/rate-limits, /api/providers/[id] |
(re-enables lazily; clear via provider PUT) |
| Model lockout | In-memory model-availability registry | GET /api/resilience/model-cooldowns |
DELETE /api/resilience/model-cooldowns |
PATCH /api/resilience accepts provider breaker overrides under providerBreaker.oauth and providerBreaker.apikey. Each profile supports degradationThreshold, failureThreshold, and resetTimeoutMs; the same fields are exposed in Dashboard → Settings → Resilience.
# Clear a single model lockout
curl -X DELETE http://localhost:20128/api/resilience/model-cooldowns \
-H "Cookie: auth_token=..." \
-H "Content-Type: application/json" \
-d '{"provider":"openai","model":"gpt-4o-mini"}'
# Wipe every lockout
curl -X DELETE http://localhost:20128/api/resilience/model-cooldowns \
-H "Cookie: auth_token=..." \
-d '{"all":true}'
Full conceptual reference and breaker defaults: see CLAUDE.md → "Resilience Runtime State" and docs/architecture/RESILIENCE_GUIDE.md.
Rate-Limit Queue & Auto-Enable Safety Net
- Supported write path:
POST /api/rate-limitswith body{"connectionId": "<id>", "enabled": true|false}is the supported management endpoint to toggle rate-limit protection for a connection.PUT /api/providers/[id]deliberately does not modify or fliprateLimitProtection(#11278). - Semantics of
enabled: false: Settingenabled: falseremoves explicit protection for the connection, but does not opt an active API-key connection out of the global auto-enable safety net (autoEnableApiKeyProviders = true). InreconcileEnabledConnections(open-sse/services/rateLimitManager.ts),falseandundefinedare equivalent; active API-key connections without explicit protection remain protected under the auto-enable safety net. - Global disable: To run API-key connections without rate-limit queues, disable the safety net globally via
RATE_LIMIT_AUTO_ENABLE=false(env) orresilienceSettings.requestQueue.autoEnableApiKeyProviders = false(Settings → Resilience /PATCH /api/resilience).
Skills
Skill framework for extending OmniRoute with custom executable handlers, plus marketplace integrations.
| Method | Path | Description |
|---|---|---|
| GET | /api/skills |
List installed skills — filterable by ?q=, ?mode=on|off|auto, ?source=skillsmp|skillssh|local, paginated |
| GET | /api/skills/[id] |
Retrieve one skill |
| PUT | /api/skills/[id] |
Update skill (name, description, mode, schema, handler, tags) |
| DELETE | /api/skills/[id] |
Uninstall a skill |
| POST | /api/skills/install |
Install a skill from a raw manifest — body: {name, version, description, schema:{input, output}, handlerCode, apiKeyId?} |
| GET | /api/skills/executions |
List recent skill executions (audit trail with inputs/outputs/duration) |
| GET | /api/skills/marketplace?q=... |
Search/popular list from the SkillsMP marketplace (requires skillsmpApiKey setting) |
| POST | /api/skills/marketplace/install |
Install a skill by id from SkillsMP |
| GET | /api/skills/skillssh?q=&limit= |
Search the skills.sh registry |
| POST | /api/skills/skillssh/install |
Install a skill by id from skills.sh |
Auth: management session/API key. Marketplace search routes accept either management auth or a Bearer API key (isAuthenticated).
Memory
Persistent conversational/factual memory store, scoped per API key / session.
| Method | Path | Description |
|---|---|---|
| GET | /api/memory |
List memories — ?apiKeyId=, ?type=, ?sessionId=, ?q=, with offset/limit or page/limit pagination |
| POST | /api/memory |
Create memory — body validated by Zod: {content, key, type?, sessionId?, apiKeyId?, metadata?, expiresAt?} |
| GET | /api/memory/[id] |
Retrieve one memory |
| DELETE | /api/memory/[id] |
Delete a memory |
| GET | /api/memory/health |
Memory subsystem health (DB connectivity, embeddings backend, vector index status) |
Auth: management session/API key (requireManagementAuth). type enum: FACTUAL, EPISODIC, SEMANTIC, PROCEDURAL (see MemoryType in src/lib/memory/types.ts).
MCP Server
OmniRoute ships an embedded Model Context Protocol server with 3 transports (stdio, SSE, streamable-http) and scoped tools. The dashboard endpoints below read status/audit data and proxy the HTTP transports.
| Method | Path | Description | |
|---|---|---|---|
| GET | /api/mcp/status |
Heartbeat, transport, online state, last call, top tools, 24h success rate | |
| GET | /api/mcp/tools |
List of MCP tools with name, description, scopes, phase, auditLevel, sourceEndpoints |
|
| GET | /api/mcp/sse |
Open SSE stream for the SSE transport (returns 503 if MCP disabled or transport mismatch) |
|
| POST | /api/mcp/sse |
Send JSON-RPC frame on the SSE transport | |
| GET | /api/mcp/stream |
Open SSE side of the Streamable HTTP transport (server-initiated messages) | |
| POST | /api/mcp/stream |
Send JSON-RPC frame on the Streamable HTTP transport | |
| DELETE | /api/mcp/stream |
End a Streamable HTTP session | |
| GET | /api/mcp/audit |
Query audit log — ?limit=, ?offset=, ?tool=, `?success=true |
false, ?apiKeyId=` |
| GET | /api/mcp/audit/stats |
Aggregate audit stats (totals, success rate, avg duration, top tools) |
Auth: the sse/stream transports honor the MCP-specific auth surface (Bearer API key with mcp scope); the status/tools/audit* routes are readable from the dashboard (no extra auth required beyond reaching the dashboard host).
Both HTTP transports are gated by
settings.mcpEnabledandsettings.mcpTransport— a transport mismatch returns400, an MCP disabled state returns503.
A2A Server
OmniRoute exposes an A2A (Agent-to-Agent) JSON-RPC 2.0 endpoint plus a REST wrapper for inspection/dashboard use.
JSON-RPC
POST /a2a
Authorization: Bearer your-api-key # optional unless OMNIROUTE_API_KEY is set
Content-Type: application/json
{
"jsonrpc": "2.0",
"id": 1,
"method": "message/send",
"params": {
"skill": "smart-routing",
"messages": [{"role": "user", "content": "Route this coding task"}]
}
}
Supported methods (all gated on settings.a2aEnabled):
| Method | Description |
|---|---|
message/send |
Synchronous skill execution; returns {task, artifacts, metadata} |
message/stream |
Streaming SSE execution of the same skill set |
tasks/get |
Fetch a task by taskId |
tasks/cancel |
Cancel a task by taskId |
Built-in skills: smart-routing, quota-management, provider-discovery, cost-analysis, health-report.
Agent Card
GET /.well-known/agent.json
Returns the public A2A agent card (name, description, capabilities, skill catalog, auth scheme) — cached publicly for 1h. No auth required.
REST helpers
| Method | Path | Description |
|---|---|---|
| GET | /api/a2a/status |
A2A enabled + task stats + cached agent card summary |
| GET | /api/a2a/tasks |
List tasks — ?state=submitted|working|completed|failed|cancelled, ?skill=, ?limit= (≤200), ?offset= |
| POST | /api/a2a/tasks |
(Not implemented as a REST helper — create via JSON-RPC message/send) |
| GET | /api/a2a/tasks/[id] |
Retrieve one task |
| POST | /api/a2a/tasks/[id]/cancel |
Cancel a task |
Auth: the REST helpers run without management auth (dashboard-readable); the JSON-RPC /a2a route uses Bearer OMNIROUTE_API_KEY if configured.
Cloud, Evals & Assess
| Method | Path | Description | ||
|---|---|---|---|---|
| POST | /api/cloud/auth |
Verify a Bearer key and return masked provider connections + model aliases for cloud sync clients | ||
| POST | /api/cloud/credentials/update |
Update encrypted credentials for a cloud-synced provider | ||
| POST | /api/cloud/model/resolve |
Resolve a logical model id to a concrete provider/model using the local routing table | ||
| GET | /api/cloud/models/alias |
List model aliases as exposed to cloud sync | ||
| GET | /api/assess |
Read latest assessment categorizations (per-provider/model) | ||
| POST | /api/assess |
Run an assessment — body: `{scope: {type:"all"} | {type:"provider", providerId} | {type:"model", modelId}, trigger?}` |
| GET | /api/evals |
List built-in eval suites + most recent runs | ||
| POST | /api/evals |
Trigger an eval run | ||
| POST | /api/evals/suites |
Create a custom eval suite — body validated by evalSuiteSaveSchema |
||
| GET | /api/evals/suites/[id] |
Retrieve a custom eval suite |
Auth: /api/cloud/auth validates a Bearer key directly and returns the masked key and projectId of each connection only for a key with the manage / admin scope; the other /api/cloud/*, /api/evals/*, and /api/assess routes require management session/API key. /api/assess POST uses validateBody with a discriminated-union scope schema.
ACP (Agent Client Protocol) Management
as child processes. These endpoints manage ACP agent detection and custom agent registration.
| Method | Path | Description |
|---|---|---|
| GET | /api/acp/agents |
List all known CLI agents (built-in + custom) with installation status, version, binary |
| POST | /api/acp/agents |
Register a custom ACP agent or refresh cache — body: {id, name, binary, versionCommand, providerAlias, spawnArgs, protocol} or {action: "refresh"} |
| DELETE | /api/acp/agents |
Remove a custom ACP agent — query param: ?id=<agentId> |
Response example (GET /api/acp/agents):
{
"agents": [
{
"id": "claude",
"name": "Claude Code CLI",
"binary": "claude",
"version": "1.0.45",
"installed": true,
"protocol": "stdio",
"providerAlias": "claude",
"isCustom": false
},
{
"id": "my-custom-cli",
"name": "My Custom CLI",
"installed": false,
"protocol": "stdio",
"providerAlias": "my-provider",
"isCustom": true
}
],
"cacheTtlMs": 60000,
"cacheAge": 1234
}
Auth: Requires management session (dashboard auth_token cookie) or a
management-scoped API key.
See ACP Framework for full details.
Analytics & Observability
Real-time analytics endpoints for monitoring routing, compression, and provider
diversity. These power the /dashboard/analytics/* pages.
Auto-routing analytics
| Method | Path | Description |
|---|---|---|
| GET | /api/analytics/auto-routing |
Aggregate auto-routing stats: total calls, strategy distribution, tier distribution, top providers |
| GET | /api/analytics/auto-routing?days=7 |
Time-windowed stats (default 24h) |
Response example:
{
"window": "24h",
"totalCalls": 1234,
"strategyBreakdown": {
"rules": 800,
"cost": 200,
"latency": 150,
"sla-aware": 50,
"lkgp": 34
},
"tierBreakdown": {
"ultra": 100,
"pro": 500,
"standard": 400,
"free": 234
},
"topProviders": [
{ "provider": "openai", "calls": 500, "avgLatencyMs": 850 },
{ "provider": "anthropic", "calls": 300, "avgLatencyMs": 1200 }
]
}
Compression analytics
| Method | Path | Description |
|---|---|---|
| GET | /api/analytics/compression |
Aggregate compression stats: tokens saved, savings %, mode distribution, engine usage |
Response example:
{
"window": "24h",
"totalOriginalTokens": 5000000,
"totalCompressedTokens": 3500000,
"totalSavings": 1500000,
"savingsPct": 30.0,
"modeBreakdown": {
"lite": 400,
"standard": 600,
"aggressive": 100,
"ultra": 50,
"rtk": 84
},
"engineBreakdown": {
"caveman": 800,
"rtk": 434
}
}
Provider diversity tracking
| Method | Path | Description |
|---|---|---|
| GET | /api/analytics/diversity |
Shannon entropy-based diversity tracking: prevents single points of failure by measuring provider spread |
Response example:
{
"window": "24h",
"shannonEntropy": 2.45,
"maxEntropy": 3.17,
"diversityRatio": 0.77,
"providerUsage": {
"openai": 0.4,
"anthropic": 0.25,
"google": 0.2,
"kiro": 0.15
},
"warnings": ["OpenAI accounts for 40% of traffic — consider diversifying"]
}
Auth: Requires management session or management-scoped API key.
Admin Operations
Admin-only endpoints for operational management.
| Method | Path | Description |
|---|---|---|
| GET | /api/admin/concurrency |
Read current concurrency limits (global + per-provider) |
| POST | /api/admin/concurrency |
Update concurrency limits — body: {global?: number, perProvider?: Record<string, number>} |
Auth: Requires management session with admin scope.
CLI Tools Management
Manage CLI tools that integrate with OmniRoute (antigravity, commandCode, devin-cli, etc.). See Provider Reference for the full list.
| Method | Path | Description |
|---|---|---|
| GET | /api/cli-tools/all-statuses |
Status of all CLI tools (installed, version, last seen) |
| GET | /api/cli-tools/status |
Status detail for one CLI tool (?tool= query) |
| POST | /api/cli-tools/apply |
Write a tool's generated config (dryRun previews; 422 + containerEphemeralTarget when containerized; migration notes a legacy Codex YAML) |
| GET | /api/cli-tools/backups |
List CLI tool configuration backups |
| POST | /api/cli-tools/backups |
Create a backup of all CLI tool configurations |
| POST | /api/cli-tools/backups |
Restore: same endpoint with {tool, backupId} in the body restores that backup |
| GET | /api/cli-tools/antigravity-mitm |
Antigravity MITM proxy status (the "antigravity-mitm" CLI tool) |
| POST | /api/cli-tools/antigravity-mitm/alias |
Configure antigravity-mitm aliases |
Auth: Requires management session.
Agent Skills
Manage AI agent skills (similar to OpenAI's custom GPTs but for agents).
| Method | Path | Description |
|---|---|---|
| GET | /api/agent-skills |
List all agent skills (built-in + custom) |
| GET | /api/agent-skills/[id] |
Get a specific agent skill |
| POST | /api/agent-skills |
Create a custom agent skill — body: {name, description, prompt, model?, temperature?} |
| PUT | /api/agent-skills/[id] |
Update a custom agent skill |
| DELETE | /api/agent-skills/[id] |
Delete a custom agent skill |
| GET | /api/agent-skills/[id]/raw |
Get raw prompt + metadata (no execution) |
| POST | /api/agent-skills/generate |
AI-generate a new skill from a natural language description |
Auth: Requires management session or management-scoped API key.
Cache Management
Manage the semantic cache and reasoning cache.
| Method | Path | Description |
|---|---|---|
| GET | /api/cache |
Cache overview: total entries, hit rate, size on disk |
| GET | /api/cache/entries |
List cached entries (with pagination) |
| DELETE | /api/cache/entries |
Delete cache entries (filter by query parameters) |
| GET | /api/cache/stats |
Detailed cache statistics (per-provider, per-model) |
| GET | /api/cache/reasoning |
Reasoning cache status (for reasoning replay) |
| DELETE | /api/cache/reasoning |
Clear reasoning cache — query params: ?toolCallId=<id> (single) or ?provider=<p> or no params (all) |
Auth: Requires management session.
Memory System
Manage persistent memory (FTS5 + vector embeddings).
| Method | Path | Description |
|---|---|---|
| GET | /api/memory |
List memory entries (filter by scope, type, search query) |
| POST | /api/memory |
Create a new memory entry — body: {scope, type, content, metadata?} |
| GET | /api/memory/[id] |
Get a specific memory entry |
| PUT | /api/memory/[id] |
Update a memory entry |
| DELETE | /api/memory/[id] |
Delete a memory entry |
| GET | /api/memory?q= |
Search memory (FTS5 + vector) — stats are included in the same response |
Auth: Requires management session or management-scoped API key.
Webhooks
Manage webhook subscriptions for events.
| Method | Path | Description |
|---|---|---|
| GET | /api/webhooks |
List all webhook subscriptions |
| POST | /api/webhooks |
Create a webhook subscription — body: {url, events[], secret?, active?} |
| GET | /api/webhooks/[id] |
Get a specific webhook subscription |
| PUT | /api/webhooks/[id] |
Update a webhook subscription |
| DELETE | /api/webhooks/[id] |
Delete a webhook subscription |
| GET | /api/webhooks/[id]/deliveries |
List delivery history for a webhook (success/failure log) |
| POST | /api/webhooks/[id]/test |
Send a test event to a webhook |
Auth: Requires management session.
See Webhooks Framework for full event types.
Skills Framework
Manage Skills (the agentic extensions framework).
| Method | Path | Description |
|---|---|---|
| GET | /api/skills |
List all installed skills (built-in + custom) |
| POST | /api/skills/install |
Install a skill from a local path or URL |
| DELETE | /api/skills/[id] |
Uninstall a skill |
| PUT | /api/skills/[id] |
Enable or disable a skill — body: {enabled?: boolean, mode?: "on" | "off" | "auto"} |
| POST | /api/skills/executions |
Execute a skill — body: {skillName, apiKeyId, input?, sessionId?} |
| GET | /api/skills/executions |
List execution history for all skills (filter by ?apiKeyId=) |
Auth: Requires management session or management-scoped API key.
See Skills Framework for full details.
Plugins
Manage OmniRoute plugins (third-party extensions).
| Method | Path | Description |
|---|---|---|
| GET | /api/plugins |
List installed plugins |
| POST | /api/plugins/marketplace/install |
Install a plugin from the marketplace |
| DELETE | /api/plugins/[name] |
Uninstall a plugin |
| POST | /api/plugins/[name]/activate |
Activate a plugin |
| POST | /api/plugins/[name]/deactivate |
Deactivate a plugin |
| GET | /api/plugins/[name]/config |
Get plugin configuration |
| PUT | /api/plugins/[name]/config |
Update plugin configuration |
Auth: Requires management session.
See Plugins Framework for full details.
Shadow Routing
Shadow / A-B comparison of providers is not a standalone REST surface — it is configured through combo routing (see Auto-Combo). Per-combo comparison metrics are served by GET /api/combos/metrics.
Guardrails
Inspect the runtime guardrails (PII detection, prompt injection detection, vision bridging). Guardrails run on every request; per-call opt-out is via the x-omniroute-disabled-guardrails request header — there is no persisted enable/disable surface.
| Method | Path | Description |
|---|---|---|
| GET | /api/guardrails |
List the registered guardrails and their status (name / enabled / priority) |
| POST | /api/guardrails/test |
Dry-run the pre-call pipeline over a sample input — body: {input, disabledGuardrails?} |
Auth: Requires management session.
See Security > Guardrails for full details.
Authentication
See Management Authentication for the four
credential families (dashboard session, local CLI token, oma_live_… Access
Token, manage-scoped API key) and how they differ from inference keys.
- Dashboard routes (
/dashboard/*) useauth_tokencookie - Login uses saved password hash; fallback to
INITIAL_PASSWORD requireLogintoggleable via/api/settings/require-login/v1/*routes optionally require Bearer API key whenREQUIRE_API_KEY=true- "management token" / "management-scoped API key" in this reference means one of the families in that guide — not an undefined extra secret type
Breaking change (v3.8.0) —
/api/v1/agents/tasks/*and the cooldown management endpoints now require management auth (dashboardauth_tokencookie or a management-scoped API key). Clients that previously called these routes unauthenticated will receive401 Unauthorized. See commit588a0333(fix(auth): require management auth for agent and cooldown APIs).