Files
OmniRoute/docs/openapi.yaml
Diego Rodrigues de Sa e Souza 3ddcee6369 Release v3.8.45 (#6202)
* chore(release): open v3.8.45 development cycle

* chore(release): parallel-cycle flow — sync-next-cycle script + Hard Rule #21 semantics (#6203)

Integrated into release/v3.8.45

* perf(test): tsx/esm loader + tsx 4.23 + órfãos recuperados + CI via npm scripts (plano testes+CI, Pacote 1) (#6214)

* perf(test): tsx/esm loader, tsx 4.23 bump, orphan tests recovered, CI runs npm scripts

Pacote 1 (quick wins) do plano mestre testes+CI:

- Swap --import tsx -> --import tsx/esm on the 19 test scripts: the repo is pure
  ESM and the CJS hook costs ~1.3s PER test process (2,462 processes/run).
  Measured: bootstrap 2.3s -> 1.1s; real-suite A/B (tests/unit/db, 12 files)
  22.2s -> 14.1s wall (-36%), 82/82 pass. Non-test scripts keep the full hook.
- Bump tsx ^4.22.3 -> ^4.23.0 (fix for privatenumber/tsx#809 startup regression;
  helps module resolution on big graphs — hook cost unchanged, honest note).
- Recover 22 ORPHAN test files (tests/unit/feature-triage/*.test.mjs, 53 cases,
  53/53 pass) that matched no glob and ran in NO CI job; drop the dead
  'executors' dir from the braces glob.
- Single source of truth for the unit-suite invocation: new test:unit:ci:shard
  (shard via TEST_SHARD env) called by ci.yml test-unit/node24/node26/coverage
  and quality.yml fast-unit — closing two silent drifts: CI was NOT importing
  setupPolyfill.ts, and the fast path glob OMITTED tests/unit/memory + usage.
  quality.yml TIA step + ci.yml test-integration get the tsx/esm swap only.

Validation: full unit suite 21,153 tests / 21,135 pass / 13 skip (5 fails =
known load-flake family, 10/10 green rerun isolated); vitest 237/237; smoke
db+feature-triage 135/135. Record run 2 = ci.yml workflow_dispatch on the
stacked pacote-2 branch (clean runners).

* fix(test): dashboard UI tests keep full tsx hook; recover 15 more .mjs orphans; extend discovery gate to .mjs

Follow-up do dispatch de validacao (run 28720431562), que pegou 2 problemas reais:

1. tests/unit/dashboard/** (11 arquivos, 102 casos) importam componentes React cujo
   grafo puxa @lobehub/icons — o build es/ dele faz require() interno de arquivos com
   sintaxe ESM, que so funciona com o patch CJS do tsx (sem ele: SyntaxError
   'Unexpected token export' no CI; local vira crawl de ~60s/arquivo). Esses 11
   arquivos rodam agora numa 2a invocacao com --import tsx COMPLETO (mesmo shard),
   e o resto da suite mantem tsx/esm (-50% bootstrap). Validado: 102/102.
2. check:test-discovery falhou porque ancorava textualmente os globs nos workflows —
   COLLECTORS atualizado p/ o modelo fonte-unica (ancora = nome do script
   test:unit:ci:shard nos workflows) + varredura ESTENDIDA a .test.mjs, que era o
   ponto cego que deixou os orfaos apodrecerem. A extensao revelou +15 orfaos .mjs
   (top-level + db/) alem dos 22 de feature-triage — TODOS religados via glob
   tests/unit/**/*.test.mjs (171/171 pass). Um deles (encryption-error-handling)
   codificava o contrato PRE-hardening (decrypt falho retornava ciphertext cru —
   vazamento); alinhado ao contrato shipped (null + log) com comentario.

Gate: [test-discovery] OK — 2892 arquivos, 22 collectors, 60 orfaos congelados
(divida rastreada, shrink-only).

* ci: dedup heavy pipeline — compat to nightly, coverage folded into unit shards, i18n single job, draft-skip (#6215)

Pacote 2+3-ci do plano mestre testes+CI (aprovado 2026-07-04). O CI pesado rodava a
suite unit 4x por sync da release-PR (95 jobs, 208 min-maquina) e o ciclo v3.8.44
disparou 123 desses runs (88 cancelados, 0 uteis) porque a release-PR viva fica
aberta o ciclo inteiro.

- D2: matrizes Node 24/26 (build + 8 jobs de teste, ~28% do custo por run) saem do
  per-sync e viram .github/workflows/nightly-compat.yml (diario, fail-fast off,
  resolve a release ativa como o nightly-release-green, abre issue de tracking em
  falha). ci.yml/ci-summary limpos das referencias.
- D3: a matrix Coverage Shard x8 (~18% do custo) e eliminada — o job test-unit roda
  os MESMOS shards sob c8/NODE_V8_COVERAGE e sobe os artifacts coverage-shard-N; o
  job de merge (test-coverage) so repontou needs (padrao do CI do nodejs/node).
  timeout test-unit 15->25min pelo overhead de instrumentacao.
- D4: a matrix i18n de ~40 jobs de <1min (saturava sozinha os 20 slots de
  concorrencia da conta Free) vira 1 job que itera os idiomas com ::group:: por
  idioma e artifact unico com resultados nomeados por idioma (antes 40 result.txt
  colidiam no merge-multiple do ci-summary).
- P3: jobs pesados pulam pull_requests DRAFT (predicado em 10 jobs-raiz; o resto
  pula pela cadeia de needs; ci-summary segue rodando como sinal unico) — a skill
  /generate-release ja abre a release-PR viva como draft e flipa ready no 0a.0a
  (commit eb04fc5 no repo .agents/skills).
- C5 (CodeQL schedule) NAO incluido: bloqueado na acao do dono Settings -> CodeQL
  Default->Advanced (documentado no proprio codeql.yml).

Validacao: js-yaml parse ok; check:workflows zizmor 156 < baseline 159 (ratchet
verde); validacao de execucao = workflow_dispatch deste ci.yml neste branch ate
package-artifact + electron-package-smoke verdes (registrada no PR).

* feat(quality): no-new-warnings por PR — ESLint bulk suppressions + lint-guard fork-condicional (Pacote 4) (#6218)

* feat(quality): no-new-warnings per PR via native ESLint bulk suppressions

Pacote 4 do plano mestre testes+CI (aprovado 2026-07-04). O ratchet de
eslintWarnings so rodava no CI pesado (release-PR) -> o drift acumulava invisivel
e explodia na release (+41/+37/+88 por ciclo, rebaselinado as cegas — historico
no proprio quality-baseline.json). Modelo novo (SonarSource Clean-as-You-Code +
ESLint bulk suppressions nativo >=9.24):

- config/quality/eslint-suppressions.json congela a divida existente por
  arquivo+regra: 476 arquivos / 4.273 violacoes.
- npm run lint + lint-staged (pre-commit) + novo job lint-guard no quality.yml
  rodam suppressions-aware: violacao NOVA fica vermelha NO PR que a introduz
  (bulk suppressions ainda eleva estouros de baseline por arquivo a error).
- 3 regras warn promovidas a error em src/** (react-hooks/exhaustive-deps,
  @next/next/no-img-element, import/no-anonymous-default-export) — divida
  existente congelada, ocorrencia nova = erro imediato.
- collect-metrics mede sob o baseline congelado -> a metrica eslintWarnings
  vira 'divida liquida nova' (~0 em regime); baseline apertado 4279->0 no mesmo
  PR (exigencia do require-tighten). Aperto do ESTOQUE congelado: npx eslint .
  --prune-suppressions na reconciliacao da release.
- Principio Zero: lint-guard usa continue-on-error para PR de FORK (report-only;
  a campanha /green-prs aplica o fix via co-autoria) — bloqueante so para
  branches internas, a origem real do drift.

Validacao: negativo (any novo em tests/) exit 1; negativo (img em src/, regra
promovida) exit 1; positivo escopado exit 0; baseline gerado por --suppress-all
no tip (tree inteiro passa por construcao); YAML js-yaml ok.

* fix(quality): clear the 6 residual warnings so lint-guard runs clean at --max-warnings 0

The committed baseline still let 6 warnings through the lint-guard gate:
5 now-unused inline eslint-disable directives (the file-level suppressions
made them redundant — removed via eslint --fix, suppressions regenerated to
absorb the re-exposed occurrences) and 1 anonymous default export in
tests/load/k6-soak.js (outside the src/** severity-override scope — named
the k6 scenario function instead).

Verified on the clean tree: lint-guard exit=0; any-canary (new 'const x: any'
in open-sse) exit=1 — the gate bites on NEW violations while the 4,273
frozen ones stay suppressed (476 files).

* fix(ci): lint-guard continue-on-error must be boolean on non-PR events

github.event.pull_request is undefined on workflow_dispatch — the bare property
expression made the job fail at plan time (run 28722888456: 4 jobs green, run red,
lint-guard never materialized). Guard with event_name check so the expression is
always boolean: PR de fork = report-only (Principio Zero), resto = blocking.

* docs(changelog): v3.8.45 bullets for the tests+quality+CI pipeline overhaul (#6214, #6215, #6218)

i18n CHANGELOG mirrors intentionally left to the release reconciliation
(release:sync-changelog-i18n), per cycle practice.

* fix(api): stabilize relay SSRF-guard binding for minified builds (#6149) (#6224)

* fix(mcp): forward extra context through static tool loops (#6178) (#6228)

* fix(services): 9Router embed route + pre-spawn port probe (#6205) (#6227)

* fix(backend): system-first memory injection for strict providers (#6135) (#6225)

* fix(auth): clear error for stale-key decryption failures (#6148) (#6226)

* fix(backend): record reasoning source for zero-metered reasoning models (#6187) (#6229)

* fix(providers): refresh stale NVIDIA NIM model registry (#6108) (#6223)

* fix(backend): distinct max_input_tokens for GPT-family models (#6191) (#6230)

* fix(oauth): extract keychain-import-only guard to restore file-size freeze (base-red) (#6158)

`src/app/api/oauth/[provider]/[action]/route.ts` grew to 959 lines, past its
frozen cap of 924 (`check:file-size` → Fast Quality Gates red on release/v3.8.44).
The growth came from #6054 (graceful 400 for keychain-import-only providers / zed):
a doc block, two Sets (KEYCHAIN_IMPORT_ONLY_PROVIDERS, OAUTH_FLOW_ACTIONS) and a
keychainImportOnlyResponse() helper, plus two duplicated guard blocks in GET/POST.

That is a cohesive, self-contained leaf, so extract it to a new
`keychainImportOnly.ts` exposing `keychainImportOnlyGuard(provider, action)`
(returns the 400 NextResponse or null). The two route callsites collapse to a
2-line guard each. route.ts: 959 -> 918 (< 924, freeze restored). No behavior
change.

Tests (Rule #8/#18):
- Existing tests/unit/oauth-keychain-import-only-6041.test.ts (route-level GET/POST
  zed 400) still pass unchanged — behavior preserved.
- New tests/unit/oauth-keychain-import-only-guard.test.ts pins the extracted guard
  in isolation (zed+flow -> 400, normal provider -> null, zed+non-flow -> null).

* fix(dashboard): stop model-test error freezing the page (React #31 object toast) (#6161)

Clicking 'test' on a provider model (e.g. a ClinePass flash model) could freeze
the entire dashboard. Root cause: POST /api/models/test returned an OBJECT in
`error` on the Zod-validation and invalid-JSON paths (`validation.error.format()`
/ a details object). The client does `notify.error(data.error)`, and
NotificationToast renders the message directly as a React child — an object throws
React #31 ('Objects are not valid as a React child'), crashing the tree = frozen
page instead of a toast.

Fixed in three layers (defense in depth):
1. Server (root cause): /api/models/test now returns a STRING `error` on every
   path — flattens Zod issues to text, returns 'Invalid JSON body' for bad JSON.
2. Client: onTestModel funnels the response through extractApiErrorMessage() so any
   object-shaped error is coerced to a string before notify.error.
3. Toast: NotificationToast coerces title/message via toToastText() — a resilient
   catch-all so no future caller can freeze the page with a non-string.

Tests (Rule #18, both node:test / blocking suite):
- tests/unit/models-test-error-shape.test.ts — asserts STRING error on Zod-fail,
  missing-field, and invalid-JSON (fails on the pre-fix route: 3/3 red -> green).
- tests/unit/notification-toast-coercion.test.ts — toToastText coercion matrix.

* fix(dashboard): remove the always-on Auto-Routing (combo) banner from the home page (#6164)

The blue "Auto-Routing Active — OmniRoute is automatically routing requests
using combo-based strategies" banner was rendered unconditionally on the home
page (`/home`, the default dashboard landing) — it did NOT reflect whether
auto-routing was actually active, and reappeared on every fresh browser / private
window / cleared localStorage (dismissal is stored per-browser). It added noise
to the landing page without conveying live state.

Remove it: drop the <AutoRoutingBanner /> usage + import from home/page.tsx and
delete the now-unused component and its test.

* fix(cline): force upstream streaming for Cline/ClinePass (streaming-only API) (#6165)

* fix(cline): force upstream streaming for Cline/ClinePass (streaming-only API)

Cline's API (api.cline.bot) only implements streaming (streamText). A
non-streaming request returns HTTP 500 "generateText is not implemented" (Claude
models) or HTTP 502 "empty response" (others). Live-verified on the VPS:
stream:true → works (STREAM_OK), stream:false → fails. This is why testing a Cline
model in the dashboard (the test button sends stream:false) failed.

Fix (reuses the existing isClaudeCodeCompatible mechanism, no new handler):
- Flag `cline` and `clinepass` registry entries with `forceStream: true`.
- In chatCore, OR `providerRequiresStreaming` into `upstreamStream` (line 1591)
  so the upstream request always streams for these providers, while the client's
  original `stream` intent still drives the response format. The existing
  non-streaming branch (parseNonStreamingResponseBody) already accumulates the
  upstream SSE and converts it back to JSON for stream:false clients — the same
  path Claude-Code-compatible providers already use.

Tests (Rule #18): tests/unit/cline-force-stream.test.ts pins the registry flags +
resolveStreamFlag forcing behavior. Live VPS before/after recorded on the PR.

* fix(sse): cline forceStream must stream upstream only, keep client JSON

The #2081 wiring fed providerRequiresStreaming into resolveStreamFlag,
forcing the client-facing stream flag to true for forceStream providers.
That skips the if(!stream) branch that drains a forced upstream SSE and
converts it back to JSON, so a stream:false caller (model-test button,
plain JSON API) got STREAM_EARLY_EOF instead of a JSON body.

Keep providerRequiresStreaming only on upstreamStream (force upstream to
stream); leave the client-facing stream as the client sent it, so
readNonStreamingResponseBody accumulates the SSE into JSON. The promised
handleForcedSSEToJson (#2081 comment) was never implemented — this uses
the existing non-streaming SSE-buffering path (same as isClaudeCodeCompatible).

Live-verified on VPS: cline stream:true worked, stream:false failed.

* fix(providers): correct Kiro model catalog to real upstream ids (#6170)

* fix(providers): correct Kiro model catalog to real upstream ids

Kiro's API (generateAssistantResponse) returns 400 "Invalid model. Please
select a different model" for any id it does not recognize. The registry
exposed fabricated ids (copied from OmniRoute's own Anthropic catalog) that
Kiro never serves, so every call to them 400'd. Live-verified on the VPS:

  Removed (400 Invalid model):
    - auto-kiro       (no "auto" model id — was sent verbatim upstream)
    - claude-fable-5  (Kiro offers no Fable)
    - claude-opus-4.8/4.7/4.6 (Kiro offers no Opus)
  Corrected:
    - claude-sonnet-4.6 -> claude-sonnet-4.5 (Kiro's Sonnet is 4.5; 4.5 -> 200)
  Kept:
    - claude-sonnet-5 (real Kiro model, plan-gated per account)
    - claude-haiku-4.5, deepseek-3.2, glm-5, minimax-m2.5/m2.1,
      qwen3-coder-next (all proven 200 on the VPS)

Aligns the free-model catalog and drops the orphaned auto-kiro price key.
Regression guard: tests/unit/kiro-catalog-real-models.test.ts (3/3).
Kiro cluster #6112/#6113/#6099.

* test(providers): align stale Kiro-catalog tests to the corrected upstream ids

The fabricated Kiro ids removed in the parent commit (claude-fable-5,
claude-opus-4.8/4.7/4.6, claude-sonnet-4.6) were still asserted as present by
three pre-existing tests, which encoded the bug:
- catalog-updates-v3x: now asserts Kiro does NOT expose Fable 5 / Opus (kept the
  legit cc exposure) and guards the real claude-sonnet-4.5 pricing.
- model-family-fallback-notation: the dot-notation example moves from kiro/ to
  anthropic/ (which genuinely serves Opus/Fable in dot notation) — coverage kept.
- provider-models-route: the Kiro local-catalog assertion now expects the real
  Sonnet 5 / Sonnet 4.5 set and negatively guards the fabricated ids.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* fix(sse): surface ChatGPT-web image silent-drop as an accurate error (#6208)

When ChatGPT Web generates an image as an image_asset_pointer but the pointer
fails to resolve to a downloadable URL (unknown asset scheme, download 403/
expired, oversize), resolveImagePointers returned [] — indistinguishable from
'no image produced' — so the image-generation handler reported the misleading
502 'completed without returning image markdown'. The image genuinely existed
upstream; OmniRoute dropped it silently.

Fix: the executor flags x_image_resolution_failed when a pointer existed but
none resolved (and logs the unresolved asset scheme for follow-up), and the
handler surfaces a truthful 'generated but not retrievable' 502 instead of
'no image markdown'. Adds executorFactory DI for unit testing.

TDD: tests/unit/chatgpt-web-image-silentdrop.test.ts (red -> green), plus the
existing chatgpt-web / image-generation-handler suites stay green.

Reported via community triage (mesh escalated backlog).

* fix(dashboard): providers page data-timeout guard + live-ws standalone wiring (#6211)

* fix(dashboard): providers page data-timeout guard + live-ws standalone wiring

Captura de trabalho em progresso: timeout de dados na página de providers,
ajuste em ProviderLimits e instrumentation-node, com testes novos
(providers-page-data-timeout, live-ws-standalone-wiring).

* chore(quality): rebaseline ProviderLimits/index.tsx file-size (+6, #6211 data-timeout guard)

Cohesive fix growth from PR #6211's data-timeout guard on the quota page's two
first-paint fetches (1121->1127). The fast-path PR->release skips check:file-size,
so the bump lands with the PR. Justification recorded in file-size-baseline.json.

* fix(translator): strip reasoning param for nvidia z-ai/glm-5.2 (#6181)

* fix(translator): strip reasoning param for nvidia z-ai/glm-5.2

NVIDIA NIM OpenAI-compatible wrapper rejects the reasoning body field
and returns HTTP 400 "Unsupported parameter(s): `reasoning`".
Add a StripRule scoped to provider=nvidia + model /z-ai\/glm-5\.2/i.
Mirrors PR #6102 drop pattern (minimax-m2.7 thinking).

* docs(translator): tighten nvidia glm-5.2 strip-rule comment

* fix(translator): anchor glm-5.2 strip rule with word boundary

* fix: add nvidia to PROVIDER_TOOL_LIMITS (1536) to prevent tool truncation (#6177)

NVIDIA NIM API (nvidia/* models) silently truncates the tool list to 128
(the default MAX_TOOLS_LIMIT) because nvidia is not in PROVIDER_TOOL_LIMITS.
Tools beyond index 127 are dropped, causing agents to lose access to
critical tools like task, read, or high-index MCP tools.

Verified that NVIDIA NIM API supports up to 1536 tools by direct testing.
End-to-end confirmed: 198 tools sent, model successfully called tools at
indices 193, 195, and 197 (previously dropped by truncation to 128).

Follows the same pattern as #5563 (grok-cli: 200), integrated in v3.8.43.

* feat(provider): add Claude 5 Sonnet to Claude Web provider (#6200) (#6209)

* feat(provider): add Claude 5 Sonnet to Claude Web provider (#6200)

* test(providers): guard claude-web claude-sonnet-5 registry entry (#6209)

Adds the missing regression test the PR-test-policy gate requires: asserts the
claude-web registry exposes claude-sonnet-5 (Claude 5 Sonnet web) alongside the
existing 4.6 Sonnet / 4.5 Haiku entries. Fails on the release base (no entry).

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>

* fix(cli): detect POSIX auto-set HOSTNAME via os.hostname() to fix bind address (#6194) (#6195)

POSIX shells (bash/zsh) always set HOSTNAME to the machine name. The
.env loader uses first-wins semantics, so HOSTNAME=0.0.0.0 in .env is
silently ignored. This causes the server to bind to the LAN hostname
instead of 0.0.0.0, breaking localhost access and all internal
self-requests (ModelSync, HealthCheck, cloud sync).

The fix compares process.env.HOSTNAME against os.hostname(): when they
match, it's the POSIX auto-set signature and HOSTNAME is ignored.
OMNIROUTE_SERVER_HOST takes precedence as the dedicated escape hatch.

Backward compatibility is preserved: users who set HOSTNAME to a value
that doesn't match the machine name (e.g. Windows CMD/PowerShell users
with HOSTNAME in .env) will still have their value honoured.

Closes #6194

* feat(sse): surface Kiro adaptive-thinking reasoning as reasoning_content (#6213)

Kiro/CodeWhisperer streams Claude's reasoning as native `reasoningContentEvent`
frames when adaptive thinking is enabled, but the Kiro executor had no handler
for them, so `reasoning_effort` requests returned no reasoning. Wire it end to
end:

- translator (openai-to-kiro): enable Kiro thinking when the request carries
  `reasoning_effort`, Anthropic `output_config.effort`, or a `thinking` block
  (`{type:"enabled",budget_tokens}` mapped to a level; `{type:"adaptive"}`
  defaults to `high`, matching Anthropic's documented default). Prepends the
  Kiro `<thinking_mode>`/`<max_thinking_length>` prompt directive and sets
  top-level `additionalModelRequestFields` ({output_config.effort,
  thinking:{type:"adaptive"}, max_tokens}). Gated on `supportsReasoning`; drops
  non-default temperature/top_p (rejected by adaptive-only Claude models).
- executor transformRequest: forward `additionalModelRequestFields` to AWS
  (previously dropped by the strict top-level allowlist).
- executor stream loop: parse `reasoningContentEvent` (and reasoningText
  variants) into the OpenAI reasoning_content channel.

Verified against the live CodeWhisperer stream: reasoningContentEvent frames are
returned, and larger effort/budget measurably deepens reasoning up to the model
cap. Unit tests cover the effort sources, forwarding, temp/top_p stripping, and
native reasoning-frame parsing.

* fix(chatcore): exempt opencode client from the default 128-tool truncation (#6193)

* fix(chatcore): exempt opencode client from the default 128-tool truncation

The default MAX_TOOLS_LIMIT (128) cap made truncateToolList blind-slice
tools.slice(0, 128), dropping opencode's built-in task tool and part of
its MCP tools when the inbound list exceeded 128 — so models routed
through OmniRoute could not launch subagents or reach all their tools.

Detect the opencode client (any x-opencode-* header, or 'opencode' in
the user-agent) and bypass ONLY the speculative 128 default. A known
provider ceiling (proactive PROVIDER_TOOL_LIMITS or a detected limit)
always wins and still truncates, even for opencode, so upstreams with
real hard limits (e.g. grok-cli 200) keep their 400-avoidance guard.
Non-opencode clients are unchanged.

- requestFormat.ts: add isOpencodeClient(headers, userAgent) + expose it
  on resolveChatCoreRequestFormat.
- toolLimitDetector.ts: add getKnownToolLimit(); getEffectiveToolLimit
  becomes getKnownToolLimit(provider) ?? DEFAULT_LIMIT (byte-identical
  for existing callers).
- upstreamBody.ts: truncateToolList takes bypassDefaultToolLimit and
  encodes the precedence; fix cosmetic debug-log count.
- chatCore.ts: thread the flag into prepareUpstreamBody.
- tests: extend tool-limit-detector unit tests.

* refactor(tools): accept nullable provider in tool-limit resolvers

Address PR review: widen getKnownToolLimit / getEffectiveToolLimit to
(provider: string | null | undefined) to match the call sites in
truncateToolList, and add unit assertions covering null/undefined
providers (getKnownToolLimit -> null, getEffectiveToolLimit -> 128).

---------

Co-authored-by: DKotsyuba <16292493+DKotsyuba@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>

* fix(providers): refresh GitHub Copilot catalog (#6154)

* fix(providers): refresh github copilot catalog

Limit GitHub Copilot discovery to the curated supported model set and keep the provider cooldown panel client-safe by moving countdown formatting out of localDb.

* chore(quality): rebaseline providerPageHelpers.ts file-size (+13, #6154 copilot catalog)

The GitHub Copilot catalog refresh grows the provider-page model-section helper
(1021->1034). Fast-path PR->release skips check:file-size, so the bump lands with
the PR. Justification recorded in file-size-baseline.json.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

---------

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* chore(quality): rebaseline kiro-translator file-size debt from #6213

The #6213 kiro adaptive-thinking feature grew openai-to-kiro.ts (853->890) and its
test (1093->1234); the fast-path PR->release does not gate check:file-size on merge,
so the growth accumulated on the release tip. Rebaselined to keep the tip green.
Justification recorded in file-size-baseline.json.

* fix(doctor): resolve two false-positive WARNs (#6162) (#6163)

* fix(doctor): resolve two false-positive WARNs (#6162)

The `omniroute doctor` command reported two warnings on healthy installs
even though the underlying checks actually passed. Both came from the
doctor probing state that already worked; they looked like bugs but users
couldn't tell without manual digging.

Issue 1 — Server liveness HTTP 401
  /api/health and /api/health/degradation both require the management
  token. Doctor called them without auth → 401 → WARN, even when the
  Next.js server was clearly alive and listening.

  Fix: probe the configured health endpoint first; on 401/403, fall
  back to a publicly served static asset (/favicon.ico) to confirm the
  server is alive. WARN now only fires when both probes fail.

Issue 2 — CLI Tools '@/shared' import
  tool-detector.ts (and 3 other cli-helper files) import @/shared/...
  aliases that resolve via tsconfig.json paths. The CLI ships raw TS
  source (no compile step) and runs through tsx, but tsx does not honor
  tsconfig paths at runtime, and tsconfig-paths only hooks CJS
  Module._resolveFilename while doctor uses ESM `import()`.

  Fix: replace @/shared/... with relative imports in the 4 cli-helper
  files. This is the same pattern these files already use for ./config-
  generator/* imports. No new dependency, no architectural change, and
  the fix doesn't regress Next.js itself which keeps using @/shared.

Verified on v3.8.43 (Node v24.17, Windows 11):
  Before: 7 ok, 2 warning(s), 0 failure(s)
  After:  8 ok, N warning(s), 0 failure(s)
    where N accurately reflects which CLI tools are installed and
    configured for OmniRoute (e.g. Hermes Agent installed but not
    pointed at 20128 → 2 real warnings, not 1 false-positive).

Refs #6162

* fix(doctor): derive fallback URL from primary URL via new URL()

Per Gemini code-assist review feedback: the previous fallback constructed
the /favicon.ico URL from defaults (127.0.0.1:PORT) which ignored custom
host/port/protocol configurations supplied via:
  - OMNIROUTE_DOCTOR_LIVENESS_URL
  - OMNIROUTE_DOCTOR_HOST
  - --liveness-url / --host CLI flags

Parse the primary URL with new URL() to preserve protocol, host, port, and
subpaths. The previous default-based fallback remains as a catch-all for
invalid primary URLs.

* test(doctor): add regression tests for #6162 fixes

Two new test files lock the fix and satisfy the PR Test Policy gate
("production code change without tests"):

- tests/unit/cli-helper-tool-detector-paths-6162.test.ts
    Locks the @/shared → relative imports fix across all 4 cli-helper
    files. Asserts (a) no @/shared alias remains in the cli-helper
    sources, and (b) each file is importable at runtime via tsx/ESM,
    which would have thrown "Cannot find package '@/shared'" before
    the fix.

- tests/unit/cli-doctor-liveness-fallback-6162.test.ts
    Locks the /favicon.ico fallback in doctor.mjs. Asserts the
    fallback probe exists, derives its URL from the primary URL via
    new URL() (per Gemini review feedback), and that the buggy
    'Server responded with HTTP 401' WARN path is gone.

Both tests use only node:test + node:assert/strict so they slot into
the existing 'test' and 'test:unit' scripts with no extra config.

* test(doctor): fix primary.ok regex in fallback test

The earlier regex /primary\.ok\s*\?/ required a '?' immediately after,
but the actual doctor.mjs code uses a multi-line if-block:

  if (primary.ok) {
    return ok(...);
  }

Use /\bprimary\.ok\b/ instead so the assertion matches the existing
branching.

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>

* fix(doubao-web): switch provider to Dola global (#6235)

* fix(doubao-web): switch provider to Dola global

* fix(doubao-web): use .dola.com cookie domain for s_v_web_id + rebaseline test

The Dola switch left s_v_web_id with a host-only "www.dola.com" domain, which fails
the token-source contract (domain must start with "." or "http") — the sibling
sessionid/ttwid cookies and the canonical cookieDomain already use ".dola.com", which
also matches www.dola.com. Also rebaselines web-cookie-providers-new.test.ts (850->890)
for the provider-switch regression cases.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>

* fix(providers): register zed in OAuth PROVIDERS to fix Unknown provider error (#6041) (#6078)

Registers a minimal import_token entry for the existing Zed IDE keychain-import
provider so getProvider("zed") no longer throws "Unknown provider: zed" when the
UI probes the OAuth capability endpoint; generateAuthData returns { supported: false }.

Test runner fix: the regression test imported from "vitest" but lives in tests/unit/
(node:test territory, outside the vitest include globs) — it ran in no runner. Converted
to node:test + node:assert so it actually executes (8/8 green).

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* fix(oauth): align zed in OAUTH_PROVIDER_IDS + config enum after #6078 merge

#6078 registered zed in the OAuth PROVIDERS registry but did not add it to the
constants PROVIDERS id map nor the oauth-providers-config enumeration test, leaving
that test red on the release tip (getProvider enumeration vs EXPECTED mismatch).
Adds ZED to the id constants + zed to EXPECTED_PROVIDER_KEYS/EXPECTED_CONFIG_BY_PROVIDER.

* fix(mitm): strip colons from macOS cert fingerprint before keychain match (#6134) (#6204)

fix(mitm): strip colons from macOS cert fingerprint before keychain match (#6134). Extracted testable macCertOutputHasFingerprint helper + regression guard. Thanks @rianonehub. Integrated into release/v3.8.45.

* docs(architecture): sync stale DB-layer counts (45+/55 → 95+/110+) in REPOSITORY_MAP, db-schema diagram and llm.txt (+42 i18n mirrors) (#6167)

docs(architecture): sync stale DB-layer counts (45+/55 → 95+/110+) across REPOSITORY_MAP, db-schema diagram, llm.txt + 42 i18n mirrors (#6167). Docs-only; check:docs-all passes locally on the reconstruction. Reds are pre-existing base-red drift on release/v3.8.45 (dast-smoke #6228; executor-kiro.test.ts eslint anys; changelog/package.json version drift) — none introduced by this PR. Integrated into release/v3.8.45.

* fix(api): count tool_use/tool_result/thinking blocks in count_tokens estimate (port from 9router#2337) (#6221)

fix(api): count tool_use/tool_result/thinking blocks in count_tokens estimate (#6221, port from 9router#2337). TDD-covered (6/6); typed the test casts to clear no-new-eslint. Reds are pre-existing base-red drift on release/v3.8.45 (dast-smoke #6228; executor-kiro.test.ts eslint anys; changelog/package.json version drift) — none introduced by this PR. Thanks @luweiCN. Integrated into release/v3.8.45.

* fix(antigravity): strip trailing assistant prefill turn for Vertex Claude models (#6114)

fix(antigravity): strip trailing assistant prefill for Vertex Claude models (#6114). TDD-covered (6/6), merged on TDD strength per owner. Reds are pre-existing base-red drift on release/v3.8.45. Thanks @anki1kr. Integrated into release/v3.8.45.

* fix(security): require management auth for mutable cloud routes (#6233) (#6233)

fix(security): require management auth for mutable cloud routes (#6233). Verified: 3 PR tests + full authz/route-guard suite 241/241 green. Thanks @vittoroliveira-dev. Integrated into release/v3.8.45.

* fix(dashboard): use connection.id (UUID) not connection.provider (category) in onboarding wizard href (issue #6144) (#6166)

refactor(dashboard): extract tested buildProviderDetailsHref helper for onboarding wizard (#6166). Behavioral #6144 fix already on tip via #6145; this lands the tested-helper hardening. Thanks @KooshaPari. Integrated into release/v3.8.45.

* feat(rankings): add 'Configured Only' filter to Free Provider Rankings page (#6245)

feat(rankings): add 'Configured Only' filter to Free Provider Rankings (#6245, closes #6150). 9/9 test green. Thanks @Iammilansoni. Integrated into release/v3.8.45.

* fix(i18n): add 118 missing Italian translations (#6212)

i18n(it): add 118 Italian translations (#6212). Audited net-additive (0 keys dropped, valid JSON). Thanks @serverless83. Integrated into release/v3.8.45.

* test(dashboard): realign #6145 onboarding-href guard to the #6166 helper refactor (#6270)

Realign the #6145 onboarding-href guard to the #6166 helper refactor (buildProviderDetailsHref). Test-only; unblocks the fast-path unit job across the open PR queue. Base-reds only (dast-smoke #6228, docs version-drift, executor-kiro anys). Integrated into release/v3.8.45.

* feat(providers): add Yuanbao (web) cookie-session provider (#6196) (#6256)

feat(providers): add Yuanbao (web) cookie-session provider (#6196). TDD-covered; base-reds only (dast-smoke #6228, docs version-drift, executor-kiro anys — #6145 guard fixed on tip via #6270). Integrated into release/v3.8.45.

* feat(providers): route built-in agentrouter through dynamic CC wire image (#6056) (#6255)

feat(providers): route built-in agentrouter through dynamic CC wire image (#6056). TDD-covered (agentrouter-cc-wire-image.test.ts). Base-reds only. Integrated into release/v3.8.45.

* feat(providers): bulk-add API keys for Cloudflare Workers AI (#6174) (#6254)

feat(providers): bulk-add API keys for Cloudflare Workers AI (#6174). Per-entry providerSpecificData (fixes shared-object reuse); TDD guard bulk-api-key-parser-cloudflare.test.ts. Base-reds only. Integrated into release/v3.8.45. (thanks @muflifadla38)

* feat(dashboard): routing/settings UX clarity — share %, Cloud Sync rename, base-URL override (#6147) (#6253)

feat(dashboard): routing/settings UX clarity (#6147) — effective share %, Cloud Sync→Remote Settings Sync rename, opt-in advanced base-URL override. TDD guard routing-settings-ux-6147.test.ts (6/6). Base-reds only. Integrated into release/v3.8.45.

* feat(combo): add option to disable session stickiness (#6168) (#6252)

feat(combo): add option to disable session stickiness (#6168) — per-combo/global, precedence config→settings→false (preserves #3825). TDD guard combo-disable-session-stickiness.test.ts (8/8). Base-reds only. Integrated into release/v3.8.45. (thanks @RCrushMe)

* feat(docker): OMNIROUTE_NO_SUDO env flag for root-less MITM cert trust (#6122) (#6249)

feat(docker): OMNIROUTE_NO_SUDO env flag for root-less MITM cert trust (#6122). resolveSudoSpawn strips sudo when set; argv-array spawn preserved (Hard Rule #13). TDD guard mitm-systemCommands-no-sudo.test.ts (5/5). Base-reds only. Integrated into release/v3.8.45. (thanks @powellnorma)

* feat(providers): add Requesty as an OpenAI-compatible gateway provider (#6120) (#6250)

feat(providers): add Requesty as an OpenAI-compatible gateway provider (#6120). Fixed the APIKEY_PROVIDERS count guard (167→168) the feature had missed. TDD guard requesty-provider.test.ts (4/4) + providers-constants-split (4/4). Base-reds only. Integrated into release/v3.8.45. (thanks @chirag127)

* fix(providers): remove deprecated MiMo v2 entries (#6248)

chore(providers): remove deprecated MiMo V2 catalog entries (superseded by V2.5); realign provider-catalog tests. Merged — thank you, @backryun! Base-reds only. Integrated into release/v3.8.45.

* fix(github-skills): add missing import, add unit tests, fix settings JSON parse (#6186)

feat(skills): GitHub skill-discovery subsystem — search/score/scan/import agent skills from GitHub, MCP tools (scope-gated) + /api/github-skills route (host-pinned api.github.com, sanitized errors). Merged — thank you, @Moseyuh333! Pre-merge fixes: passed toolDef.scopes so tools gate correctly under scope enforcement, routed the GET error path through sanitizeErrorMessage+500 (Hard Rule #12), and realigned the agent-skills count guards (catalog/routes/generator/mcp) for the new catalog entry. Base-reds only. Integrated into release/v3.8.45.

* Fix/5976 continued (#6216)

fix(combo): 5 streaming-path fixes (#5976) — locked-stream 500, error-frame-only-if-no-content, Gemini MALFORMED_RESPONSE→content_filter failover, correlationId substring, per-model-500 lockout skip + request-logger UI. Merged — thank you, @hartmark! Maintainer follow-up: releaseQualityClone cancels the abandoned quality-check tee branch (per-request memory) + regression test. All 41 combo/streaming quality tests green. Base-reds only. Integrated into release/v3.8.45.

* feat(dashboard): filter Free Provider Rankings by configured/available (#6150) (#6251)

feat(dashboard): configured-only / available-only filters on Free Provider Rankings (#6150) — server-side query params + tested lib helper; supersedes the client-side #6245 toggle with an available-only dimension. Lib logic 11/11 green; UI validated live on VPS. Base-reds only. Integrated into release/v3.8.45.

* ci: unblock test jobs from the Build gate (start at minute 0) (#6275)

test-unit x8, test-vitest, test-integration x2 and test-security all had
needs: build but never download the next-build artifact — the dependency
only serialized ~20min of Build wall-clock in front of every test run.
Switch them to needs: changes with the same skip condition Build uses
(docs-only PRs and drafts still skip), so the test chain
(test-unit -> test-coverage -> quality-gate -> sonarqube) now runs in
parallel with Build instead of after it.

Jobs that genuinely consume the artifact keep needs: build unchanged:
test-e2e x9, package-artifact, electron-package-smoke.

Expected effect on a full ci.yml run: critical path drops from
build + tests (~30min+) to max(build, tests) — roughly 15-20min saved
per run, no extra runner minutes beyond starting the same jobs earlier.

* ci(build): switch Next.js production build to Turbopack (1.9x faster) (#6273)

Next 16 ships Turbopack as the stable production bundler. Benchmarked on a
32-core box against the same tree: webpack 1035s -> Turbopack 539s (1.92x).
The build script already supports the switch via OMNIROUTE_USE_TURBOPACK=1
(scripts/build/build-next-isolated.mjs) and next.config.mjs already mirrors
the resolveAlias stubs in the turbopack block, so this only flips the CI env.

The webpack .build/next/cache actions/cache step is removed in the same
commit: Turbopack does not use the webpack cache dir (its persistent FS
cache is experimental and NOT enabled), so restoring ~0.5 GB per run would
be pure wasted download. Revert restores webpack + its cache together.

Validation: standalone output smoke-tested (server.js boots, health 200,
dashboard 307); the 428 build warnings are the known benign 'overly broad
file pattern' static-analysis notices for dynamic fs usage (covered at
runtime by outputFileTracingIncludes). Downstream e2e x9, package-artifact
and electron-package-smoke consume this artifact, so a green CI run here
validates the Turbopack artifact end-to-end. nightly-compat and npm-publish
stay on webpack until this PR proves out.

* feat(build): make Turbopack the default bundler for dev and build (#6283)

Turbopack (stable in Next 16) becomes the code default in the three entry
points that previously required an explicit OMNIROUTE_USE_TURBOPACK=1:

- scripts/build/build-next-isolated.mjs (production build)
- scripts/dev/run-next.mjs (dev server)
- scripts/dev/run-next-playwright.mjs (playwright dev runner)

OMNIROUTE_USE_TURBOPACK=0 remains the webpack escape hatch (Windows /
native-binding / bundler-compat issues), and only the documented '0'
opts out — junk values keep the default.

Benchmarked on this codebase (same tree, Next 16.2.9): webpack 1035s vs
Turbopack 539s on a 32-core box; ~20min vs 6min59s on ubuntu-latest.
Artifact validated end-to-end (standalone smoke + e2e/package-artifact/
electron-package-smoke CI jobs, Docker amd64+arm64 builds clean with the
v3.8.27 ImportTracer panic gone on 16.2.9).

TDD: tests/unit/build-bundler-default-turbopack.test.ts (new) +
run-next-playwright.test.ts extended with the unset-env default case;
both red before the flip, green after. ENVIRONMENT.md updated.

* feat(docker): build the image with Turbopack (v3.8.27 panic gone on Next 16.2.9) (#6285)

Flips the builder stage to OMNIROUTE_USE_TURBOPACK=1 and rewrites the stale
panic comment: the v3.8.27-era TurbopackInternalError ('entered unreachable
code: there must be a path to a root' in ImportTracer::get_traces) no longer
reproduces on Next 16.2.9.

Validation (2026-07-05, this exact Dockerfile, default
OMNIROUTE_BUILD_MEMORY_MB=4096, no overrides):
- amd64: docker build 659s, exit 0, zero panic/OOM strings in the full log,
  container smoke-tested (/api/monitoring/health 200)
- arm64 (qemu): exit 0, zero panic strings

Webpack stays available as the escape hatch:
--build-arg / -e OMNIROUTE_USE_TURBOPACK=0. The V8 heap ceiling is kept:
Turbopack's compile is native Rust, but prerender/export still runs on V8.

* ci: opt-in self-hosted VPS runners for the release window (anti-queue) (#6284)

Adds the on-demand self-hosted runner plumbing for /generate-release:

- scripts/vps/release-runner-up.sh: starts the runner VM on Proxmox, waits
  for >=1 'omni-release' runner to report online via the GitHub API, then
  flips the USE_VPS_RUNNER repo variable to true. Any failure/timeout sets
  it back to false and exits 1 so the caller falls back to hosted runners.
- scripts/vps/release-runner-down.sh: flips USE_VPS_RUNNER=false FIRST
  (so no job gets scheduled onto a dying runner), then gracefully shuts
  the VM down. Idempotent.
- ci.yml: build, test-unit x8 and test-vitest pick their runner
  dynamically. Self-hosted is used ONLY when USE_VPS_RUNNER == 'true'
  AND the event is own-origin (push/dispatch, or a PR whose head repo is
  this repository). Fork PRs and the var's default/absent state always
  fall back to ubuntu-latest.

Why: the Free plan caps hosted concurrency at 20 jobs; a release run
saturates it and queues. The benchmarked VPS (32-core, 4 runners) matches
hosted per-job times (unit ~8.5min/shard, build 9min turbo) but eliminates
the queue, which is the real release bottleneck (~30-50min on a busy day).
Scope is conservative: only the three job families benchmarked on the VPS;
e2e/electron/integration stay hosted (playwright/xvfb provisioning not
validated on the runner workspace yet).

* docs(changelog): restore v3.8.45/v3.8.44 sections eaten by the #6193 merge auto-resolve + CI-perf campaign bullets (#6273 #6275 #6283 #6284 #6285)

* fix(dashboard): null-guard connection in EditConnectionModal base-URL override (#6147) (#6287)

fix(dashboard): null-guard connection in EditConnectionModal (#6147) — fixes 'Cannot read properties of null (reading authType)' crash on every provider-detail page entry. TDD: connModals.test.tsx null-mount 9/9. Base-reds only. Integrated into release/v3.8.45.

* chore(release-green): clear test-masking + docs-all HARD reds for the v3.8.45 pre-flight

- test-masking: allowlist the 4 verified-legitimate assert reductions of the
  cycle (#6248 MiMo V2 removal, #6170 Kiro catalog correction, #6154 Copilot
  catalog refresh) and register the #6164 AutoRoutingBanner test deletion with
  a real replacement guard (tests/unit/home-no-autorouting-banner.test.ts —
  asserts the banner stays out of home/page.tsx and the component stays deleted)
- docs-sync: executors count 68 -> 73 in ARCHITECTURE.md + CODEBASE_DOCUMENTATION.md
- env-doc-sync: document OMNIROUTE_NO_SUDO (#6249/#6122) in .env.example +
  docs/reference/ENVIRONMENT.md

* fix(quality): clear the cycle's 11 net-new ESLint errors + make validate-release-green suppressions-aware

- executor-kiro/save-call-log/call-logs-correlation tests: replace 15 'as any'
  casts with typed shapes (net-new no-explicit-any errors from #6213/#6216);
  prune the now-empty suppression entries so the frozen baseline stays exact
- github-skills + usage/call-logs routes: raw toLowerCase().includes() search
  replaced by matchesSearch() (no-restricted-syntax — Turkish-safe search,
  behavior covered by tests/unit/call-logs-correlation-substring.test.ts and
  tests/unit/github-collector.test.ts)
- validate-release-green.mjs: run ESLint with --suppressions-location (match
  the npm run lint contract — frozen debt is not a release red) and raise the
  lint timeout 15->30min (a full pass takes ~14min alone; the 15min ceiling
  expired under concurrent suite load and surfaced as 'could not parse eslint
  json')

* fix(skills): generate the missing omni-github-skills registry entry + align catalog count tests

PR #6186 added omni-github-skills to the agent-skills catalog (API 22 -> 23)
but did not run the generator, so skills/omni-github-skills/SKILL.md never
existed and 6 integration assertions split between the old (42/43) and new
counts. Generated via scripts/skills/generate-agent-skills.mjs --apply and
aligned agent-skills-discovery to the real totals (43 = 23 API + 20 CLI;
handlers return 44 with config-codex-cli). 30/30 discovery+content tests green.

* fix(combo): restrict the #6216 empty-stream failover to truly empty bodies (restores #3399/#3685 contracts)

The 'streaming no recognized content' branch added by #6216 marked ANY
stream that ended without content deltas as invalid — sweeping in two
regression-guarded pass-through contracts: an empty stream terminated by an
explicit 'data: [DONE]' (#3399 context-cache protection) and an incomplete
Claude lifecycle (ping only, no message_start; #3685 — stream-readiness
timeout territory, not failover). Both unit guards were red on the branch
and green on main (86/86 vs 84/86).

The branch now fires only for a truly EMPTY body (zero bytes — the Gemini
HTTP-200-empty case that motivated #6216), tracked via sawAnyBytes. New
guard: '#5976 truly EMPTY streaming body (zero bytes) -> invalid for combo
failover'. 87/87 across both files.

Also in this pre-flight batch:
- agentSkillTools-mcp: api.have upper bound 22 -> 23 (the #6186 catalog
  addition updated the totals but missed this bound)
- delete tests/unit/free-provider-rankings-configured-filter.test.ts:
  #6251 (server-side configuredOnly/availableOnly) superseded the #6245
  client-side toggle it pinned; replacement declared in the test-masking
  allowlist (tests/unit/freeProviderRankings-filters.test.ts, 11/11)

* chore(quality): prune stale ESLint suppressions (4,273 -> 4,233)

Entries whose violations no longer exist (cleaned by cycle merges and the
pre-flight fixes) made 'npm run lint' exit 2 with 'suppressions left that do
not occur anymore'. Regenerated via --prune-suppressions; net-new policy
unchanged.

* fix(proxy): #6246 stop the v3.8.44 proxy IP-leak + over-deactivation regression (#6296)

Merged into release/v3.8.45. Reds pré-existentes classificados: dast-smoke (infra), check:file-size (drift de baseline de god-files congelados), e 3 testes de contagem agentSkills stale (43→44 já corrigidos no tip pelo #6186 — somem no squash sobre o tip). Núcleo do fix #6246 (proxy IP-leak + over-deactivation).

* fix(proxy): make "Test All" read-only + add bulk enable/disable (#6246) (#6299)

Merged into release/v3.8.45. Delta do par #6246 (Test-All read-only + bulk enable/disable), reconciliado com o núcleo #6296 já mergeado. CHANGELOG restaurado (ambos bullets do #6246 coexistem). Reds pré-existentes: dast-smoke (infra) + file-size drift.

* fix(resilience): evict sticky affinity on pinned-account failover (#6219) (#6231)

Merged into release/v3.8.45. Sticky affinity failover (#6219). Sincronizado com tip; CHANGELOG restaurado (base bullets preservados, net +1). Reds pré-existentes: dast-smoke + file-size drift.

* fix(sse): drop commentary-phase text in Responses passthrough (#6199) (#6232)

Merged into release/v3.8.45. Responses commentary-phase filter (#6199). Sincronizado; CHANGELOG restaurado net +1. Reds: dast-smoke + file-size drift.

* fix: bug-fix sweep — log path, AgentBridge DNS, opencode-go headers, GitLab Duo tools, M365 EDU (#6197 #6127 #6198 #5997 #6220 #6210) (#6234)

Merged into release/v3.8.45. Bug-fix sweep (#6197 log path, #6127/#6198 AgentBridge DNS, #6210 M365 EDU, #6220 GitLab Duo tools, #5997 opencode-go headers). 27 testes verdes. CHANGELOG restaurado net +5. Reds: dast-smoke + file-size drift.

* fix(docker): add id= to BuildKit cache mounts for strict builders (#6291)

Merged into release/v3.8.45. Dockerfile-only: explicit id= on BuildKit cache mounts (fixes strict-frontend parse error). Reds pré-existentes (dast-smoke/file-size drift) não relacionados a mudança de Dockerfile. Thanks @karimalsalah.

* fix(sse): strip zero-width markers from streamed tool-call arguments (follow-up to #5857) (#6292)

Merged into release/v3.8.45. Strip zero-width markers from streamed tool-call arguments (#5857 follow-up). Test 50/50. CHANGELOG reconciled net +1. Reds pré-existentes (dast-smoke/file-size drift). Thanks @DKotsyuba.

* ci(quality): merge-integrity fast-gates + pre-flight hermetic mode (#6300)

Merged into release/v3.8.45. CI merge-integrity fast-gates (changelog-integrity + agent-skills-sync) + pre-flight hermetic mode. O gate novo detectou 11 SKILL.md gerados fora de sync (drift pré-existente no tip: omni-api-keys endpoints, omni-github-skills do #6186) — regenerados neste PR, Merge-integrity GREEN no CI. Reds restantes base-red (dast-smoke/file-size drift/live-data flaky). Testes novos 17/17.

* fix(a2a): finish the #6186 catalog-count update — 3 hardcoded 22s left in production

#6186 added omni-github-skills (API 22 -> 23) and updated computeCoverage's
total, but left the old count hardcoded in the A2A layer: listCapabilities
metadata reported coverage.api.total 22 (type literal + value) and
SkillCoverageSchema pinned z.literal(22) — so the schema would REJECT the
correct runtime value. Aligned all three to 23 + the unit fixtures
(listCapabilities-a2a, agentSkills-schemas, 46/46 with agentSkillTools-mcp).

* fix(quality): type the 7 net-new 'as any' casts from #6292 (Lint red on the release tip)

#6292 rewrote/added zero-width-marker tests with 7 new 'as any' result casts,
pushing the file past its frozen suppression (84) — and when violations
exceed the suppressed count ESLint reports ALL of them, so the ci.yml Lint
job went red with 90 errors. The 7 new sites get typed shapes (delta /
arguments / choices / output accessors — same pattern as fecf888fd); the
pre-existing debt stays frozen at the exact new count (83). 50/50 tests
green, file lint-clean under the suppressions baseline.

* fix(api): Zod-validate POST /api/github-skills + document new gate envs + pin merge-integrity actions

Three latent heavy-CI reds surfaced by the VPS validation dispatch (the fast
path never runs these gates):

- t06 route-validation: POST /api/github-skills destructured request.json()
  blind — a non-array 'targets' would .map-crash. Now validateBody(zod)
  with defaults preserved (Hard Rule #7). Guard:
  tests/unit/github-skills-route-validation.test.ts (4/4).
- env-doc-sync: document OMNIROUTE_SKIP_SYSTEM_TRUST (#6310) and the
  changelog-integrity gate envs CHANGELOG_BASE_REF/ALLOW_CHANGELOG_REMOVALS
  (#6300) in .env.example + ENVIRONMENT.md.
- zizmor ratchet: the new merge-integrity job's checkout/setup-node uses were
  unpinned (+1 finding, 160 > baseline 159); pinned by SHA -> 158 (< baseline).

* fix(quality): clear the 2 remaining heavy-gate reds on the release tip

- check:error-helper: githubSkillTools.ts (#6186 wave) built MCP install
  error results with raw err.message — routed through sanitizeErrorMessage()
  (Hard Rule #12; 19/19 agentSkillTools tests green)
- check:mutation-test-coverage: tests/unit/combo-provider-cooldown-sibling.test.ts
  (#6216) was missing from stryker.conf tap.testFiles — added so its mutant
  kills count on nightly-mutation

* fix(security): 405 method-first for /api/keys/{id}/devices (dast-smoke QUERY check)

Schemathesis's newer unsupported-methods check (unpinned tool drift) sends
QUERY /api/keys/{id}/devices and demands 405 Method Not Allowed; the path had
no HIGH_RISK_METHOD_RULES entry, so the auth layer answered 401 first. Add
the devices rule (GET-only) so undocumented methods get a clean method-first
405 — same pattern as the v3.8.44 TRACE fix. TDD:
tests/unit/dast-method-not-allowed.test.ts gains the devices QUERY case (4/4).

* fix(mitm): test suite and CI must never mutate the OS trust store (OMNIROUTE_SKIP_SYSTEM_TRUST) (#6310)

Incident 2026-07-05 on the self-hosted release runner (VM 113): the
integration test 'POST /cert: installs trust when cert exists' exercised the
REAL install path, wrote a 105-byte fake PEM (FakeMITMCertForTestingOnly)
into /usr/local/share/ca-certificates and update-ca-certificates baked the
invalid entry into ca-certificates.crt — breaking ALL system TLS on the VM
(curl error 77, apt cert failures, and the intermittent gzip-corrupted
next-build artifacts that failed 6/9 e2e shards in run 28754447912). Hosted
runners are ephemeral, so the same mutation went unnoticed for months.

- installCert/uninstallCert: skip the OS dispatch under
  OMNIROUTE_SKIP_SYSTEM_TRUST=1 — AFTER the input checks, so the #4546
  environment-skip contract (missing file throws -> structured skip) and the
  already-installed/not-installed early returns are preserved.
- installTproxyCa/uninstallTproxyCa: same guard, only when no run dep is
  injected (DI'd tests keep exercising the full command sequence with mocks).
- tests/_setup/isolateDataDir.ts sets the env for every node:test process;
  ci.yml/quality.yml/nightly-release-green.yml set it workflow-wide (e2e runs
  the real app outside the test setup).

TDD: tests/unit/system-trust-test-guard.test.ts (guard exported to every test
process; guarded install resolves on a real file without touching the OS;
missing-file contract preserved). 82/82 across the affected cert/tproxy/
agent-bridge suites.

* ci(vps): hermetic nightly pre-flight on the release runner (descoped: e2e/integration/electron stay hosted) (#6305)

* ci(vps): extend the dynamic omni-release runner to e2e/integration/electron + nightly pre-flight

Completes the #6284 rollout to the jobs where the VPS pays the most:
- test-e2e (9 shards, 15-20min/shard hosted — dominated by setup, not tests),
  test-integration (2 shards) and electron-package-smoke now pick the
  self-hosted omni-release runner under the same gate: vars.USE_VPS_RUNNER
  == 'true' AND own-origin (fork PRs never reach self-hosted).
- nightly-release-green (the release pre-flight) becomes runner-dynamic too:
  on the VPS it runs in a clean env — no operator OMNIROUTE_API_KEY, no
  local noauth CLIs — eliminating the machine-specific false positives that
  dominated the 2026-07-05 pre-flight; passes --hermetic (no-op until the
  #6300 validator lands, then belt-and-suspenders).

Validation plan (per operator request): release-runner-up.sh -> full ci.yml
workflow_dispatch on this branch exercising e2e/integration/electron on the
VPS (proves playwright --with-deps + xvfb on the runner) -> down.sh -> VM
off verified.

* fix(mitm): test suite and CI must never mutate the OS trust store (OMNIROUTE_SKIP_SYSTEM_TRUST)

Incident 2026-07-05 on the self-hosted release runner (VM 113): the
integration test 'POST /cert: installs trust when cert exists' exercised the
REAL install path, wrote a 105-byte fake PEM (FakeMITMCertForTestingOnly)
into /usr/local/share/ca-certificates and update-ca-certificates baked the
invalid entry into ca-certificates.crt — breaking ALL system TLS on the VM
(curl error 77, apt cert failures, and the intermittent gzip-corrupted
next-build artifacts that failed 6/9 e2e shards in run 28754447912). Hosted
runners are ephemeral, so the same mutation went unnoticed for months.

- installCert/uninstallCert: skip the OS dispatch under
  OMNIROUTE_SKIP_SYSTEM_TRUST=1 — AFTER the input checks, so the #4546
  environment-skip contract (missing file throws -> structured skip) and the
  already-installed/not-installed early returns are preserved.
- installTproxyCa/uninstallTproxyCa: same guard, only when no run dep is
  injected (DI'd tests keep exercising the full command sequence with mocks).
- tests/_setup/isolateDataDir.ts sets the env for every node:test process;
  ci.yml/quality.yml/nightly-release-green.yml set it workflow-wide (e2e runs
  the real app outside the test setup).

TDD: tests/unit/system-trust-test-guard.test.ts (guard exported to every test
process; guarded install resolves on a real file without touching the OS;
missing-file contract preserved). 82/82 across the affected cert/tproxy/
agent-bridge suites.

* ci(vps): descope — e2e/integration/electron stay on hosted runners; keep the hermetic nightly pre-flight dynamic

Validation verdict (runs 28754447912 + 28757670732, VM 113):
- e2e cannot run >1 per VM: both jobs bind port 20128 ('already used') — needs
  a per-job port in the playwright runner before any VM rollout.
- concurrent ~1GB artifact downloads truncate on the VM uplink (gzip 'invalid
  compressed data' — with 2 runners the e2e shard passed; corruption returned
  at 4) — actions/download-artifact has no integrity retry here.
- integration shard 2 exceeded its 15-min timeout twice on the VM.
The VPS remains a win for whole-machine jobs: build/unit/vitest (already
dynamic via #6284) and nightly-release-green (single job, clean env, hermetic
pre-flight) — which this PR keeps.

* chore(quality): v3.8.45 cycle-close file-size rebaseline (Phase 0 drift absorption)

13 files grown by the cycle's merged PRs (#6216 streaming+request-logger UI;
#6251/#6253 dashboard UX) — legitimate merged-feature growth absorbed by the
release captain per the Phase 0 drift policy; all entries stay frozen (cannot
grow further). Justification key: _rebaseline_2026_07_06_v3845_release_close.

* chore(quality): v3.8.45 cycle-close cognitive/cyclomatic rebaseline (Phase 0 drift absorption)

cognitive 867->877 (+10), cyclomatic 2028->2035 (+7) — inherited cycle drift
measured by check:release-green (hermetic) on the release tip; the captain's
pre-flight fixes are gate/test/workflow changes (complexity-neutral).
Justification keys: _rebaseline_2026_07_06_v3845_release_close.

* docs(changelog): v3.8.45 reconciliation — fold Unreleased into the version section, 30 missing bullets, contributors hall

Phase 0a reconciliation (/generate-release): every commit since v3.8.44 now
has a bullet or a Maintenance rollup (82 commits; the only ref-less residues
are the bump/recording commits); #6193 bullet gains its PR ref; #6298
diagnosis credited (@subhansh-dev, landed via #6234); [3.8.44] header dated;
'### 🙌 Contributors' table injected (27 external + maintainer) + 42 i18n
mirrors resynced.

* chore(release): v3.8.45 — 2026-07-06

* fix(resilience): 502/503/504 keep the connection-unavailability path — only the exact 500 skips lockout (#5976 contract)

The #6216 branch used 'status >= 500', so a 503 on a per-model-quota /
openai-compatible provider returned cooldownMs 0 — no model lockout AND no
connection cooldown — hot-looping the failing upstream and breaking the
resilience-http-e2e 'priority combo falls back on 503' guard on the release
PR (the request after a 503 hit the same primary again). #6216's OWN unit
contract pins the narrow behavior ('Gemini 503 should NOT skip cooldown',
combo-provider-cooldown-sibling.test.ts) but computes the condition inline
instead of exercising auth.ts. Code aligned to the tested contract:
status === 500 skips (intermittent, not model-specific); 502/503/504 keep
the pre-#6216 model-lockout path. Validated: the failing integration test
flips red -> green locally (19s, deterministic before).

* fix(security): crypto-backed randomNumericId in doubao-web (CodeQL js/insecure-randomness)

The synthetic Dola device/web id was built from Math.random; CodeQL flags it
as insecure randomness in a security context. Not a secret, but
crypto.getRandomValues costs the same and closes alert #692 at the source.
Doubao executor tests 14/14 green. Alerts #693-695 (incomplete-url-substring
in unit-test asserts) dismissed as false positives per the v3.8.35 precedent
(Hard Rule #14).

* chore(quality): shave the #5976 fix comment back under the auth.ts file-size freeze (2447)

---------

Co-authored-by: Danny S <36470572+kanztu@users.noreply.github.com>
Co-authored-by: Luis Alejandro Vega <LuisAlejandroVega@redesprivadasvirtuales.com>
Co-authored-by: Milan Soni <123074437+Iammilansoni@users.noreply.github.com>
Co-authored-by: R. Beltran <rbeltran8000@gmail.com>
Co-authored-by: VXNCXNX <93332837+VXNCXNX@users.noreply.github.com>
Co-authored-by: Denis Kotsyuba <kocubads96@gmail.com>
Co-authored-by: DKotsyuba <16292493+DKotsyuba@users.noreply.github.com>
Co-authored-by: backryun <bakryun0718@proton.me>
Co-authored-by: Aris <arissunandar399@gmail.com>
Co-authored-by: Ankit <177378174+anki1kr@users.noreply.github.com>
Co-authored-by: Rian Priskanova <rian@evercore.technology>
Co-authored-by: Vittor Guilherme Borges de Oliveira <vittoroliveira.dev@gmail.com>
Co-authored-by: KooshaPari <42529354+KooshaPari@users.noreply.github.com>
Co-authored-by: serverless83 <35410475+serverless83@users.noreply.github.com>
Co-authored-by: Moseyuh333 <148680980+Moseyuh333@users.noreply.github.com>
Co-authored-by: Markus Hartung <mail@hartmark.se>
Co-authored-by: HenryHaniHannoush <karimmalsalah@gmail.com>
2026-07-06 02:25:17 -03:00

7172 lines
200 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
openapi: 3.1.0
info:
title: OmniRoute API
version: 3.8.45
description: |
OmniRoute is a local-first AI API proxy router. It provides an OpenAI-compatible
endpoint that routes requests to multiple AI providers with load balancing,
failover, and usage tracking.
## Base URLs
- **Local**: `http://localhost:20128`
## Authentication
All proxy endpoints require a Bearer token (API key managed via the dashboard).
Management endpoints are protected when `requireLogin` is enabled.
contact:
name: OmniRoute
license:
name: MIT
servers:
- url: http://localhost:20128
description: Local development
tags:
- name: Playground
description: Playground Studio — preset management and prompt improvement
- name: Memory
description: Conversational memory management — CRUD, engine status, playground preview, summarization, reindex, and Qdrant settings (plan 21 — v3.8.6). All routes require management auth.
- name: Chat
description: OpenAI-compatible chat completions
- name: Messages
description: Anthropic-compatible messages
- name: Responses
description: OpenAI Responses API
- name: Embeddings
description: Text embedding generation
- name: Images
description: Image generation
- name: Audio
description: Audio speech and transcription
- name: Moderations
description: Content moderation
- name: Rerank
description: Document reranking
- name: Models
description: Available model listing
- name: Providers
description: Provider connection management
- name: Provider Nodes
description: Provider node configuration
- name: API Keys
description: API key management
- name: Combos
description: Routing combo management
- name: Settings
description: Application settings
- name: Compression
description: Prompt compression, RTK filters, Caveman rules, and compression combos
- name: Usage
description: Usage analytics and logs
- name: Translator
description: Format translation debug & testing
- name: CLI Tools
description: CLI tool configuration management
- name: Embedded Services
description: >-
Install, start, stop, and monitor locally-running embedded services (9Router, CLIProxyAPI).
All routes are LOCAL_ONLY — accessible from loopback only (hard rule #17).
- name: OAuth
description: OAuth flows for provider authentication
- name: System
description: System management (restart, shutdown, backup)
- name: Pricing
description: Model pricing configuration
- name: Cloud
description: Cloud worker authentication and sync
- name: Fallback
description: Fallback chain management
- name: Telemetry
description: Telemetry and token health monitoring
- name: Agent Skills
description: >-
Agent Skills catalog — 42 SKILL.md files (22 REST API + 20 CLI) for external agents,
MCP clients, and A2A orchestrators to discover OmniRoute capabilities.
- name: AgentBridge
description: >-
MITM proxy manager for 9 IDE agents (Antigravity, Kiro, Copilot, Codex, Cursor, Zed,
Claude Code, Open Code, Trae). Controls server lifecycle, DNS/model mappings, bypass list,
and cert management. All routes are LOCAL_ONLY + SPAWN_CAPABLE (hard rules #15, #17).
See docs/frameworks/AGENTBRIDGE.md.
- name: Traffic Inspector
description: >-
LLM-aware HTTPS traffic debugger with 4 capture modes (AgentBridge, Custom Hosts,
HTTP_PROXY :8080, System-wide). Provides real-time WebSocket stream, session recording,
HAR export, SSE merge, and conversation normalization.
All routes are LOCAL_ONLY + SPAWN_CAPABLE (hard rules #15, #17).
See docs/frameworks/TRAFFIC_INSPECTOR.md.
paths:
# --- Playground + Search Tools (plans 17+18) ---
/api/playground/improve-prompt:
post:
tags:
- Playground
summary: Improve prompt via LLM
description: |
Rewrites the supplied system prompt and/or user prompt using a meta-prompt
(inspired by Anthropic Console Prompt Improver). Internally calls
`/v1/chat/completions` with the model specified in the request body.
Quota is consumed from the caller's account.
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- model
properties:
system:
type: string
maxLength: 50000
description: System prompt to improve (at least one of system/prompt required)
prompt:
type: string
maxLength: 50000
description: User prompt to improve
model:
type: string
description: Model to use for the improvement call (e.g. openai/gpt-4o)
tone:
type: string
enum:
- concise
- detailed
default: concise
responses:
"200":
description: Improved prompt(s)
content:
application/json:
schema:
type: object
properties:
improvedSystem:
type: string
improvedPrompt:
type: string
tokensIn:
type: integer
tokensOut:
type: integer
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
/api/playground/presets:
get:
tags:
- Playground
summary: List playground presets
description: Returns all saved playground presets ordered by creation date (newest first).
security:
- BearerAuth: []
responses:
"200":
description: Preset list
content:
application/json:
schema:
type: object
properties:
presets:
type: array
items:
$ref: "#/components/schemas/PlaygroundPreset"
"401":
$ref: "#/components/responses/Unauthorized"
post:
tags:
- Playground
summary: Create playground preset
description: Saves the current playground configuration as a named preset in the database.
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/PlaygroundPresetCreate"
responses:
"201":
description: Created preset
content:
application/json:
schema:
$ref: "#/components/schemas/PlaygroundPreset"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
/api/playground/presets/{id}:
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
get:
tags:
- Playground
summary: Get playground preset
security:
- BearerAuth: []
responses:
"200":
description: Preset found
content:
application/json:
schema:
$ref: "#/components/schemas/PlaygroundPreset"
"401":
$ref: "#/components/responses/Unauthorized"
"404":
description: Preset not found
put:
tags:
- Playground
summary: Update playground preset
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/PlaygroundPresetCreate"
responses:
"200":
description: Updated preset
content:
application/json:
schema:
$ref: "#/components/schemas/PlaygroundPreset"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"404":
description: Preset not found
delete:
tags:
- Playground
summary: Delete playground preset
security:
- BearerAuth: []
responses:
"204":
description: Deleted
"401":
$ref: "#/components/responses/Unauthorized"
"404":
description: Preset not found
# --- Memory Engine (plan 21) ---
/api/memory:
get:
tags:
- Memory
summary: List memory entries
security:
- ManagementSessionAuth: []
parameters:
- name: apiKeyId
in: query
schema:
type: string
- name: type
in: query
schema:
type: string
enum:
- factual
- episodic
- procedural
- semantic
- name: sessionId
in: query
schema:
type: string
- name: q
in: query
schema:
type: string
- name: limit
in: query
schema:
type: integer
minimum: 1
maximum: 200
default: 50
- name: page
in: query
schema:
type: integer
minimum: 1
default: 1
- name: offset
in: query
schema:
type: integer
minimum: 0
responses:
"200":
description: Paginated list of memories with stats
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: "#/components/schemas/MemoryEntry"
total:
type: integer
totalPages:
type: integer
stats:
type: object
properties:
total:
type: integer
tokensUsed:
type: integer
hitRate:
type: number
cacheStats:
type: object
properties:
hits:
type: integer
misses:
type: integer
"401":
$ref: "#/components/responses/Unauthorized"
post:
tags:
- Memory
summary: Create a memory entry
security:
- ManagementSessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- content
- key
properties:
content:
type: string
minLength: 1
key:
type: string
minLength: 1
type:
type: string
enum:
- factual
- episodic
- procedural
- semantic
default: factual
sessionId:
type: string
nullable: true
apiKeyId:
type: string
metadata:
type: object
additionalProperties: true
expiresAt:
type: string
format: date-time
nullable: true
responses:
"201":
description: Created memory entry
content:
application/json:
schema:
$ref: "#/components/schemas/MemoryEntry"
"400":
description: Validation error
"401":
$ref: "#/components/responses/Unauthorized"
/api/memory/{id}:
parameters:
- name: id
in: path
required: true
schema:
type: string
description: Memory UUID
get:
tags:
- Memory
summary: Get a single memory entry
security:
- ManagementSessionAuth: []
responses:
"200":
description: Memory entry
content:
application/json:
schema:
$ref: "#/components/schemas/MemoryEntry"
"401":
$ref: "#/components/responses/Unauthorized"
"404":
description: Memory not found
put:
tags:
- Memory
summary: Update a memory entry
description: Update `type`, `key`, `content`, and/or `metadata` of an existing memory. If an embedding source is available, the vector in `vec_memories` is also regenerated. Corresponds to `MemoryUpdatePutSchema`.
security:
- ManagementSessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
type:
type: string
enum:
- factual
- episodic
- procedural
- semantic
key:
type: string
minLength: 1
content:
type: string
minLength: 1
metadata:
type: object
additionalProperties: true
additionalProperties: false
responses:
"200":
description: Updated memory entry
content:
application/json:
schema:
$ref: "#/components/schemas/MemoryEntry"
"400":
description: Validation error
"401":
$ref: "#/components/responses/Unauthorized"
"404":
description: Memory not found
delete:
tags:
- Memory
summary: Delete a memory entry
description: Deletes the SQLite row, removes the vector from `vec_memories`, and best-effort deletes the point from Qdrant.
security:
- ManagementSessionAuth: []
responses:
"200":
description: Deleted
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
"401":
$ref: "#/components/responses/Unauthorized"
"404":
description: Memory not found
/api/memory/health:
get:
tags:
- Memory
summary: Memory store health check
description: Round-trip create→list→delete to verify the store is alive.
security:
- ManagementSessionAuth: []
responses:
"200":
description: Health result
content:
application/json:
schema:
type: object
properties:
working:
type: boolean
latencyMs:
type: number
error:
type: string
nullable: true
"401":
$ref: "#/components/responses/Unauthorized"
/api/memory/retrieve-preview:
post:
tags:
- Memory
summary: Dry-run memory retrieval (Playground)
description: Simulates `retrieveMemories()` for a given query and returns the ranked results with score, tier, and token count. Does NOT modify any memory. Corresponds to `RetrievePreviewSchema`.
security:
- ManagementSessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- query
properties:
query:
type: string
minLength: 1
strategy:
type: string
enum:
- exact
- semantic
- hybrid
default: hybrid
maxTokens:
type: integer
minimum: 1
maximum: 16000
default: 2000
apiKeyId:
type: string
description: Optional — tests global pool when omitted
limit:
type: integer
minimum: 1
maximum: 100
default: 20
additionalProperties: false
responses:
"200":
description: Preview results
content:
application/json:
schema:
type: object
properties:
memories:
type: array
items:
type: object
properties:
id:
type: string
type:
type: string
enum:
- factual
- episodic
- procedural
- semantic
key:
type: string
content:
type: string
score:
type: number
tokens:
type: integer
tier:
type: string
enum:
- fts5
- vector
- hybrid-rrf
- qdrant
vecScore:
type: number
nullable: true
ftsScore:
type: number
nullable: true
resolution:
type: object
properties:
embeddingSource:
type: string
enum:
- remote
- static
- transformers
nullable: true
embeddingModel:
type: string
nullable: true
vectorStore:
type: string
enum:
- sqlite-vec
- qdrant
- none
strategyUsed:
type: string
enum:
- exact
- semantic
- hybrid
rerankApplied:
type: boolean
fallbackReason:
type: string
nullable: true
totalTokensUsed:
type: integer
budgetMaxTokens:
type: integer
"400":
description: Validation error
"401":
$ref: "#/components/responses/Unauthorized"
/api/memory/embedding-providers:
get:
tags:
- Memory
summary: List embedding providers
description: Returns all providers that have embedding-capable models, indicating which have an active API key configured.
security:
- ManagementSessionAuth: []
responses:
"200":
description: Provider list
content:
application/json:
schema:
type: object
properties:
providers:
type: array
items:
type: object
properties:
provider:
type: string
hasKey:
type: boolean
models:
type: array
items:
type: object
properties:
id:
type: string
description: "Format: provider/model"
name:
type: string
dimensions:
type: integer
nullable: true
"401":
$ref: "#/components/responses/Unauthorized"
/api/memory/engine-status:
get:
tags:
- Memory
summary: Memory engine status
description: Returns the full engine status including keyword tier availability, embedding resolution, vector store statistics (sqlite-vec), Qdrant health, and rerank configuration. Corresponds to `MemoryEngineStatusSchema`.
security:
- ManagementSessionAuth: []
responses:
"200":
description: Engine status
content:
application/json:
schema:
type: object
properties:
keyword:
type: object
properties:
available:
type: boolean
backend:
type: string
enum:
- FTS5
embedding:
type: object
properties:
source:
type: string
enum:
- remote
- static
- transformers
nullable: true
model:
type: string
nullable: true
dimensions:
type: integer
nullable: true
available:
type: boolean
reason:
type: string
cacheStats:
type: object
properties:
hits:
type: integer
misses:
type: integer
size:
type: integer
vectorStore:
type: object
properties:
backend:
type: string
enum:
- sqlite-vec
- qdrant
- none
available:
type: boolean
rowCount:
type: integer
needsReindex:
type: integer
reason:
type: string
qdrant:
type: object
properties:
enabled:
type: boolean
healthy:
type: boolean
nullable: true
latencyMs:
type: number
nullable: true
error:
type: string
nullable: true
rerank:
type: object
properties:
enabled:
type: boolean
provider:
type: string
nullable: true
model:
type: string
nullable: true
available:
type: boolean
reason:
type: string
"401":
$ref: "#/components/responses/Unauthorized"
/api/memory/summarize:
post:
tags:
- Memory
summary: Compact old memories
description: "Manually triggers memory compaction for memories older than `olderThanDays`. Use `dryRun: true` to preview candidates. Corresponds to `MemorySummarizeSchema`."
security:
- ManagementSessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
olderThanDays:
type: integer
minimum: 1
maximum: 365
default: 30
apiKeyId:
type: string
description: Optional — compacts all keys when omitted
dryRun:
type: boolean
default: false
additionalProperties: false
responses:
"200":
description: Summarization result
content:
application/json:
schema:
type: object
properties:
candidates:
type: integer
tokensSaved:
type: integer
dryRun:
type: boolean
"400":
description: Validation error
"401":
$ref: "#/components/responses/Unauthorized"
/api/memory/reindex:
post:
tags:
- Memory
summary: Trigger vector reindex
description: "Starts background reindexing of memories with `needs_reindex = 1`. Use `force: true` to regenerate ALL vectors regardless of index status. Corresponds to `MemoryReindexSchema`."
security:
- ManagementSessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
force:
type: boolean
default: false
description: When true, marks all memories needs_reindex=1 before running.
additionalProperties: false
responses:
"200":
description: Reindex started
content:
application/json:
schema:
type: object
properties:
started:
type: boolean
pending:
type: integer
description: Memories still pending after this batch
"400":
description: Validation error
"401":
$ref: "#/components/responses/Unauthorized"
/api/settings/memory:
get:
tags:
- Memory
- Settings
summary: Get memory settings
description: Returns the extended memory settings including 7 new fields added in plan 21 (embeddingSource, embeddingProviderModel, transformersEnabled, staticEnabled, rerankEnabled, rerankProviderModel, vectorStore).
security:
- ManagementSessionAuth: []
responses:
"200":
description: Extended memory settings
content:
application/json:
schema:
$ref: "#/components/schemas/MemorySettingsExtended"
"401":
$ref: "#/components/responses/Unauthorized"
put:
tags:
- Memory
- Settings
summary: Update memory settings
description: "Update any subset of the extended memory settings. All fields are optional; only provided fields are updated. Schema: `MemorySettingsExtendedSchema`."
security:
- ManagementSessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/MemorySettingsExtended"
responses:
"200":
description: Updated memory settings
content:
application/json:
schema:
$ref: "#/components/schemas/MemorySettingsExtended"
"400":
description: Validation error
"401":
$ref: "#/components/responses/Unauthorized"
/api/settings/qdrant:
get:
tags:
- Memory
- Settings
summary: Get Qdrant settings
description: Returns current Qdrant configuration. The `apiKey` field is never returned raw — use `hasApiKey` / `apiKeyMasked` instead.
security:
- ManagementSessionAuth: []
responses:
"200":
description: Qdrant settings
content:
application/json:
schema:
$ref: "#/components/schemas/QdrantSettings"
"401":
$ref: "#/components/responses/Unauthorized"
put:
tags:
- Memory
- Settings
summary: Update Qdrant settings
description: 'Update Qdrant configuration. Pass `apiKey: ""` to remove the stored key. Schema: `QdrantSettingsUpdateSchema`.'
security:
- ManagementSessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
enabled:
type: boolean
host:
type: string
port:
type: integer
minimum: 1
maximum: 65535
collection:
type: string
minLength: 1
embeddingModel:
type: string
minLength: 1
apiKey:
type: string
description: Empty string removes the key
additionalProperties: false
responses:
"200":
description: Updated Qdrant settings
content:
application/json:
schema:
$ref: "#/components/schemas/QdrantSettings"
"400":
description: Validation error
"401":
$ref: "#/components/responses/Unauthorized"
/api/settings/qdrant/health:
get:
tags:
- Memory
summary: Qdrant health probe
description: Performs a liveness check against the configured Qdrant instance. Returns latency and any connection error (sanitized — no stack traces).
security:
- ManagementSessionAuth: []
responses:
"200":
description: Health result
content:
application/json:
schema:
$ref: "#/components/schemas/QdrantHealthResult"
"401":
$ref: "#/components/responses/Unauthorized"
/api/settings/qdrant/search:
post:
tags:
- Memory
summary: Qdrant semantic search test
description: "Performs a test semantic search against the Qdrant collection. Useful for validating that the integration works end-to-end. Schema: `QdrantSearchSchema`."
security:
- ManagementSessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- query
properties:
query:
type: string
minLength: 1
topK:
type: integer
minimum: 1
maximum: 50
default: 5
additionalProperties: false
responses:
"200":
description: Search results
content:
application/json:
schema:
type: object
properties:
results:
type: array
items:
type: object
"400":
description: Validation error
"401":
$ref: "#/components/responses/Unauthorized"
"503":
description: Qdrant unavailable (structured error, no stack trace)
/api/settings/qdrant/cleanup:
post:
tags:
- Memory
summary: Clean up expired Qdrant points
description: Removes Qdrant points for memories that have expired or exceeded the configured retention window.
security:
- ManagementSessionAuth: []
responses:
"200":
description: Cleanup result
content:
application/json:
schema:
type: object
properties:
deleted:
type: integer
checked:
type: integer
"401":
$ref: "#/components/responses/Unauthorized"
"503":
description: Qdrant unavailable (structured error, no stack trace)
/api/settings/qdrant/embedding-models:
get:
tags:
- Memory
summary: List Qdrant embedding models
description: Returns the list of embedding models available for use with Qdrant.
security:
- ManagementSessionAuth: []
responses:
"200":
description: Embedding models list
content:
application/json:
schema:
type: object
properties:
models:
type: array
items:
type: string
"401":
$ref: "#/components/responses/Unauthorized"
# ─── Proxy Endpoints ──────────────────────────────────────────
/api/v1/chat/completions:
post:
tags: [Chat]
summary: Create chat completion
description: OpenAI-compatible chat completions endpoint. Routes to configured providers.
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ChatCompletionRequest"
responses:
"200":
description: Chat completion response (or SSE stream)
headers:
X-OmniRoute-Response-Cost:
schema:
type: string
description: Request cost in USD, fixed 10 decimals (e.g. `0.0001234500`; `0.0000000000` for free/unpriced).
X-OmniRoute-Tokens-In:
schema:
type: string
description: Input (prompt) token count.
X-OmniRoute-Tokens-Out:
schema:
type: string
description: Output (completion) token count.
X-OmniRoute-Model:
schema:
type: string
description: Resolved model.
X-OmniRoute-Provider:
schema:
type: string
description: Resolved provider alias.
X-OmniRoute-Latency-Ms:
schema:
type: string
description: Handler latency in milliseconds.
X-OmniRoute-Cache-Hit:
schema:
type: string
enum: ["true", "false"]
description: Whether the response was served from cache.
X-OmniRoute-Fallback-Attempts:
schema:
type: string
description: Number of fallback attempts (only present when > 0).
X-OmniRoute-Request-Id:
schema:
type: string
description: Request correlation id (present when known).
X-OmniRoute-Version:
schema:
type: string
description: OmniRoute build version (always present).
X-OmniRoute-Cost-Saved:
schema:
type: string
description: >-
On a semantic-cache HIT, the original (would-have-been) cost in USD that
the cache avoided (fixed 10 decimals). Present only on cache hits;
X-OmniRoute-Response-Cost is 0 for the same response (incremental cost).
content:
application/json:
schema:
$ref: "#/components/schemas/ChatCompletionResponse"
text/event-stream:
schema:
type: string
"401":
$ref: "#/components/responses/Unauthorized"
"502":
description: All upstream providers failed
/api/v1/ws:
get:
tags: [Chat]
summary: Chat completion over WebSocket (handshake + upgrade)
description: >-
OpenAI-compatible chat over a WebSocket connection. `GET` with
`?handshake=1` returns the connection descriptor (auth path, message
protocol and live-event channels) as JSON; a plain `GET` without an
Upgrade returns `426 Upgrade Required`. After upgrading, the client
exchanges JSON frames — `{type:"request", id, payload:{model, messages}}`
to start a completion and `{type:"cancel", id}` to abort it. A separate
live channel (default port `LIVE_WS_PORT=20129`, path `/live`) streams
dashboard events on the `requests`, `combo` and `credentials` topics with
a 15s heartbeat. Requires an API key.
security:
- BearerAuth: []
parameters:
- name: handshake
in: query
description: Set to `1` to receive the JSON connection descriptor instead of upgrading.
required: false
schema:
type: string
enum: ["1"]
responses:
"101":
description: WebSocket upgrade successful
"200":
description: Handshake descriptor (auth path, message protocol, live channels)
"401":
description: WebSocket auth required (no credential supplied)
"403":
description: Invalid WebSocket credential
"426":
description: Upgrade Required — connect via WebSocket or use `?handshake=1`
/api/v1/providers/{provider}/chat/completions:
post:
tags: [Chat]
summary: Create chat completion (provider-specific)
description: Routes to a specific provider by name.
security:
- BearerAuth: []
parameters:
- name: provider
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ChatCompletionRequest"
responses:
"200":
description: Chat completion response
"401":
$ref: "#/components/responses/Unauthorized"
/api/v1/api/chat:
post:
tags: [Chat]
summary: Ollama-compatible chat endpoint
description: Provides compatibility with Ollama's /api/chat format.
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Chat response (JSON or streaming)
/api/v1/messages:
post:
tags: [Messages]
summary: Create message (Anthropic-compatible)
description: Anthropic Messages API endpoint. Routes to Claude providers.
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/MessagesRequest"
responses:
"200":
description: >-
Message response (or SSE stream). Non-streaming success responses
carry the `X-OmniRoute-*` cost-telemetry headers (see
`POST /api/v1/chat/completions`), including `X-OmniRoute-Request-Id`
and `X-OmniRoute-Version`.
"401":
$ref: "#/components/responses/Unauthorized"
/api/v1/messages/count_tokens:
post:
tags: [Messages]
summary: Count tokens for a message
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Token count
/api/v1/responses:
post:
tags: [Responses]
summary: Create response (OpenAI Responses API)
description: OpenAI Responses API endpoint.
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: >-
Response object or SSE stream. Non-streaming success responses carry
the `X-OmniRoute-*` cost-telemetry headers (see
`POST /api/v1/chat/completions`), including `X-OmniRoute-Request-Id`
and `X-OmniRoute-Version`.
"401":
$ref: "#/components/responses/Unauthorized"
/api/v1/embeddings:
post:
tags: [Embeddings]
summary: Create embeddings
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [input, model]
properties:
input:
oneOf:
- type: string
- type: array
items:
type: string
model:
type: string
responses:
"200":
description: >-
Embedding vectors. Success responses carry the `X-OmniRoute-*`
cost-telemetry headers (see `POST /api/v1/chat/completions`); media
cost is computed per modality when pricing is available, otherwise
`0` (fail-open).
/api/v1/providers/{provider}/embeddings:
post:
tags: [Embeddings]
summary: Create embeddings (provider-specific)
security:
- BearerAuth: []
parameters:
- name: provider
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Embedding vectors
/api/v1/images/generations:
post:
tags: [Images]
summary: Generate images
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [prompt]
properties:
prompt:
type: string
model:
type: string
n:
type: integer
default: 1
size:
type: string
default: 1024x1024
responses:
"200":
description: >-
Generated images. Success responses carry the `X-OmniRoute-*`
cost-telemetry headers (see `POST /api/v1/chat/completions`); image
cost is computed per image when pricing is available, otherwise `0`
(fail-open).
/api/v1/providers/{provider}/images/generations:
post:
tags: [Images]
summary: Generate images (provider-specific)
security:
- BearerAuth: []
parameters:
- name: provider
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Generated images
/api/v1/audio/speech:
post:
tags: [Audio]
summary: Generate speech audio
description: Text-to-speech endpoint. Routes to configured TTS providers.
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [input]
properties:
input:
type: string
model:
type: string
voice:
type: string
responses:
"200":
description: >-
Audio data. Success responses carry the `X-OmniRoute-*`
cost-telemetry headers (see `POST /api/v1/chat/completions`); speech
cost is computed per character when pricing is available, otherwise
`0` (fail-open).
/api/v1/audio/transcriptions:
post:
tags: [Audio]
summary: Transcribe audio
description: Audio-to-text transcription endpoint.
security:
- BearerAuth: []
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required: [file]
properties:
file:
type: string
format: binary
model:
type: string
responses:
"200":
description: >-
Transcription result. Success responses carry the `X-OmniRoute-*`
cost-telemetry headers (see `POST /api/v1/chat/completions`);
transcription cost is computed per second when pricing is available,
otherwise `0` (fail-open).
/api/v1/moderations:
post:
tags: [Moderations]
summary: Create moderation
description: Content moderation endpoint. Routes to configured moderation providers.
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [input]
properties:
input:
oneOf:
- type: string
- type: array
items:
type: string
responses:
"200":
description: >-
Moderation result. Success responses carry the `X-OmniRoute-*`
cost-telemetry headers (see `POST /api/v1/chat/completions`);
moderations are always cost `0` (free).
/api/v1/rerank:
post:
tags: [Rerank]
summary: Rerank documents
description: Document reranking endpoint.
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [query, documents]
properties:
query:
type: string
documents:
type: array
items:
type: string
model:
type: string
responses:
"200":
description: >-
Reranked documents. Success responses carry the `X-OmniRoute-*`
cost-telemetry headers (see `POST /api/v1/chat/completions`); rerank
cost is computed per search-unit when pricing is available,
otherwise `0` (fail-open).
/api/v1:
get:
tags: [System]
summary: API v1 root endpoint
description: Returns basic API info and status.
security:
- BearerAuth: []
responses:
"200":
description: API info
/api/v1/models:
get:
tags: [Models]
summary: List available models
description: Returns all models available across configured providers.
security:
- BearerAuth: []
responses:
"200":
description: Model list
content:
application/json:
schema:
type: object
properties:
object:
type: string
example: list
data:
type: array
items:
$ref: "#/components/schemas/Model"
/api/v1/providers/{provider}/models:
get:
tags: [Models]
summary: List models for a specific provider
description: Returns only models for the selected provider with provider prefix removed from each model id.
security:
- BearerAuth: []
parameters:
- in: path
name: provider
required: true
schema:
type: string
description: Provider id or alias (for example `openai`, `claude`, `cc`).
responses:
"200":
description: Provider-scoped model list
content:
application/json:
schema:
type: object
properties:
object:
type: string
example: list
data:
type: array
items:
$ref: "#/components/schemas/Model"
"400":
description: Unknown provider
/api/models:
get:
tags: [Models]
summary: List models (management)
responses:
"200":
description: Internal model list with aliases
/api/models/alias:
post:
tags: [Models]
summary: Create or update a model alias
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Alias created/updated
/api/models/catalog:
get:
tags: [Models]
summary: Get full model catalog
responses:
"200":
description: Complete catalog with all providers
# ─── Management Endpoints ──────────────────────────────────────
/api/providers:
get:
tags: [Providers]
summary: List provider connections
responses:
"200":
description: Provider connection list
content:
application/json:
schema:
type: object
properties:
connections:
type: array
items:
$ref: "#/components/schemas/ProviderConnection"
post:
tags: [Providers]
summary: Create provider connection
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ProviderConnectionCreate"
responses:
"201":
description: Created provider connection
/api/providers/{id}:
get:
tags: [Providers]
summary: Get provider connection
parameters:
- $ref: "#/components/parameters/ResourceId"
responses:
"200":
description: Provider connection details
"404":
description: Provider not found
patch:
tags: [Providers]
summary: Update provider connection
parameters:
- $ref: "#/components/parameters/ResourceId"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ProviderConnectionCreate"
responses:
"200":
description: Updated provider
delete:
tags: [Providers]
summary: Delete provider connection
parameters:
- $ref: "#/components/parameters/ResourceId"
responses:
"200":
description: Provider deleted
/api/providers/{id}/test:
post:
tags: [Providers]
summary: Test provider connection
parameters:
- $ref: "#/components/parameters/ResourceId"
responses:
"200":
description: Test result
/api/providers/{id}/models:
get:
tags: [Providers]
summary: List models for a provider
parameters:
- $ref: "#/components/parameters/ResourceId"
responses:
"200":
description: Provider model list
/api/providers/test-batch:
post:
tags: [Providers]
summary: Test multiple providers at once
responses:
"200":
description: Batch test results
/api/providers/validate:
post:
tags: [Providers]
summary: Validate provider credentials
responses:
"200":
description: Validation result
/api/providers/client:
get:
tags: [Providers]
summary: Get client-side provider info
responses:
"200":
description: Provider info for frontend
/api/providers/agy-auth/import:
post:
tags: [Providers]
summary: Import an Antigravity CLI (agy) token file as an `agy` connection
responses:
"200":
description: Created or updated provider connection
/api/providers/agy-auth/import-bulk:
post:
tags: [Providers]
summary: Bulk-import multiple Antigravity CLI (agy) token files (up to 50)
responses:
"200":
description: Per-entry import results (success/failed counts)
/api/providers/agy-auth/zip-extract:
post:
tags: [Providers]
summary: Extract `.json` token files from an uploaded ZIP for agy bulk import
responses:
"200":
description: Extracted token-file entries
/api/providers/agy-auth/apply-local:
post:
tags: [Providers]
summary: Auto-detect and import the local Antigravity CLI (agy) login from disk
responses:
"200":
description: Created or updated provider connection
"404":
description: No local agy login found
/api/provider-nodes:
get:
tags: [Provider Nodes]
summary: List provider nodes
responses:
"200":
description: Provider node list
post:
tags: [Provider Nodes]
summary: Create provider node
responses:
"201":
description: Created node
/api/provider-nodes/{id}:
patch:
tags: [Provider Nodes]
summary: Update provider node
parameters:
- $ref: "#/components/parameters/ResourceId"
responses:
"200":
description: Updated node
delete:
tags: [Provider Nodes]
summary: Delete provider node
parameters:
- $ref: "#/components/parameters/ResourceId"
responses:
"200":
description: Node deleted
/api/provider-nodes/validate:
post:
tags: [Provider Nodes]
summary: Validate a provider node
responses:
"200":
description: Validation result
/api/provider-models:
get:
tags: [Provider Nodes]
summary: List provider models
responses:
"200":
description: Provider model list
/api/keys:
get:
tags: [API Keys]
summary: List API keys
responses:
"200":
description: API key list
content:
application/json:
schema:
type: object
properties:
keys:
type: array
items:
$ref: "#/components/schemas/ApiKey"
"401":
description: Authentication required
post:
tags: [API Keys]
summary: Create API key
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [label]
properties:
label:
type: string
responses:
"201":
description: Created API key (includes full key value)
"401":
description: Authentication required
/api/keys/{id}:
get:
tags: [API Keys]
summary: Get API key
parameters:
- $ref: "#/components/parameters/ResourceId"
responses:
"200":
description: API key metadata
"401":
description: Authentication required
"404":
description: Key not found
patch:
tags: [API Keys]
summary: Update API key
parameters:
- $ref: "#/components/parameters/ResourceId"
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: true
responses:
"200":
description: API key settings updated
"400":
description: Invalid update request
"401":
description: Authentication required
"404":
description: Key not found
delete:
tags: [API Keys]
summary: Delete API key
parameters:
- $ref: "#/components/parameters/ResourceId"
responses:
"200":
description: Key deleted
"401":
description: Authentication required
"404":
description: Key not found
/api/combos:
get:
tags: [Combos]
summary: List routing combos
responses:
"200":
description: Combo list
post:
tags: [Combos]
summary: Create routing combo
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ComboCreate"
responses:
"201":
description: Created combo
/api/combos/{id}:
patch:
tags: [Combos]
summary: Update combo
parameters:
- $ref: "#/components/parameters/ResourceId"
responses:
"200":
description: Updated combo
delete:
tags: [Combos]
summary: Delete combo
parameters:
- $ref: "#/components/parameters/ResourceId"
responses:
"200":
description: Combo deleted
/api/combos/metrics:
get:
tags: [Combos]
summary: Get combo metrics
responses:
"200":
description: Metrics for combos
/api/combos/test:
post:
tags: [Combos]
summary: Test a combo configuration
responses:
"200":
description: Test result
/api/settings:
get:
tags: [Settings]
summary: Get application settings
responses:
"200":
description: Current settings
patch:
tags: [Settings]
summary: Update settings
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Updated settings
/api/settings/purge-request-history:
post:
tags: [Settings]
summary: Clear request log history
description: Deletes `call_logs`, legacy `request_detail_logs`, and local request artifact files under `DATA_DIR/call_logs`.
security:
- ManagementSessionAuth: []
responses:
"200":
description: Request history cleared
content:
application/json:
schema:
type: object
properties:
deleted:
type: integer
deletedArtifacts:
type: integer
deletedDetailedLogs:
type: integer
errors:
type: integer
"401":
description: Unauthorized
"500":
description: Cleanup failed or reported errors
content:
application/json:
schema:
type: object
properties:
deleted:
type: integer
deletedArtifacts:
type: integer
deletedDetailedLogs:
type: integer
errors:
type: integer
error:
type: object
/api/settings/compression:
get:
tags: [Compression]
summary: Get global compression settings
security:
- ManagementSessionAuth: []
responses:
"200":
description: Current compression settings
put:
tags: [Compression]
summary: Update global compression settings
security:
- ManagementSessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
enabled:
type: boolean
defaultMode:
type: string
enum: [off, lite, standard, aggressive, ultra, rtk, stacked]
autoTriggerMode:
type: string
enum: [off, lite, standard, aggressive, ultra, rtk, stacked]
autoTriggerTokens:
type: integer
minimum: 0
rtkConfig:
type: object
additionalProperties: true
stackedPipeline:
type: array
items:
type: object
responses:
"200":
description: Updated compression settings
/api/settings/compression/mcp-accessibility:
get:
tags: [Compression]
summary: Get the MCP tool-output accessibility (trimming) config
security:
- ManagementSessionAuth: []
responses:
"200":
description: Current mcpAccessibility config
put:
tags: [Compression]
summary: Update the MCP tool-output accessibility (trimming) config
description: >-
Partial-merge update. Numeric floors (e.g. a maxTextChars below the truncation-tail
reserve) are folded back to the safe defaults server-side, so the response reflects the
effective config.
security:
- ManagementSessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
enabled:
type: boolean
maxTextChars:
type: integer
minimum: 1
collapseThreshold:
type: integer
minimum: 1
collapseKeepHead:
type: integer
minimum: 0
collapseKeepTail:
type: integer
minimum: 0
minLengthToProcess:
type: integer
minimum: 1
responses:
"200":
description: Updated mcpAccessibility config (numeric floors applied)
/api/compression/preview:
post:
tags: [Compression]
summary: Preview compression for a message payload
security:
- BearerAuth: []
- ManagementSessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [messages, mode]
properties:
mode:
type: string
enum: [off, lite, standard, aggressive, ultra, rtk, stacked]
messages:
type: array
items:
type: object
required: [role, content]
properties:
role:
type: string
content:
oneOf:
- type: string
- type: array
items: {}
config:
type: object
additionalProperties: true
responses:
"200":
description: Compression preview with diff, validation, and stats
/api/compression/language-packs:
get:
tags: [Compression]
summary: List Caveman compression language packs
security:
- BearerAuth: []
- ManagementSessionAuth: []
responses:
"200":
description: Available languages and rule-pack metadata
/api/compression/rules:
get:
tags: [Compression]
summary: List Caveman compression rule metadata
security:
- BearerAuth: []
- ManagementSessionAuth: []
responses:
"200":
description: Caveman rule metadata
/api/context/rtk/config:
get:
tags: [Compression]
summary: Get RTK compression settings
security:
- ManagementSessionAuth: []
responses:
"200":
description: Current RTK config
put:
tags: [Compression]
summary: Update RTK compression settings
security:
- ManagementSessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
enabled:
type: boolean
intensity:
type: string
enum: [minimal, standard, aggressive]
customFiltersEnabled:
type: boolean
trustProjectFilters:
type: boolean
rawOutputRetention:
type: string
enum: [never, failures, always]
rawOutputMaxBytes:
type: integer
responses:
"200":
description: Updated RTK config
/api/context/rtk/filters:
get:
tags: [Compression]
summary: List RTK filters and load diagnostics
security:
- ManagementSessionAuth: []
responses:
"200":
description: RTK filter catalog and diagnostics
/api/context/rtk/test:
post:
tags: [Compression]
summary: Run RTK compression preview for text
security:
- ManagementSessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [text]
properties:
text:
type: string
command:
type: string
config:
type: object
additionalProperties: true
responses:
"200":
description: Detection and RTK compression result
/api/context/rtk/raw-output/{id}:
get:
tags: [Compression]
summary: Read retained redacted RTK raw output
security:
- ManagementSessionAuth: []
parameters:
- in: path
name: id
required: true
schema:
type: string
pattern: "^[a-f0-9]{24}$"
responses:
"200":
description: Raw output text
"404":
description: Raw output not found
/api/settings/payload-rules:
get:
tags: [Settings]
summary: Get payload rules configuration
description: |
Returns the current payload rules used to mutate outgoing request payloads before they
are sent upstream.
Requires a dashboard management session cookie when management auth is enabled.
security:
- ManagementSessionAuth: []
responses:
"200":
description: Current payload rules configuration
content:
application/json:
schema:
$ref: "#/components/schemas/PayloadRulesConfig"
"401":
$ref: "#/components/responses/ManagementAuthenticationRequired"
"403":
$ref: "#/components/responses/ManagementInvalidToken"
"500":
description: Failed to read payload rules configuration
content:
application/json:
schema:
$ref: "#/components/schemas/ApiErrorResponse"
put:
tags: [Settings]
summary: Update payload rules configuration
description: |
Persists and hot reloads payload rules. The legacy input field `default-raw` is accepted
on writes and normalized to `defaultRaw` in responses/runtime state.
Requires a dashboard management session cookie when management auth is enabled.
security:
- ManagementSessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/UpdatePayloadRulesRequest"
responses:
"200":
description: Updated payload rules configuration
content:
application/json:
schema:
$ref: "#/components/schemas/PayloadRulesConfig"
"400":
$ref: "#/components/responses/ValidationError"
"401":
$ref: "#/components/responses/ManagementAuthenticationRequired"
"403":
$ref: "#/components/responses/ManagementInvalidToken"
"500":
description: Failed to update payload rules configuration
content:
application/json:
schema:
$ref: "#/components/schemas/ApiErrorResponse"
/api/settings/combo-defaults:
get:
tags: [Settings]
summary: Get combo default settings
responses:
"200":
description: Default combo settings
/api/settings/proxy:
get:
tags: [Settings]
summary: Get proxy settings
responses:
"200":
description: Current proxy settings
patch:
tags: [Settings]
summary: Update proxy settings
responses:
"200":
description: Updated proxy settings
/api/settings/proxy/test:
post:
tags: [Settings]
summary: Test proxy connection
responses:
"200":
description: Test result
/api/settings/require-login:
post:
tags: [Settings]
summary: Toggle login requirement
responses:
"200":
description: Updated
/api/settings/ip-filter:
get:
tags: [Settings]
summary: Get IP filter configuration
description: Returns the current IP filter settings including blacklist, whitelist, and temp bans.
responses:
"200":
description: IP filter configuration
put:
tags: [Settings]
summary: Update IP filter configuration
description: |
Configure IP filtering with blacklist/whitelist modes, add/remove individual IPs, and manage temp bans.
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
enabled:
type: boolean
mode:
type: string
enum: [blacklist, whitelist]
blacklist:
type: array
items:
type: string
whitelist:
type: array
items:
type: string
addBlacklist:
type: string
removeBlacklist:
type: string
addWhitelist:
type: string
removeWhitelist:
type: string
tempBan:
type: object
properties:
ip:
type: string
durationMs:
type: integer
reason:
type: string
removeBan:
type: string
responses:
"200":
description: Updated IP filter configuration
/api/settings/system-prompt:
get:
tags: [Settings]
summary: Get system prompt configuration
description: Returns the current system prompt injection settings.
responses:
"200":
description: System prompt configuration
put:
tags: [Settings]
summary: Update system prompt configuration
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
prompt:
type: string
enabled:
type: boolean
responses:
"200":
description: Updated system prompt configuration
/api/settings/thinking-budget:
get:
tags: [Settings]
summary: Get thinking budget configuration
description: Returns the current thinking/reasoning budget settings for AI models.
responses:
"200":
description: Thinking budget configuration
put:
tags: [Settings]
summary: Update thinking budget configuration
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
mode:
type: string
description: Thinking mode (e.g., auto, manual, disabled)
customBudget:
type: integer
minimum: 0
maximum: 131072
effortLevel:
type: string
enum: [none, low, medium, high]
responses:
"200":
description: Updated thinking budget configuration
/api/rate-limit:
get:
tags: [Settings]
summary: Get rate limit configuration
responses:
"200":
description: Rate limit settings
post:
tags: [Settings]
summary: Update rate limit configuration
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Updated rate limit settings
/api/tags:
get:
tags: [System]
summary: List Ollama-compatible model tags
description: Returns models in Ollama /api/tags format for Ollama client compatibility
responses:
"200":
description: Ollama model tags
# ─── Usage & Analytics ─────────────────────────────────────────
/api/usage/analytics:
get:
tags: [Usage]
summary: Get usage analytics
parameters:
- name: period
in: query
schema:
type: string
enum: [day, week, month]
default: day
responses:
"200":
description: Usage analytics data
/api/usage/call-logs:
get:
tags: [Usage]
summary: Get call logs
parameters:
- name: limit
in: query
schema:
type: integer
default: 50
- name: offset
in: query
schema:
type: integer
default: 0
responses:
"200":
description: Paginated call logs
/api/usage/call-logs/{id}:
get:
tags: [Usage]
summary: Get a specific call log
parameters:
- $ref: "#/components/parameters/ResourceId"
responses:
"200":
description: Call log detail
/api/usage/{connectionId}:
get:
tags: [Usage]
summary: Get usage for a specific connection
parameters:
- name: connectionId
in: path
required: true
schema:
type: string
responses:
"200":
description: Connection usage data
/api/usage/history:
get:
tags: [Usage]
summary: Get usage history
responses:
"200":
description: Historical usage data
/api/usage/logs:
get:
tags: [Usage]
summary: Get usage logs
responses:
"200":
description: Usage log entries
/api/usage/proxy-logs:
get:
tags: [Usage]
summary: Get proxy logs
responses:
"200":
description: Proxy log entries
/api/usage/request-logs:
get:
tags: [Usage]
summary: Get request logs
responses:
"200":
description: Request log entries
/api/usage/budget:
get:
tags: [Usage]
summary: Get usage budget status
description: Returns current budget limits and consumption.
responses:
"200":
description: Budget status
post:
tags: [Usage]
summary: Configure usage budget
description: Set or update budget limits for usage tracking.
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Updated budget configuration
# ─── Pricing ───────────────────────────────────────────────────
/api/pricing:
get:
tags: [Pricing]
summary: Get model pricing
responses:
"200":
description: Current pricing configuration
post:
tags: [Pricing]
summary: Set model pricing
responses:
"200":
description: Updated pricing
/api/pricing/defaults:
get:
tags: [Pricing]
summary: Get default pricing
responses:
"200":
description: Default pricing data
/api/pricing/models:
get:
tags: [Pricing]
summary: Get pricing per model
description: Returns pricing information organized by model.
responses:
"200":
description: Per-model pricing data
# ─── Translator ────────────────────────────────────────────────
/api/translator/detect:
post:
tags: [Translator]
summary: Detect request format
description: Detects the API format of a request body (OpenAI, Claude, Gemini, etc.)
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [body]
properties:
body:
type: object
responses:
"200":
description: Detected format
/api/translator/translate:
post:
tags: [Translator]
summary: Translate between formats
description: Converts a request between API formats (e.g. Claude → OpenAI)
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [sourceFormat, targetFormat, body]
properties:
step:
type: string
sourceFormat:
type: string
targetFormat:
type: string
provider:
type: string
body:
type: object
responses:
"200":
description: Translated request
/api/translator/send:
post:
tags: [Translator]
summary: Send translated request to provider
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [provider, body]
properties:
provider:
type: string
body:
type: object
responses:
"200":
description: Provider response (may be SSE stream)
/api/translator/history:
get:
tags: [Translator]
summary: Get translation history
description: Returns recent translation events for the Live Monitor
responses:
"200":
description: Translation history entries
# ─── CLI Remote Mode ───────────────────────────────────────────
/api/cli/connect:
post:
tags: [CLI Remote Mode]
summary: Exchange the management password for a scoped CLI access token
description: >
Remote-mode bootstrap. Public (password-gated) route: verifies the
management password with brute-force lockout, then mints an `oma_`
access token. The plaintext token is returned once.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [password]
properties:
password: { type: string }
name: { type: string }
scope: { type: string, enum: [read, write, admin] }
expiresInDays: { type: integer, minimum: 1, maximum: 3650 }
responses:
"200":
description: Token minted (token returned once)
"401":
description: Invalid password
"429":
description: Too many failed attempts
/api/cli/whoami:
get:
tags: [CLI Remote Mode]
summary: Report the current credential (scope, name, expiry)
responses:
"200":
description: Authenticated; access-token details when applicable
"401":
description: Authentication required
/api/cli/tokens:
get:
tags: [CLI Remote Mode]
summary: List access tokens (masked) — admin scope
responses:
"200":
description: Masked token list
"403":
description: Insufficient scope
post:
tags: [CLI Remote Mode]
summary: Create a scoped access token — admin scope
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [name]
properties:
name: { type: string }
scope: { type: string, enum: [read, write, admin] }
expiresInDays: { type: integer, minimum: 1, maximum: 3650 }
responses:
"200":
description: Token created (token returned once)
"403":
description: Insufficient scope
/api/cli/tokens/{id}:
delete:
tags: [CLI Remote Mode]
summary: Revoke an access token by id or display prefix — admin scope
parameters:
- name: id
in: path
required: true
schema: { type: string }
responses:
"200":
description: Token revoked
"403":
description: Insufficient scope
"404":
description: Token not found or already revoked
# ─── CLI Tools ─────────────────────────────────────────────────
/api/cli-tools/backups:
get:
tags: [CLI Tools]
summary: List CLI tool backups
responses:
"200":
description: Backup list
post:
tags: [CLI Tools]
summary: Create CLI tool backup
responses:
"200":
description: Backup created
/api/cli-tools/runtime/{toolId}:
get:
tags: [CLI Tools]
summary: Get runtime status for a CLI tool
parameters:
- name: toolId
in: path
required: true
schema:
type: string
responses:
"200":
description: Runtime status
/api/cli-tools/guide-settings/{toolId}:
get:
tags: [CLI Tools]
summary: Get guide settings for a tool
parameters:
- name: toolId
in: path
required: true
schema:
type: string
responses:
"200":
description: Guide settings
/api/cli-tools/antigravity-mitm:
get:
tags: [CLI Tools]
summary: Get Antigravity MITM proxy settings
responses:
"200":
description: MITM proxy configuration
post:
tags: [CLI Tools]
summary: Update Antigravity MITM proxy settings
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Updated MITM proxy configuration
delete:
tags: [CLI Tools]
summary: Reset Antigravity MITM proxy settings
responses:
"200":
description: MITM proxy settings reset
/api/cli-tools/antigravity-mitm/alias:
get:
tags: [CLI Tools]
summary: Get Antigravity MITM alias configuration
responses:
"200":
description: Alias configuration
put:
tags: [CLI Tools]
summary: Update Antigravity MITM alias configuration
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Updated alias configuration
/api/cli-tools/claude-settings:
get:
tags: [CLI Tools]
summary: Get Claude CLI settings
responses:
"200":
description: Claude CLI configuration
post:
tags: [CLI Tools]
summary: Apply Claude CLI settings
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Claude CLI settings applied
delete:
tags: [CLI Tools]
summary: Reset Claude CLI settings
responses:
"200":
description: Claude CLI settings reset
/api/cli-tools/cline-settings:
get:
tags: [CLI Tools]
summary: Get Cline CLI settings
responses:
"200":
description: Cline CLI configuration
post:
tags: [CLI Tools]
summary: Apply Cline CLI settings
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Cline CLI settings applied
delete:
tags: [CLI Tools]
summary: Reset Cline CLI settings
responses:
"200":
description: Cline CLI settings reset
/api/cli-tools/codex-profiles:
get:
tags: [CLI Tools]
summary: Get Codex profiles
responses:
"200":
description: Codex profile list
post:
tags: [CLI Tools]
summary: Create Codex profile
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Profile created
put:
tags: [CLI Tools]
summary: Update Codex profile
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Profile updated
delete:
tags: [CLI Tools]
summary: Delete Codex profile
responses:
"200":
description: Profile deleted
/api/cli-tools/codex-settings:
get:
tags: [CLI Tools]
summary: Get Codex CLI settings
responses:
"200":
description: Codex CLI configuration
post:
tags: [CLI Tools]
summary: Apply Codex CLI settings
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Codex CLI settings applied
delete:
tags: [CLI Tools]
summary: Reset Codex CLI settings
responses:
"200":
description: Codex CLI settings reset
/api/cli-tools/droid-settings:
get:
tags: [CLI Tools]
summary: Get Droid CLI settings
responses:
"200":
description: Droid CLI configuration
post:
tags: [CLI Tools]
summary: Apply Droid CLI settings
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Droid CLI settings applied
delete:
tags: [CLI Tools]
summary: Reset Droid CLI settings
responses:
"200":
description: Droid CLI settings reset
/api/cli-tools/kilo-settings:
get:
tags: [CLI Tools]
summary: Get Kilo CLI settings
responses:
"200":
description: Kilo CLI configuration
post:
tags: [CLI Tools]
summary: Apply Kilo CLI settings
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Kilo CLI settings applied
delete:
tags: [CLI Tools]
summary: Reset Kilo CLI settings
responses:
"200":
description: Kilo CLI settings reset
/api/cli-tools/openclaw-settings:
get:
tags: [CLI Tools]
summary: Get OpenClaw CLI settings
responses:
"200":
description: OpenClaw CLI configuration
post:
tags: [CLI Tools]
summary: Apply OpenClaw CLI settings
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: OpenClaw CLI settings applied
delete:
tags: [CLI Tools]
summary: Reset OpenClaw CLI settings
responses:
"200":
description: OpenClaw CLI settings reset
# ─── Embedded Services ─────────────────────────────────────────
# All routes LOCAL_ONLY (loopback only) — hard rule #17.
# See docs/frameworks/EMBEDDED-SERVICES.md for full reference.
/api/services/9router/install:
post:
tags: [Embedded Services]
summary: Install 9Router from npm
description: >-
Installs the `9router` npm package under DATA_DIR/services/9router/.
Uses execFile (no shell interpolation — hard rule #13).
**LOCAL_ONLY** — loopback only.
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
version:
type: string
default: latest
description: npm version tag or semver to install
responses:
"200":
description: Install succeeded
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
installedVersion:
type: string
path:
type: string
"400":
description: Invalid request body
"500":
description: npm install failed
/api/services/9router/start:
post:
tags: [Embedded Services]
summary: Start 9Router
description: >-
Spawns the 9Router process. Idempotent if already running.
**LOCAL_ONLY** — loopback only.
responses:
"200":
description: Service started (or already running)
content:
application/json:
schema:
$ref: "#/components/schemas/ServiceStatus"
"409":
description: 9Router is not installed
"503":
description: Start failed
/api/services/9router/stop:
post:
tags: [Embedded Services]
summary: Stop 9Router
description: >-
Gracefully stops 9Router (SIGTERM → 15 s → SIGKILL). Idempotent.
**LOCAL_ONLY** — loopback only.
responses:
"200":
description: Service stopped
content:
application/json:
schema:
$ref: "#/components/schemas/ServiceStatus"
"503":
description: Stop failed
/api/services/9router/restart:
post:
tags: [Embedded Services]
summary: Restart 9Router
description: >-
Equivalent to stop() then start() under the operation lock.
**LOCAL_ONLY** — loopback only.
responses:
"200":
description: Service restarted
content:
application/json:
schema:
$ref: "#/components/schemas/ServiceStatus"
/api/services/9router/update:
post:
tags: [Embedded Services]
summary: Update 9Router to a newer npm version
description: >-
Stops the service (if running), installs the newer npm version, then restarts.
**LOCAL_ONLY** — loopback only.
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
version:
type: string
default: latest
responses:
"200":
description: Update succeeded
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
previousVersion:
type: string
installedVersion:
type: string
"400":
description: Invalid request body
"500":
description: Update failed
/api/services/9router/rotate-key:
post:
tags: [Embedded Services]
summary: Rotate the 9Router API key
description: >-
Generates a new API key, encrypts it at-rest, and restarts the service to
apply it. The plaintext key is never returned.
**LOCAL_ONLY** — loopback only.
responses:
"200":
description: Key rotated
content:
application/json:
schema:
type: object
properties:
keyRotated:
type: boolean
restarted:
type: boolean
"500":
description: Rotation failed
/api/services/9router/status:
get:
tags: [Embedded Services]
summary: Get 9Router status
description: >-
Returns combined live supervisor state and DB metadata.
**LOCAL_ONLY** — loopback only.
responses:
"200":
description: Status response
content:
application/json:
schema:
$ref: "#/components/schemas/ServiceStatusExtended"
"500":
description: Status read failed
/api/services/9router/auto-start:
post:
tags: [Embedded Services]
summary: Toggle 9Router auto-start
description: >-
When enabled, 9Router starts automatically on the next OmniRoute boot.
**LOCAL_ONLY** — loopback only.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [enabled]
properties:
enabled:
type: boolean
responses:
"200":
description: Auto-start flag updated
content:
application/json:
schema:
type: object
properties:
autoStart:
type: boolean
"400":
description: Invalid request body
/api/services/cliproxy/install:
post:
tags: [Embedded Services]
summary: Install CLIProxyAPI from npm
description: >-
Installs the CLIProxyAPI package under DATA_DIR/services/cliproxy/.
**LOCAL_ONLY** — loopback only.
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
version:
type: string
default: latest
responses:
"200":
description: Install succeeded
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
installedVersion:
type: string
"400":
description: Invalid request body
"500":
description: npm install failed
/api/services/cliproxy/start:
post:
tags: [Embedded Services]
summary: Start CLIProxyAPI
description: >-
Spawns the CLIProxyAPI process. Idempotent if already running.
**LOCAL_ONLY** — loopback only.
responses:
"200":
description: Service started
content:
application/json:
schema:
$ref: "#/components/schemas/ServiceStatus"
"409":
description: CLIProxyAPI is not installed
"503":
description: Start failed
/api/services/cliproxy/stop:
post:
tags: [Embedded Services]
summary: Stop CLIProxyAPI
description: >-
Gracefully stops CLIProxyAPI. Idempotent.
**LOCAL_ONLY** — loopback only.
responses:
"200":
description: Service stopped
content:
application/json:
schema:
$ref: "#/components/schemas/ServiceStatus"
/api/services/cliproxy/restart:
post:
tags: [Embedded Services]
summary: Restart CLIProxyAPI
description: >-
stop() then start() under the operation lock.
**LOCAL_ONLY** — loopback only.
responses:
"200":
description: Service restarted
content:
application/json:
schema:
$ref: "#/components/schemas/ServiceStatus"
/api/services/cliproxy/update:
post:
tags: [Embedded Services]
summary: Update CLIProxyAPI to a newer npm version
description: >-
Stops, installs newer version, restarts.
**LOCAL_ONLY** — loopback only.
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
version:
type: string
default: latest
responses:
"200":
description: Update succeeded
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
installedVersion:
type: string
"500":
description: Update failed
/api/services/cliproxy/status:
get:
tags: [Embedded Services]
summary: Get CLIProxyAPI status
description: >-
Returns live supervisor state and DB metadata (no apiKeyMasked — CLIProxyAPI
does not use an injected API key).
**LOCAL_ONLY** — loopback only.
responses:
"200":
description: Status response
content:
application/json:
schema:
$ref: "#/components/schemas/ServiceStatus"
/api/services/cliproxy/auto-start:
post:
tags: [Embedded Services]
summary: Toggle CLIProxyAPI auto-start
description: >-
When enabled, CLIProxyAPI starts automatically on the next OmniRoute boot.
**LOCAL_ONLY** — loopback only.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [enabled]
properties:
enabled:
type: boolean
responses:
"200":
description: Auto-start flag updated
content:
application/json:
schema:
type: object
properties:
autoStart:
type: boolean
"400":
description: Invalid request body
/api/services/mux/install:
post:
tags: [Embedded Services]
summary: Install Mux from npm
description: >-
Installs the `mux` npm package (coder/mux — local agent-orchestration
daemon) under DATA_DIR/services/mux/. **LOCAL_ONLY** — loopback only.
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
version:
type: string
default: latest
responses:
"200":
description: Install succeeded
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
installedVersion:
type: string
"400":
description: Invalid request body
"500":
description: npm install failed
/api/services/mux/start:
post:
tags: [Embedded Services]
summary: Start Mux
description: >-
Spawns `mux server --host 127.0.0.1 --port <port>`. Idempotent if
already running. **LOCAL_ONLY** — loopback only.
responses:
"200":
description: Service started
content:
application/json:
schema:
$ref: "#/components/schemas/ServiceStatus"
"409":
description: Mux is not installed
"503":
description: Start failed
/api/services/mux/stop:
post:
tags: [Embedded Services]
summary: Stop Mux
description: >-
Gracefully stops Mux. Idempotent.
**LOCAL_ONLY** — loopback only.
responses:
"200":
description: Service stopped
content:
application/json:
schema:
$ref: "#/components/schemas/ServiceStatus"
/api/services/mux/restart:
post:
tags: [Embedded Services]
summary: Restart Mux
description: >-
stop() then start() under the operation lock.
**LOCAL_ONLY** — loopback only.
responses:
"200":
description: Service restarted
content:
application/json:
schema:
$ref: "#/components/schemas/ServiceStatus"
/api/services/mux/update:
post:
tags: [Embedded Services]
summary: Update Mux to a newer npm version
description: >-
Stops, installs newer version, restarts.
**LOCAL_ONLY** — loopback only.
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
version:
type: string
default: latest
responses:
"200":
description: Update succeeded
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
installedVersion:
type: string
"500":
description: Update failed
/api/services/mux/status:
get:
tags: [Embedded Services]
summary: Get Mux status
description: >-
Returns live supervisor state and DB metadata.
**LOCAL_ONLY** — loopback only.
responses:
"200":
description: Status response
content:
application/json:
schema:
$ref: "#/components/schemas/ServiceStatus"
/api/services/mux/auto-start:
post:
tags: [Embedded Services]
summary: Toggle Mux auto-start
description: >-
When enabled, Mux starts automatically on the next OmniRoute boot.
**LOCAL_ONLY** — loopback only.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [enabled]
properties:
enabled:
type: boolean
responses:
"200":
description: Auto-start flag updated
content:
application/json:
schema:
type: object
properties:
autoStart:
type: boolean
"400":
description: Invalid request body
/api/services/bifrost/install:
post:
tags: [Embedded Services]
summary: Install Bifrost
description: >-
Installs the `@maximhq/bifrost` npm package under DATA_DIR/services/bifrost/.
The package downloads the Go binary on first run. Accepts an optional `version`
field (semver or `latest`). **LOCAL_ONLY** — loopback only.
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
version:
type: string
default: latest
responses:
"200":
description: Installation result
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
installedVersion:
type: string
installPath:
type: string
durationMs:
type: number
/api/services/bifrost/start:
post:
tags: [Embedded Services]
summary: Start Bifrost
description: Starts the supervised Bifrost process. **LOCAL_ONLY** — loopback only.
responses:
"200":
description: Service status after start
"409":
description: Bifrost is not installed
/api/services/bifrost/stop:
post:
tags: [Embedded Services]
summary: Stop Bifrost
description: Stops the supervised Bifrost process. **LOCAL_ONLY** — loopback only.
responses:
"200":
description: Service status after stop
/api/services/bifrost/restart:
post:
tags: [Embedded Services]
summary: Restart Bifrost
description: Restarts the supervised Bifrost process. **LOCAL_ONLY** — loopback only.
responses:
"200":
description: Service status after restart
"409":
description: Bifrost is not installed
/api/services/bifrost/update:
post:
tags: [Embedded Services]
summary: Update Bifrost
description: >-
Updates Bifrost to the latest npm version. Stops the running process,
installs the new version, and restarts if it was previously running.
**LOCAL_ONLY** — loopback only.
responses:
"200":
description: Update result
/api/services/bifrost/status:
get:
tags: [Embedded Services]
summary: Get Bifrost status
description: Returns live and DB status for the supervised Bifrost service. **LOCAL_ONLY** — loopback only.
responses:
"200":
description: Bifrost service status
/api/services/bifrost/auto-start:
post:
tags: [Embedded Services]
summary: Toggle Bifrost auto-start
description: >-
When enabled, Bifrost starts automatically on the next OmniRoute boot.
**LOCAL_ONLY** — loopback only.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [enabled]
properties:
enabled:
type: boolean
responses:
"204":
description: Auto-start flag updated
"400":
description: Invalid request body
/api/services/{name}/logs:
get:
tags: [Embedded Services]
summary: Stream service logs via SSE
description: >-
Returns a Server-Sent Events stream from the service's in-memory ring buffer
(5 MB, circular). Sends a `snapshot` event with historical lines first, then
live `log` events, plus a `heartbeat` every 15 s.
**LOCAL_ONLY** — loopback only.
parameters:
- name: name
in: path
required: true
schema:
type: string
enum: [9router, cliproxy]
- name: tail
in: query
schema:
type: integer
default: 200
maximum: 1000
description: Number of historical lines to include in the initial snapshot
- name: filter
in: query
schema:
type: string
maxLength: 200
description: >-
Case-insensitive substring filter applied to log lines.
No regex — ReDoS-safe by design.
responses:
"200":
description: SSE log stream
content:
text/event-stream:
schema:
type: string
description: >-
Events: `snapshot` (LogLine[]), `log` (LogLine), `heartbeat` ({})
"400":
description: filter parameter exceeds maximum length
"404":
description: Service not found
# ─── OAuth ─────────────────────────────────────────────────────
/api/oauth/{provider}/{action}:
get:
tags: [OAuth]
summary: OAuth flow handler
description: Handles OAuth authorization and callback for providers
parameters:
- name: provider
in: path
required: true
schema:
type: string
- name: action
in: path
required: true
schema:
type: string
enum: [authorize, callback, refresh, status]
responses:
"200":
description: OAuth flow response
"302":
description: Redirect to provider auth page
/api/oauth/cursor/auto-import:
get:
tags: [OAuth]
summary: Auto-import Cursor OAuth credentials
description: Automatically detects and imports Cursor credentials from local config.
responses:
"200":
description: Import result
/api/oauth/cursor/import:
get:
tags: [OAuth]
summary: Get Cursor import status
responses:
"200":
description: Current import status
post:
tags: [OAuth]
summary: Import Cursor OAuth credentials
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Credentials imported
/api/oauth/kiro/auto-import:
get:
tags: [OAuth]
summary: Auto-import Kiro OAuth credentials
description: Automatically detects and imports Kiro credentials from local config.
responses:
"200":
description: Import result
/api/oauth/kiro/import:
get:
tags: [OAuth]
summary: Get Kiro import status
responses:
"200":
description: Current import status
post:
tags: [OAuth]
summary: Import Kiro OAuth credentials
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Credentials imported
/api/oauth/kiro/social-authorize:
get:
tags: [OAuth]
summary: Initiate Kiro social OAuth authorization
description: Starts the social OAuth flow for Kiro.
responses:
"302":
description: Redirect to OAuth provider
/api/oauth/kiro/social-exchange:
post:
tags: [OAuth]
summary: Exchange Kiro social OAuth token
description: Exchanges the authorization code for access tokens.
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Token exchange result
# ─── Cloud ─────────────────────────────────────────────────────
/api/cloud/auth:
post:
tags: [Cloud]
summary: Authenticate with cloud worker
description: Authenticates with the OmniRoute cloud worker for remote access.
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Authentication result
/api/cloud/credentials/update:
put:
tags: [Cloud]
summary: Update cloud worker credentials
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Credentials updated
/api/cloud/model/resolve:
post:
tags: [Cloud]
summary: Resolve model via cloud
description: Resolves a model request through the cloud worker.
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Resolved model info
/api/cloud/models/alias:
get:
tags: [Cloud]
summary: Get cloud model aliases
responses:
"200":
description: Cloud model alias list
put:
tags: [Cloud]
summary: Update cloud model alias
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Alias updated
# ─── Fallback ──────────────────────────────────────────────────
/api/fallback/chains:
get:
tags: [Fallback]
summary: List fallback chains
description: Returns all registered fallback chains for model routing.
responses:
"200":
description: Fallback chain list
post:
tags: [Fallback]
summary: Create fallback chain
description: Registers a fallback routing chain for a model.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [model, chain]
properties:
model:
type: string
chain:
type: array
items:
type: object
properties:
provider:
type: string
priority:
type: integer
enabled:
type: boolean
responses:
"200":
description: Fallback chain created
delete:
tags: [Fallback]
summary: Delete fallback chain
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [model]
properties:
model:
type: string
responses:
"200":
description: Fallback chain deleted
# ─── System ────────────────────────────────────────────────────
/api/auth/login:
post:
tags: [System]
summary: Authenticate user
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [password]
properties:
password:
type: string
minLength: 1
responses:
"200":
description: JWT token returned
"400":
description: Invalid login request
"401":
description: Invalid password
"403":
description: Password setup required
"429":
description: Too many failed attempts
/api/auth/logout:
post:
tags: [System]
summary: Log out
responses:
"200":
description: Session cleared
/api/init:
get:
tags: [System]
summary: Initialize application
responses:
"200":
description: Init status
/api/restart:
post:
tags: [System]
summary: Restart the application
responses:
"200":
description: Restart initiated
/api/shutdown:
post:
tags: [System]
summary: Shutdown the application
x-always-protected: true
responses:
"200":
description: Shutdown initiated
/api/db-backups:
get:
tags: [System]
summary: List database backups
responses:
"200":
description: Backup list
post:
tags: [System]
summary: Create database backup
responses:
"200":
description: Backup created
patch:
tags: [System]
summary: Save database backup retention settings
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
keepLatest:
type: integer
minimum: 1
maximum: 200
retentionDays:
type: integer
minimum: 0
maximum: 3650
responses:
"200":
description: Backup retention settings saved
/api/storage/health:
get:
tags: [System]
summary: Check storage health
responses:
"200":
description: Storage health status
/api/sync/cloud:
post:
tags: [System]
summary: Sync with cloud
responses:
"200":
description: Sync result
/api/sync/initialize:
post:
tags: [System]
summary: Initialize cloud sync
responses:
"200":
description: Sync initialized
# ─── Resilience & Monitoring ────────────────────────────────────
/api/resilience:
get:
tags: [System]
summary: Get resilience configuration
responses:
"200":
description: Request queue, connection cooldown, provider breaker, and wait settings
patch:
tags: [System]
summary: Update resilience configuration
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Updated resilience configuration
/api/resilience/reset:
post:
tags: [System]
summary: Reset circuit breakers
responses:
"200":
description: Circuit breakers reset
/api/monitoring/health:
get:
tags: [System]
summary: System health check
description: Returns system health including uptime, memory, circuit breakers, rate limits
responses:
"200":
description: Health status
/api/rate-limits:
get:
tags: [System]
summary: Get per-account rate limit status
responses:
"200":
description: Rate limit status by account
/api/sessions:
get:
tags: [System]
summary: Get active sessions
responses:
"200":
description: Active session list
/api/cache:
get:
tags: [System]
summary: Get cache statistics
responses:
"200":
description: Semantic cache and idempotency stats
delete:
tags: [System]
summary: Clear all caches
responses:
"200":
description: Caches cleared
/api/cache/stats:
get:
tags: [System]
summary: Get detailed cache statistics
description: Returns detailed statistics for all cache layers.
responses:
"200":
description: Detailed cache stats
delete:
tags: [System]
summary: Clear cache statistics
responses:
"200":
description: Cache stats cleared
# ─── Telemetry & Token Health ───────────────────────────────────
/api/telemetry/summary:
get:
tags: [Telemetry]
summary: Get telemetry summary
description: Returns aggregated telemetry data including request metrics and performance stats.
responses:
"200":
description: Telemetry summary data
/api/token-health:
get:
tags: [Telemetry]
summary: Get token health status
description: Returns health status of OAuth tokens across all providers.
responses:
"200":
description: Token health status
# ─── Evals & Policies ──────────────────────────────────────────
/api/evals:
get:
tags: [System]
summary: List eval suites
responses:
"200":
description: Eval suite list
post:
tags: [System]
summary: Run evaluation
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Eval results
/api/evals/{suiteId}:
get:
tags: [System]
summary: Get eval suite details
parameters:
- name: suiteId
in: path
required: true
schema:
type: string
responses:
"200":
description: Eval suite details
/api/policies:
get:
tags: [System]
summary: List routing policies
responses:
"200":
description: Policy list
post:
tags: [System]
summary: Create routing policy
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"201":
description: Created policy
delete:
tags: [System]
summary: Delete routing policy
responses:
"200":
description: Policy deleted
/api/compliance/audit-log:
get:
tags: [System]
summary: Get compliance audit log
description: >
Returns paginated audit log entries. Use `level=high` to filter to
high-level actions only (powers the Activity feed). Use `level=all`
(default) for full compliance table.
security:
- bearerAuth: []
parameters:
- name: level
in: query
schema:
type: string
enum: [high, all]
default: all
description: "high = Activity feed events only; all = all audit events"
- name: action
in: query
schema:
type: string
description: Filter by exact action string (e.g. "provider.added")
- name: actor
in: query
schema:
type: string
description: Filter by actor identifier
- name: limit
in: query
schema:
type: integer
default: 50
maximum: 500
- name: offset
in: query
schema:
type: integer
default: 0
responses:
"200":
description: Audit log entries
"401":
description: Unauthorized
"500":
description: Internal server error
# ─── Quota Sharing (Group B, plan 22) ────────────────────────────
/api/quota/pools:
get:
tags: [Quota]
summary: List quota pools
security:
- bearerAuth: []
responses:
"200":
description: Array of QuotaPool objects
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/QuotaPool"
"401":
description: Unauthorized
"500":
description: Internal server error
post:
tags: [Quota]
summary: Create quota pool
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/PoolCreate"
responses:
"201":
description: Pool created
content:
application/json:
schema:
$ref: "#/components/schemas/QuotaPool"
"400":
description: Validation error (Zod)
"401":
description: Unauthorized
"500":
description: Internal server error
/api/quota/pools/{id}:
get:
tags: [Quota]
summary: Get quota pool by ID
security:
- bearerAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
"200":
description: QuotaPool object
content:
application/json:
schema:
$ref: "#/components/schemas/QuotaPool"
"401":
description: Unauthorized
"404":
description: Pool not found
"500":
description: Internal server error
patch:
tags: [Quota]
summary: Update quota pool (name or allocations)
security:
- bearerAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/PoolUpdate"
responses:
"200":
description: Updated pool
"400":
description: Validation error
"401":
description: Unauthorized
"404":
description: Pool not found
"500":
description: Internal server error
delete:
tags: [Quota]
summary: Delete quota pool
security:
- bearerAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
"204":
description: Deleted
"401":
description: Unauthorized
"404":
description: Pool not found
"500":
description: Internal server error
/api/quota/pools/{id}/usage:
get:
tags: [Quota]
summary: Get pool usage snapshot (per-key consumption + burn rate)
security:
- bearerAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
"200":
description: PoolUsageSnapshot
content:
application/json:
schema:
$ref: "#/components/schemas/PoolUsageSnapshot"
"401":
description: Unauthorized
"404":
description: Pool not found
"500":
description: Internal server error
/api/quota/plans:
get:
tags: [Quota]
summary: List resolved provider plans (catalog + manual overrides)
security:
- bearerAuth: []
responses:
"200":
description: Array of ProviderPlan
"401":
description: Unauthorized
"500":
description: Internal server error
/api/quota/plans/{connectionId}:
get:
tags: [Quota]
summary: Get resolved plan for a connection
security:
- bearerAuth: []
parameters:
- name: connectionId
in: path
required: true
schema:
type: string
responses:
"200":
description: ProviderPlan (source = auto | manual)
"401":
description: Unauthorized
"404":
description: Connection not found
"500":
description: Internal server error
put:
tags: [Quota]
summary: Upsert manual plan override for a connection
security:
- bearerAuth: []
parameters:
- name: connectionId
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/PlanUpsert"
responses:
"200":
description: Updated plan
"400":
description: Validation error (Zod)
"401":
description: Unauthorized
"500":
description: Internal server error
delete:
tags: [Quota]
summary: Delete manual plan override (reverts to catalog/auto)
security:
- bearerAuth: []
parameters:
- name: connectionId
in: path
required: true
schema:
type: string
responses:
"204":
description: Override deleted
"401":
description: Unauthorized
"404":
description: Override not found
"500":
description: Internal server error
/api/quota/preview:
get:
tags: [Quota]
summary: Dry-run quota enforcement check (preview only, no consumption recorded)
security:
- bearerAuth: []
parameters:
- name: apiKeyId
in: query
required: true
schema:
type: string
- name: poolId
in: query
required: true
schema:
type: string
- name: estimatedTokens
in: query
schema:
type: number
- name: estimatedUsd
in: query
schema:
type: number
- name: estimatedRequests
in: query
schema:
type: integer
responses:
"200":
description: EnforceDecision (allow/block + reason)
"400":
description: Validation error (Zod)
"401":
description: Unauthorized
"500":
description: Internal server error
/api/settings/quota-store:
get:
tags: [Settings]
summary: Get current quota store driver settings
description: Redis URL is masked in the response (shows only scheme+host).
security:
- bearerAuth: []
responses:
"200":
description: QuotaStoreSettings (driver + masked redisUrl)
"401":
description: Unauthorized
"500":
description: Internal server error
put:
tags: [Settings]
summary: Update quota store driver settings
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/QuotaStoreSettings"
responses:
"200":
description: Settings updated
"400":
description: Validation error (Zod) — e.g. driver=redis without valid URL
"401":
description: Unauthorized
"500":
description: Internal server error
# ─── v1beta (Gemini-Compatible) ─────────────────────────────────
/api/v1beta/models:
get:
tags: [Models]
summary: List models (Gemini format)
description: Returns models in Gemini v1beta format for native SDK compatibility
security:
- BearerAuth: []
responses:
"200":
description: Model list in Gemini format
/api/v1beta/models/{path}:
post:
tags: [Models]
summary: Gemini generateContent
description: Gemini-compatible generateContent endpoint
security:
- BearerAuth: []
parameters:
- name: path
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Generated content
# ─── AgentBridge ──────────────────────────────────────────────
/api/tools/agent-bridge/agents:
get:
tags: [AgentBridge]
summary: List all 9 IDE agents with current state
description: >-
Returns the state (dns_enabled, cert_trusted, setup_completed, last_started_at,
last_error) for all 9 configured IDE agents. LOCAL_ONLY.
responses:
"200":
description: Array of agent state rows
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/AgentBridgeAgentState"
"403":
description: Loopback-only — request came from a non-loopback address
/api/tools/agent-bridge/state:
get:
tags: [AgentBridge]
summary: Get global AgentBridge server state
description: Returns running status, port, cert info, and intercepted request count.
responses:
"200":
description: Server state
content:
application/json:
schema:
$ref: "#/components/schemas/AgentBridgeServerState"
/api/tools/agent-bridge/server:
post:
tags: [AgentBridge]
summary: Control AgentBridge MITM server
description: Start, stop, restart, trust-cert, or regenerate-cert. SPAWN_CAPABLE.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/AgentBridgeServerAction"
responses:
"200":
description: Action executed
"400":
description: Invalid action
"409":
description: Port 443 conflict
/api/tools/agent-bridge/agents/{agentId}/dns:
post:
tags: [AgentBridge]
summary: Enable or disable DNS for one agent
description: Adds or removes /etc/hosts entries for the agent's host list. SPAWN_CAPABLE.
parameters:
- name: agentId
in: path
required: true
schema:
$ref: "#/components/schemas/AgentId"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/AgentBridgeDnsAction"
responses:
"200":
description: DNS updated
"400":
description: Validation error
/api/tools/agent-bridge/agents/{agentId}/mappings:
get:
tags: [AgentBridge]
summary: Get model mappings for one agent
parameters:
- name: agentId
in: path
required: true
schema:
$ref: "#/components/schemas/AgentId"
responses:
"200":
description: Array of source→target model mappings
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/AgentBridgeMappingRow"
put:
tags: [AgentBridge]
summary: Update model mappings for one agent
parameters:
- name: agentId
in: path
required: true
schema:
$ref: "#/components/schemas/AgentId"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/AgentBridgeMappingPut"
responses:
"200":
description: Mappings updated
/api/tools/agent-bridge/bypass:
get:
tags: [AgentBridge]
summary: List bypass patterns (hosts never decrypted)
responses:
"200":
description: Bypass patterns
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/AgentBridgeBypassRow"
put:
tags: [AgentBridge]
summary: Update user bypass patterns
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/AgentBridgeBypassUpsert"
responses:
"200":
description: Patterns updated
/api/tools/agent-bridge/cert:
post:
tags: [AgentBridge]
summary: Download or regenerate the AgentBridge CA certificate
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [action]
properties:
action:
type: string
enum: [download, regenerate]
responses:
"200":
description: CA certificate PEM (download) or regeneration confirmation
/api/tools/agent-bridge/upstream-ca:
get:
tags: [AgentBridge]
summary: Get configured upstream CA cert path
responses:
"200":
description: Upstream CA configuration
content:
application/json:
schema:
type: object
properties:
path:
type: string
nullable: true
post:
tags: [AgentBridge]
summary: Set upstream CA cert path for corporate TLS environments
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/AgentBridgeUpstreamCaPost"
responses:
"200":
description: Upstream CA configured
"400":
description: Path does not exist or is not readable
# ─── Traffic Inspector ─────────────────────────────────────────
/api/tools/traffic-inspector/requests:
get:
tags: [Traffic Inspector]
summary: List intercepted requests (filterable)
parameters:
- name: profile
in: query
schema:
type: string
enum: [llm, custom, all]
- name: host
in: query
schema:
type: string
- name: agent
in: query
schema:
$ref: "#/components/schemas/AgentId"
- name: status
in: query
schema:
type: string
enum: ["2xx", "3xx", "4xx", "5xx", error]
- name: source
in: query
schema:
$ref: "#/components/schemas/CaptureSource"
- name: sessionId
in: query
schema:
type: string
format: uuid
responses:
"200":
description: Array of intercepted requests
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/InterceptedRequest"
delete:
tags: [Traffic Inspector]
summary: Clear the in-memory traffic buffer
responses:
"204":
description: Buffer cleared
/api/tools/traffic-inspector/requests/{id}:
get:
tags: [Traffic Inspector]
summary: Get a single intercepted request by ID
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: Intercepted request details
content:
application/json:
schema:
$ref: "#/components/schemas/InterceptedRequest"
"404":
description: Request not found in buffer
/api/tools/traffic-inspector/requests/{id}/replay:
post:
tags: [Traffic Inspector]
summary: Replay a captured request through OmniRoute router
description: Re-executes the original request body against /v1/chat/completions. Consumes quota.
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: Replay response (streaming or JSON)
"404":
description: Request not found
/api/tools/traffic-inspector/requests/{id}/annotation:
put:
tags: [Traffic Inspector]
summary: Save or update annotation on a request
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/InspectorAnnotationPut"
responses:
"200":
description: Annotation saved
/api/tools/traffic-inspector/ws:
get:
tags: [Traffic Inspector]
summary: Live WebSocket stream of intercepted requests
description: >-
Upgrade to WebSocket. On connect, server sends `{type:"snapshot", data:[...]}`.
Subsequent events: `{type:"new", data:{...}}`, `{type:"update", data:{...}}`,
`{type:"clear"}`. LOCAL_ONLY.
responses:
"101":
description: WebSocket upgrade successful
"403":
description: Non-loopback origin rejected
/api/tools/traffic-inspector/export.har:
get:
tags: [Traffic Inspector]
summary: Export current filtered request list as HAR 1.2
parameters:
- name: profile
in: query
schema:
type: string
enum: [llm, custom, all]
- name: sessionId
in: query
schema:
type: string
format: uuid
responses:
"200":
description: HAR file (JSON)
content:
application/json:
schema:
type: object
description: HAR 1.2 format
/api/tools/traffic-inspector/hosts:
get:
tags: [Traffic Inspector]
summary: List custom capture hosts
responses:
"200":
description: Custom hosts list
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/InspectorCustomHost"
post:
tags: [Traffic Inspector]
summary: Add a custom capture host (edits /etc/hosts)
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/InspectorCustomHostCreate"
responses:
"201":
description: Host added
"409":
description: Host already exists
/api/tools/traffic-inspector/hosts/{host}:
delete:
tags: [Traffic Inspector]
summary: Remove a custom capture host
parameters:
- name: host
in: path
required: true
schema:
type: string
responses:
"204":
description: Host removed
patch:
tags: [Traffic Inspector]
summary: Toggle enabled state of a custom host
parameters:
- name: host
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [enabled]
properties:
enabled:
type: boolean
responses:
"200":
description: Host updated
/api/tools/traffic-inspector/capture-modes:
get:
tags: [Traffic Inspector]
summary: Get state of all 4 capture modes
responses:
"200":
description: Capture modes state
content:
application/json:
schema:
$ref: "#/components/schemas/InspectorCaptureModesState"
/api/tools/traffic-inspector/capture-modes/http-proxy:
post:
tags: [Traffic Inspector]
summary: Start or stop the HTTP_PROXY listener (port 8080)
description: SPAWN_CAPABLE — spawns a net.Server listener.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/InspectorCaptureModeAction"
responses:
"200":
description: Action executed
"409":
description: Port conflict (EADDRINUSE) when starting
/api/tools/traffic-inspector/capture-modes/system-proxy:
post:
tags: [Traffic Inspector]
summary: Apply or revert system-wide proxy settings
description: SPAWN_CAPABLE — executes networksetup/gsettings/netsh. Requires admin.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/InspectorSystemProxyAction"
responses:
"200":
description: System proxy updated
"500":
description: OS command failed (permission error)
/api/tools/traffic-inspector/capture-modes/tls-intercept:
post:
tags: [Traffic Inspector]
summary: Toggle TLS body decryption in HTTP_PROXY mode
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/InspectorTlsInterceptToggle"
responses:
"200":
description: TLS intercept mode updated
/api/tools/traffic-inspector/sessions:
get:
tags: [Traffic Inspector]
summary: List all saved recording sessions
responses:
"200":
description: Sessions list
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/InspectorSession"
post:
tags: [Traffic Inspector]
summary: Start a new recording session
requestBody:
required: false
content:
application/json:
schema:
$ref: "#/components/schemas/InspectorSessionStart"
responses:
"201":
description: Session started
content:
application/json:
schema:
$ref: "#/components/schemas/InspectorSession"
/api/tools/traffic-inspector/sessions/{id}:
get:
tags: [Traffic Inspector]
summary: Get session snapshot (all captured requests)
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: Session with embedded requests
"404":
description: Session not found
patch:
tags: [Traffic Inspector]
summary: Stop or rename a recording session
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/InspectorSessionPatch"
responses:
"200":
description: Session updated
delete:
tags: [Traffic Inspector]
summary: Delete a recording session
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
"204":
description: Session deleted
/api/tools/traffic-inspector/sessions/{id}/export.har:
get:
tags: [Traffic Inspector]
summary: Export a recorded session as HAR 1.2
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: HAR file for this session
content:
application/json:
schema:
type: object
description: HAR 1.2 format
"404":
description: Session not found
/api/tools/traffic-inspector/internal/ingest:
post:
tags: [Traffic Inspector]
summary: Internal ingest endpoint for server.cjs passthrough path
description: >-
Accepts a serialized InterceptedRequest from the CJS MITM server for requests
that do not go through TypeScript handlers (e.g., passthrough hosts). Requires
INSPECTOR_INTERNAL_INGEST_TOKEN header. LOCAL_ONLY.
security: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/InterceptedRequest"
responses:
"204":
description: Ingested
"401":
description: Invalid or missing ingest token
# ─── OpenAPI Spec ──────────────────────────────────────────────
/api/openapi/spec:
get:
tags: [System]
summary: Get OpenAPI specification catalog
description: >-
Returns a structured JSON catalog parsed from this `openapi.yaml`,
including info, servers, tags, schemas, and a flat list of endpoints
(method, path, tags, summary, security, parameters, responses).
Used by the in-app API explorer.
responses:
"200":
description: Parsed OpenAPI catalog
content:
application/json:
schema:
type: object
properties:
info:
type: object
servers:
type: array
items:
type: object
tags:
type: array
items:
type: object
endpoints:
type: array
items:
type: object
properties:
method:
type: string
path:
type: string
tags:
type: array
items:
type: string
summary:
type: string
description:
type: string
security:
type: boolean
parameters:
type: array
items:
type: object
requestBody:
type: boolean
responses:
type: array
items:
type: string
schemas:
type: array
items:
type: string
"404":
description: openapi.yaml file not found on disk
"500":
description: Failed to parse OpenAPI spec
# ─── Agent Skills Catalog ────────────────────────────────────────────────────
/api/agent-skills:
get:
tags: [Agent Skills]
summary: List agent skills catalog
description: |
Returns the full 42-entry Agent Skills catalog with optional filtering.
Skills describe how to use OmniRoute's REST API and CLI — they are structured
SKILL.md documentation files discoverable by external agents, MCP clients, and
A2A orchestrators. No authentication required.
parameters:
- name: category
in: query
required: false
schema:
type: string
enum: [api, cli]
description: Filter by category (api = REST API skills, cli = CLI skills)
- name: area
in: query
required: false
schema:
type: string
description: Filter by area slug (e.g. "providers", "models", "cli-serve")
responses:
"200":
description: Catalog list
content:
application/json:
schema:
type: object
required: [skills, count, coverage]
properties:
skills:
type: array
items:
$ref: "#/components/schemas/AgentSkill"
count:
type: integer
coverage:
$ref: "#/components/schemas/SkillCoverage"
"400":
$ref: "#/components/responses/BadRequest"
"500":
$ref: "#/components/responses/InternalError"
/api/agent-skills/{id}:
get:
tags: [Agent Skills]
summary: Get a single agent skill
description: |
Returns metadata for a single agent skill by its canonical ID
(e.g. `omni-providers`, `cli-serve`). No authentication required.
parameters:
- name: id
in: path
required: true
schema:
type: string
pattern: "^[a-z][a-z0-9-]*$"
description: Canonical skill ID
example: omni-providers
responses:
"200":
description: Agent skill metadata
content:
application/json:
schema:
$ref: "#/components/schemas/AgentSkill"
"400":
$ref: "#/components/responses/BadRequest"
"404":
$ref: "#/components/responses/NotFound"
"500":
$ref: "#/components/responses/InternalError"
/api/agent-skills/{id}/raw:
get:
tags: [Agent Skills]
summary: Get raw SKILL.md content
description: |
Returns the SKILL.md content for a skill as `text/markdown`.
Resolution order: local filesystem `skills/{id}/SKILL.md` → GitHub raw URL (1-hour cache).
No authentication required.
parameters:
- name: id
in: path
required: true
schema:
type: string
pattern: "^[a-z][a-z0-9-]*$"
description: Canonical skill ID
example: omni-providers
responses:
"200":
description: SKILL.md content as Markdown
headers:
X-Skill-Source:
schema:
type: string
enum: [filesystem, github, generated]
description: Where the content was loaded from
X-Skill-Fetched-At:
schema:
type: string
format: date-time
description: ISO timestamp of when the content was fetched
Cache-Control:
schema:
type: string
description: "public, max-age=3600"
content:
text/markdown:
schema:
type: string
"400":
$ref: "#/components/responses/BadRequest"
"404":
$ref: "#/components/responses/NotFound"
"502":
description: Upstream GitHub fetch failed
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"500":
$ref: "#/components/responses/InternalError"
/api/agent-skills/coverage:
get:
tags: [Agent Skills]
summary: Get SKILL.md coverage stats
description: |
Returns how many of the 22 API skills and 20 CLI skills have SKILL.md
files on the local filesystem vs the catalog totals. No authentication required.
responses:
"200":
description: Coverage stats
content:
application/json:
schema:
$ref: "#/components/schemas/SkillCoverage"
"500":
$ref: "#/components/responses/InternalError"
/api/agent-skills/generate:
post:
tags: [Agent Skills]
summary: Trigger SKILL.md generator
description: |
Runs the Agent Skills generator which writes `skills/{id}/SKILL.md` for
all 42 catalog entries (or a subset via `onlyIds`). Preserves
`<!-- skill:custom-start --> ... <!-- skill:custom-end -->` blocks.
**Requires management authentication.**
security:
- BearerAuth: []
- ManagementSessionAuth: []
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
dryRun:
type: boolean
default: true
description: "If true, reports what would be generated without writing files"
prune:
type: boolean
default: false
description: "If true, deletes skill directories not in the catalog"
onlyIds:
type: array
items:
type: string
description: "If provided, only regenerate these skill IDs"
responses:
"200":
description: Generator report
content:
application/json:
schema:
type: object
required: [generated, unchanged, pruned, orphansDetected, errors]
properties:
generated:
type: array
items:
type: string
description: IDs that got new/updated SKILL.md
unchanged:
type: array
items:
type: string
description: IDs whose content was already up to date
pruned:
type: array
items:
type: string
description: IDs whose directories were deleted (prune mode)
orphansDetected:
type: array
items:
type: string
description: Directories found in skills/ not in the catalog
errors:
type: array
items:
type: object
required: [id, error]
properties:
id:
type: string
error:
type: string
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"500":
$ref: "#/components/responses/InternalError"
"503":
description: Generator module not available
/api/v1/ocr:
post:
tags:
- Images
summary: Document OCR
description: >-
Mistral OCRcompatible document OCR endpoint. Accepts a JSON body
referencing a document/image and returns extracted text. Success
responses carry the `X-OmniRoute-*` cost-telemetry headers.
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
model:
type: string
document:
type: object
responses:
"200":
description: OCR result with extracted text.
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/audio/translations:
post:
tags:
- Audio
summary: Translate audio to English
description: >-
OpenAI Whispercompatible audio translation (multipart/form-data).
Unlike `/api/v1/audio/transcriptions`, output is always English
regardless of the source language. Success responses carry the
`X-OmniRoute-*` cost-telemetry headers.
security:
- BearerAuth: []
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required:
- file
properties:
file:
type: string
format: binary
model:
type: string
responses:
"200":
description: English translation of the audio.
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/providers/suggested-models:
get:
tags:
- Providers
summary: Suggested media models
description: >-
Read-only server-side proxy to the public HuggingFace Hub models search
API, used by the dashboard to suggest models for a media provider kind
without exposing an HF token client-side. Never accepts or returns
credentials.
parameters:
- name: type
in: query
schema:
type: string
description: Media kind to search for (e.g. `image`, `audio`, `video`).
responses:
"200":
description: List of suggested HuggingFace Hub models.
"500":
$ref: "#/components/responses/InternalError"
/api/v1/provider-plugin-manifest:
get:
tags:
- Providers
summary: Provider plugin manifest
description: Returns the manifest describing installed provider plugins.
responses:
"200":
description: Provider plugin manifest.
"500":
$ref: "#/components/responses/InternalError"
/api/keys/{id}/devices:
get:
tags:
- API Keys
summary: List devices for an API key
description: >-
Lists the distinct devices (masked IP + User-Agent fingerprints)
tracked for an API key by the in-memory device tracker. IPs are masked
before storage; the route never sees the raw client IP.
x-internal: true
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
"200":
description: Distinct devices seen for the API key.
"401":
$ref: "#/components/responses/ManagementAuthenticationRequired"
"404":
$ref: "#/components/responses/NotFound"
/api/settings/purge-usage-history:
post:
tags:
- Settings
summary: Purge usage history
description: Dashboard-only. Purges stored usage-history records.
x-internal: true
responses:
"200":
description: Usage history purged.
"401":
$ref: "#/components/responses/Unauthorized"
/api/oauth/codex/import-token:
post:
tags:
- OAuth
summary: Import a Codex connection from a bare access token
description: >-
Dashboard-only. Creates a Codex (ChatGPT/OpenAI) connection from a raw
access token with no refresh token (authType `access_token`).
x-internal: true
responses:
"200":
description: Connection imported.
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
/api/cli-tools/crush-settings:
get:
tags:
- CLI Tools
summary: Read Crush CLI OmniRoute config
description: Local-only. Reads the OmniRoute provider block in Crush's config.
x-internal: true
responses:
"200":
description: Current Crush config state.
post:
tags:
- CLI Tools
summary: Write Crush CLI OmniRoute config
description: Local-only. Registers OmniRoute as an `openai-compat` provider in Crush's config.
x-internal: true
responses:
"200":
description: Crush config updated.
delete:
tags:
- CLI Tools
summary: Remove OmniRoute from Crush CLI config
description: Local-only. Removes the OmniRoute provider block from Crush's config.
x-internal: true
responses:
"200":
description: Crush config entry removed.
/api/cli-tools/codewhale-settings:
get:
tags:
- CLI Tools
summary: Read CodeWhale CLI OmniRoute config
description: >-
Local-only. Reads the OmniRoute config block from
`~/.codewhale/config.toml` (with `~/.deepseek/config.toml` legacy
fallback).
x-internal: true
responses:
"200":
description: Current CodeWhale config state.
post:
tags:
- CLI Tools
summary: Write CodeWhale CLI OmniRoute config
description: Local-only. Writes the OmniRoute config block in CodeWhale TOML format.
x-internal: true
responses:
"200":
description: CodeWhale config updated.
delete:
tags:
- CLI Tools
summary: Remove OmniRoute from CodeWhale CLI config
description: Local-only. Removes the OmniRoute config block from CodeWhale's config.
x-internal: true
responses:
"200":
description: CodeWhale config entry removed.
components:
securitySchemes:
BearerAuth:
type: http
scheme: bearer
description: API key obtained from the OmniRoute dashboard
ManagementSessionAuth:
type: apiKey
in: cookie
name: auth_token
description: Dashboard management session cookie for protected management routes
parameters:
ResourceId:
name: id
in: path
required: true
schema:
type: string
responses:
Unauthorized:
description: Missing or invalid API key
content:
application/json:
schema:
type: object
properties:
error:
type: string
example: Unauthorized
ManagementAuthenticationRequired:
description: Authentication required for management routes
content:
application/json:
schema:
$ref: "#/components/schemas/ApiErrorResponse"
example:
error:
message: Authentication required
type: invalid_request
requestId: 3f9f6f5a-509a-4b35-b0a7-2d2d99d73a01
ManagementInvalidToken:
description: Bearer tokens are not accepted for management routes
content:
application/json:
schema:
$ref: "#/components/schemas/ApiErrorResponse"
example:
error:
message: Invalid management token
type: invalid_request
requestId: 1b6a6ff8-d60c-4900-8d0a-25f81749f0a3
ValidationError:
description: Request body failed validation
content:
application/json:
schema:
$ref: "#/components/schemas/ValidationErrorResponse"
BadRequest:
description: The request was malformed or failed validation
content:
application/json:
schema:
$ref: "#/components/schemas/ApiErrorResponse"
example:
error:
message: Invalid request
type: invalid_request_error
requestId: 8c2b1d44-7a3e-4c91-9b0f-1e2d3c4b5a60
NotFound:
description: The requested resource was not found
content:
application/json:
schema:
$ref: "#/components/schemas/ApiErrorResponse"
example:
error:
message: Resource not found
type: not_found_error
requestId: 4d5e6f70-1a2b-3c4d-5e6f-7a8b9c0d1e2f
InternalError:
description: An unexpected server error occurred
content:
application/json:
schema:
$ref: "#/components/schemas/ApiErrorResponse"
example:
error:
message: Internal server error
type: api_error
requestId: 0a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d
schemas:
PlaygroundPreset:
type: object
required:
- id
- name
- endpoint
- model
- params
- created_at
properties:
id:
type: string
format: uuid
name:
type: string
maxLength: 100
endpoint:
type: string
description: Playground endpoint key (e.g. "chat.completions")
model:
type: string
system:
type: string
nullable: true
params:
type: object
additionalProperties: true
description: Serialized parameter values (temperature, max_tokens, etc.)
created_at:
type: string
format: date-time
PlaygroundPresetCreate:
type: object
required:
- name
- endpoint
- model
properties:
name:
type: string
minLength: 1
maxLength: 100
endpoint:
type: string
minLength: 1
model:
type: string
minLength: 1
system:
type: string
nullable: true
params:
type: object
additionalProperties: true
default: {}
MemoryEntry:
type: object
description: A single persisted memory entry
properties:
id:
type: string
description: UUID
apiKeyId:
type: string
sessionId:
type: string
nullable: true
type:
type: string
enum:
- factual
- episodic
- procedural
- semantic
key:
type: string
description: Stable upsert key (e.g. preference:i_prefer_python)
content:
type: string
metadata:
type: object
additionalProperties: true
createdAt:
type: string
format: date-time
updatedAt:
type: string
format: date-time
expiresAt:
type: string
format: date-time
nullable: true
needsReindex:
type: integer
description: 1 if the vector for this memory is stale or missing
MemorySettingsExtended:
type: object
description: Extended memory settings including 7 new fields from plan 21. All fields are optional for PUT (patch semantics).
properties:
enabled:
type: boolean
maxTokens:
type: integer
minimum: 0
maximum: 16000
retentionDays:
type: integer
minimum: 1
maximum: 365
strategy:
type: string
enum:
- recent
- semantic
- hybrid
skillsEnabled:
type: boolean
embeddingSource:
type: string
enum:
- remote
- static
- transformers
- auto
description: Which embedding source to use. "auto" = remote > static > transformers.
embeddingProviderModel:
type: string
nullable: true
description: Embedding provider/model in "provider/model" format (e.g. openai/text-embedding-3-small).
transformersEnabled:
type: boolean
description: Opt-in for Transformers.js local MiniLM model (~400MB RAM)
staticEnabled:
type: boolean
description: Opt-in for static potion-base-8M local model
rerankEnabled:
type: boolean
description: Enable reranking step (+200-500ms/req)
rerankProviderModel:
type: string
nullable: true
description: Rerank provider/model in "provider/model" format
vectorStore:
type: string
enum:
- sqlite-vec
- qdrant
- auto
description: Which vector backend to use
QdrantSettings:
type: object
description: Qdrant vector database configuration (read shape — no raw apiKey)
properties:
enabled:
type: boolean
host:
type: string
port:
type: integer
minimum: 1
maximum: 65535
collection:
type: string
embeddingModel:
type: string
hasApiKey:
type: boolean
apiKeyMasked:
type: string
nullable: true
description: First 4 chars of the configured API key, or null
QdrantHealthResult:
type: object
description: Result of a Qdrant liveness probe
properties:
ok:
type: boolean
latencyMs:
type: number
error:
type: string
nullable: true
description: Sanitized error message (no stack traces)
AgentSkill:
type: object
description: >-
Single entry in the Agent Skills catalog. Describes one OmniRoute REST API surface
(category: api) or CLI subcommand group (category: cli) with a canonical ID and a
link to its SKILL.md documentation file.
required: [id, name, description, category, area, rawUrl, githubUrl]
properties:
id:
type: string
pattern: "^[a-z][a-z0-9-]*$"
description: Canonical skill ID (e.g. "omni-providers", "cli-serve")
example: omni-providers
name:
type: string
minLength: 1
maxLength: 100
description: Human-readable skill name
example: Provider Management
description:
type: string
minLength: 1
maxLength: 2000
description: One-paragraph description of what the skill covers
category:
type: string
enum: [api, cli]
description: "api = REST API skill; cli = CLI subcommand skill"
area:
type: string
minLength: 1
maxLength: 50
description: Functional area slug (e.g. "providers", "combos-routing", "cli-serve")
example: providers
endpoints:
type: array
items:
type: string
description: REST API endpoints (present for api-category skills only)
example: ["POST /api/providers", "GET /api/providers/:id"]
cliCommands:
type: array
items:
type: string
description: CLI subcommand names (present for cli-category skills only)
example: ["providers list", "providers test", "providers rotate"]
icon:
type: string
description: Material symbol icon name for dashboard display
isEntry:
type: boolean
description: Whether this is a recommended starting point
isNew:
type: boolean
description: Whether this skill was added in a recent release
rawUrl:
type: string
format: uri
description: GitHub raw URL of the SKILL.md file
example: "https://raw.githubusercontent.com/diegosouzapw/OmniRoute/refs/heads/main/skills/omni-providers/SKILL.md"
githubUrl:
type: string
format: uri
description: GitHub blob URL for viewing the SKILL.md in the browser
example: "https://github.com/diegosouzapw/OmniRoute/blob/main/skills/omni-providers/SKILL.md"
SkillCoverage:
type: object
description: >-
Coverage statistics for the Agent Skills catalog: how many of the 22 REST API
skills and 20 CLI skills have generated SKILL.md files on the local filesystem.
required: [api, cli, totalSkills, generatedAt]
properties:
api:
type: object
required: [have, total]
properties:
have:
type: integer
minimum: 0
maximum: 22
description: Number of API skills with SKILL.md on disk
total:
type: integer
enum: [22]
description: Canonical API skill count (always 22)
cli:
type: object
required: [have, total]
properties:
have:
type: integer
minimum: 0
maximum: 20
description: Number of CLI skills with SKILL.md on disk
total:
type: integer
enum: [20]
description: Canonical CLI skill count (always 20)
totalSkills:
type: integer
minimum: 0
maximum: 42
description: Sum of api.have + cli.have
generatedAt:
type: string
format: date-time
description: ISO datetime when coverage was last computed
ErrorResponse:
type: object
description: Standard error response body
required: [error]
properties:
error:
type: object
required: [message]
properties:
message:
type: string
description: Human-readable error message (never includes stack traces)
code:
type: string
description: Machine-readable error code
# ─── AgentBridge Schemas ────────────────────────────────────────
AgentId:
type: string
enum:
- antigravity
- kiro
- copilot
- codex
- cursor
- zed
- claude-code
- open-code
- trae
description: One of the 9 supported IDE agents
AgentBridgeAgentState:
type: object
description: Per-agent MITM state
properties:
agent_id:
$ref: "#/components/schemas/AgentId"
dns_enabled:
type: boolean
cert_trusted:
type: boolean
setup_completed:
type: boolean
last_started_at:
type: string
format: date-time
nullable: true
last_error:
type: string
nullable: true
AgentBridgeServerState:
type: object
description: Global AgentBridge MITM server state
properties:
running:
type: boolean
port:
type: integer
example: 443
certReady:
type: boolean
interceptedCount:
type: integer
activeConnections:
type: integer
lastStartedAt:
type: string
format: date-time
nullable: true
AgentBridgeServerAction:
type: object
required: [action]
properties:
action:
type: string
enum: [start, stop, restart, trust-cert, regenerate-cert]
AgentBridgeDnsAction:
type: object
required: [enabled]
properties:
enabled:
type: boolean
AgentBridgeMappingRow:
type: object
properties:
agent_id:
$ref: "#/components/schemas/AgentId"
source_model:
type: string
example: gpt-4o
target_model:
type: string
example: claude-sonnet-4.7
updated_at:
type: string
format: date-time
AgentBridgeMappingPut:
type: object
required: [mappings]
properties:
mappings:
type: array
items:
type: object
required: [source, target]
properties:
source:
type: string
example: gpt-4o
target:
type: string
example: claude-sonnet-4.7
AgentBridgeBypassRow:
type: object
properties:
pattern:
type: string
example: "*.bank.*"
source:
type: string
enum: [default, user]
created_at:
type: string
format: date-time
AgentBridgeBypassUpsert:
type: object
required: [patterns]
properties:
patterns:
type: array
items:
type: string
example: ["*.bank.*", "*.gov.*"]
AgentBridgeUpstreamCaPost:
type: object
required: [path]
properties:
path:
type: string
description: Absolute path to a PEM file for corporate upstream CA
example: "/etc/ssl/certs/corporate-ca.pem"
# ─── Traffic Inspector Schemas ──────────────────────────────────
CaptureSource:
type: string
enum: [agent-bridge, custom-host, http-proxy, system-proxy]
DetectedKind:
type: string
enum: [llm, app, unknown]
InterceptedRequest:
type: object
description: A single intercepted HTTP request captured by the Traffic Inspector
required:
[
id,
source,
timestamp,
method,
host,
path,
requestHeaders,
requestSize,
responseHeaders,
responseSize,
status,
]
properties:
id:
type: string
format: uuid
source:
$ref: "#/components/schemas/CaptureSource"
agent:
$ref: "#/components/schemas/AgentId"
timestamp:
type: string
format: date-time
method:
type: string
example: POST
host:
type: string
example: api.githubcopilot.com
path:
type: string
example: /v1/chat/completions
requestHeaders:
type: object
additionalProperties:
type: string
requestBody:
type: string
nullable: true
description: Masked (secrets replaced with ***)
requestSize:
type: integer
responseHeaders:
type: object
additionalProperties:
type: string
responseBody:
type: string
nullable: true
responseSize:
type: integer
status:
oneOf:
- type: integer
- type: string
enum: [in-flight, error]
proxyLatencyMs:
type: number
nullable: true
upstreamLatencyMs:
type: number
nullable: true
totalLatencyMs:
type: number
nullable: true
error:
type: string
nullable: true
description: Sanitized error message (no stack traces)
sourceModel:
type: string
nullable: true
mappedModel:
type: string
nullable: true
detectedKind:
$ref: "#/components/schemas/DetectedKind"
contextKey:
type: string
nullable: true
description: 12-char SHA-256 hex of the system prompt (for conversation grouping)
example: a3f9c2b1d5e4
annotation:
type: string
nullable: true
sessionId:
type: string
format: uuid
nullable: true
note:
type: string
nullable: true
description: Informational note (e.g. TLS tunnel metadata)
InspectorCustomHost:
type: object
properties:
host:
type: string
example: api.openai.com
enabled:
type: boolean
label:
type: string
nullable: true
kind:
type: string
enum: [llm, app, custom]
added_at:
type: string
format: date-time
last_seen_at:
type: string
format: date-time
nullable: true
InspectorCustomHostCreate:
type: object
required: [host]
properties:
host:
type: string
minLength: 1
example: my-internal-llm.company.com
enabled:
type: boolean
default: true
label:
type: string
nullable: true
kind:
type: string
enum: [llm, app, custom]
default: custom
InspectorCaptureModesState:
type: object
properties:
agentBridge:
type: object
properties:
active:
type: boolean
customHosts:
type: object
properties:
active:
type: boolean
count:
type: integer
httpProxy:
type: object
properties:
active:
type: boolean
port:
type: integer
example: 8080
systemProxy:
type: object
properties:
active:
type: boolean
guardMinutes:
type: integer
InspectorCaptureModeAction:
type: object
required: [action]
properties:
action:
type: string
enum: [start, stop]
InspectorSystemProxyAction:
type: object
required: [action]
properties:
action:
type: string
enum: [apply, revert]
port:
type: integer
minimum: 1
maximum: 65535
example: 8080
guardMinutes:
type: integer
minimum: 1
example: 30
InspectorTlsInterceptToggle:
type: object
required: [enabled]
properties:
enabled:
type: boolean
InspectorAnnotationPut:
type: object
required: [annotation]
properties:
annotation:
type: string
maxLength: 10000
InspectorSession:
type: object
properties:
id:
type: string
format: uuid
name:
type: string
nullable: true
started_at:
type: string
format: date-time
ended_at:
type: string
format: date-time
nullable: true
request_count:
type: integer
profile:
type: string
enum: [llm, custom, all]
nullable: true
InspectorSessionStart:
type: object
properties:
name:
type: string
example: "Antigravity test run #1"
InspectorSessionPatch:
type: object
required: [action]
properties:
action:
type: string
enum: [stop, rename]
name:
type: string
QuotaPool:
type: object
description: A quota sharing pool — binds a provider connection to allocation rules.
required: [id, connectionId, name, createdAt, allocations]
properties:
id:
type: string
connectionId:
type: string
name:
type: string
createdAt:
type: string
format: date-time
allocations:
type: array
items:
$ref: "#/components/schemas/PoolAllocation"
PoolAllocation:
type: object
required: [apiKeyId, weight, policy]
properties:
apiKeyId:
type: string
weight:
type: number
minimum: 0
maximum: 100
description: Share percentage (0100)
capValue:
type: number
nullable: true
description: Absolute cap value (optional)
capUnit:
type: string
enum: [percent, requests, tokens, usd]
nullable: true
policy:
type: string
enum: [hard, soft, burst]
PoolCreate:
type: object
required: [connectionId, name]
properties:
connectionId:
type: string
name:
type: string
maxLength: 120
allocations:
type: array
items:
$ref: "#/components/schemas/PoolAllocation"
default: []
PoolUpdate:
type: object
properties:
name:
type: string
maxLength: 120
allocations:
type: array
items:
$ref: "#/components/schemas/PoolAllocation"
PoolUsageSnapshot:
type: object
required: [poolId, generatedAt, dimensions]
properties:
poolId:
type: string
generatedAt:
type: string
format: date-time
dimensions:
type: array
items:
type: object
properties:
unit:
type: string
enum: [percent, requests, tokens, usd]
window:
type: string
enum: ["5h", hourly, daily, weekly, monthly]
limit:
type: number
consumedTotal:
type: number
perKey:
type: array
items:
type: object
properties:
apiKeyId:
type: string
consumed:
type: number
fairShare:
type: number
deficit:
type: number
description: "Negative = surplus; positive = over-allocation"
borrowing:
type: boolean
burnRate:
type: object
nullable: true
properties:
tokensPerSecond:
type: number
timeToExhaustionMs:
type: number
nullable: true
QuotaDimension:
type: object
required: [unit, window, limit]
properties:
unit:
type: string
enum: [percent, requests, tokens, usd]
window:
type: string
enum: ["5h", hourly, daily, weekly, monthly]
limit:
type: number
minimum: 0
PlanUpsert:
type: object
required: [dimensions]
properties:
dimensions:
type: array
minItems: 1
items:
$ref: "#/components/schemas/QuotaDimension"
QuotaStoreSettings:
type: object
required: [driver]
properties:
driver:
type: string
enum: [sqlite, redis]
redisUrl:
type: string
format: uri
nullable: true
description: Redis connection URL (write-only; masked in GET responses)
ServiceStatus:
type: object
description: Live supervisor state for an embedded service
properties:
tool:
type: string
example: 9router
state:
type: string
enum: [not_installed, stopped, starting, running, stopping, error]
pid:
type: integer
nullable: true
port:
type: integer
example: 20130
health:
type: string
enum: [unknown, healthy, degraded]
startedAt:
type: string
format: date-time
nullable: true
lastError:
type: string
nullable: true
ServiceStatusExtended:
allOf:
- $ref: "#/components/schemas/ServiceStatus"
- type: object
description: >-
Extended status including version metadata and (for 9Router) API key preview.
properties:
installedVersion:
type: string
nullable: true
latestVersion:
type: string
nullable: true
updateAvailable:
type: boolean
apiKeyMasked:
type: string
nullable: true
description: >-
Masked API key preview (e.g. "nr_****abcd").
Present only for services that use an injected API key (9Router).
autoStart:
type: boolean
providerExpose:
type: boolean
description: >-
Whether models from this service are exposed as a routing provider.
9Router only.
ApiErrorResponse:
type: object
properties:
error:
type: object
properties:
message:
type: string
type:
type: string
details:
description: Optional additional error details
requestId:
type: string
format: uuid
ValidationErrorResponse:
type: object
properties:
error:
type: object
required: [message, details]
properties:
message:
type: string
example: Invalid request
details:
type: array
items:
type: object
required: [field, message]
properties:
field:
type: string
message:
type: string
PayloadRuleModelSpec:
type: object
additionalProperties: false
required: [name]
properties:
name:
type: string
minLength: 1
protocol:
type: string
minLength: 1
PayloadMutationRule:
type: object
additionalProperties: false
required: [models, params]
properties:
models:
type: array
minItems: 1
items:
$ref: "#/components/schemas/PayloadRuleModelSpec"
params:
type: object
minProperties: 1
additionalProperties: true
PayloadFilterRule:
type: object
additionalProperties: false
required: [models, params]
properties:
models:
type: array
minItems: 1
items:
$ref: "#/components/schemas/PayloadRuleModelSpec"
params:
type: array
minItems: 1
items:
type: string
minLength: 1
PayloadRulesConfig:
type: object
additionalProperties: false
required: [default, override, filter, defaultRaw]
properties:
default:
type: array
items:
$ref: "#/components/schemas/PayloadMutationRule"
override:
type: array
items:
$ref: "#/components/schemas/PayloadMutationRule"
filter:
type: array
items:
$ref: "#/components/schemas/PayloadFilterRule"
defaultRaw:
type: array
items:
$ref: "#/components/schemas/PayloadMutationRule"
UpdatePayloadRulesRequest:
type: object
additionalProperties: false
description: At least one payload-rules section must be present in the request body.
properties:
default:
type: array
items:
$ref: "#/components/schemas/PayloadMutationRule"
override:
type: array
items:
$ref: "#/components/schemas/PayloadMutationRule"
filter:
type: array
items:
$ref: "#/components/schemas/PayloadFilterRule"
defaultRaw:
type: array
items:
$ref: "#/components/schemas/PayloadMutationRule"
default-raw:
type: array
items:
$ref: "#/components/schemas/PayloadMutationRule"
anyOf:
- required: [default]
- required: [override]
- required: [filter]
- required: [defaultRaw]
- required: [default-raw]
ChatCompletionRequest:
type: object
required: [model, messages]
properties:
model:
type: string
example: gpt-4o
messages:
type: array
items:
type: object
required: [role]
properties:
role:
type: string
description: >-
Message role. The proxy accepts any non-empty string; common values
include system, user, assistant, tool, function, and developer.
example: user
content:
description: >-
Message content. May be a plain string, an array of content parts
for multimodal inputs (text, image, audio, etc.), or null when the
message only carries tool/function calls.
oneOf:
- type: string
- type: array
items:
type: object
- type: "null"
name:
type: string
tool_call_id:
type: string
tool_calls:
type: array
items:
type: object
function_call:
type: object
stream:
type: boolean
default: false
temperature:
type: number
minimum: 0
maximum: 2
max_tokens:
type: integer
top_p:
type: number
minimum: 0
maximum: 1
n:
type: integer
minimum: 1
default: 1
stop:
description: Up to 4 stop sequences (string or array of strings).
oneOf:
- type: string
- type: array
items:
type: string
maxItems: 4
frequency_penalty:
type: number
minimum: -2
maximum: 2
presence_penalty:
type: number
minimum: -2
maximum: 2
seed:
type: integer
logprobs:
type: boolean
top_logprobs:
type: integer
minimum: 0
maximum: 20
response_format:
type: object
description: Output format constraint (e.g. JSON mode or JSON Schema).
properties:
type:
type: string
example: json_object
tools:
type: array
description: Tool definitions available to the model.
items:
type: object
tool_choice:
description: Controls which tool (if any) is invoked by the model.
oneOf:
- type: string
example: auto
- type: object
parallel_tool_calls:
type: boolean
default: true
service_tier:
type: string
example: auto
user:
type: string
description: Stable end-user identifier for abuse monitoring.
ChatCompletionResponse:
type: object
properties:
id:
type: string
object:
type: string
example: chat.completion
choices:
type: array
items:
type: object
properties:
index:
type: integer
message:
type: object
properties:
role:
type: string
content:
type: string
finish_reason:
type: string
usage:
type: object
properties:
prompt_tokens:
type: integer
completion_tokens:
type: integer
total_tokens:
type: integer
MessagesRequest:
type: object
required: [model, messages, max_tokens]
properties:
model:
type: string
example: claude-sonnet-4-5-20250514
messages:
type: array
items:
type: object
required: [role, content]
properties:
role:
type: string
enum: [user, assistant]
content:
type: string
max_tokens:
type: integer
stream:
type: boolean
default: false
system:
type: string
Model:
type: object
properties:
id:
type: string
object:
type: string
example: model
owned_by:
type: string
ProviderConnection:
type: object
properties:
id:
type: string
provider:
type: string
name:
type: string
url:
type: string
isActive:
type: boolean
maxConcurrent:
type: integer
nullable: true
minimum: 0
priority:
type: integer
testStatus:
type: string
enum: [active, error, untested]
createdAt:
type: string
format: date-time
ProviderConnectionCreate:
type: object
required: [provider, url]
properties:
provider:
type: string
example: openai
name:
type: string
url:
type: string
apiKey:
type: string
isActive:
type: boolean
default: true
maxConcurrent:
type: integer
nullable: true
minimum: 0
ApiKey:
type: object
properties:
id:
type: string
label:
type: string
keyPreview:
type: string
description: Last 4 characters of the key
isActive:
type: boolean
createdAt:
type: string
format: date-time
ComboCreate:
type: object
required: [name, model]
properties:
name:
type: string
model:
type: string
strategy:
type: string
enum:
- priority
- weighted
- round-robin
- context-relay
- fill-first
- p2c
- random
- least-used
- cost-optimized
- reset-aware
- reset-window
- headroom
- strict-random
- auto
- lkgp
- context-optimized
- fusion
default: priority
nodes:
type: array
items:
type: object
properties:
connectionId:
type: string
weight:
type: integer
priority:
type: integer