Concepts

Virtual keys (sk_live_…, sk_test_…)

What your callers send in Authorization: Bearer. Minted per workspace. Each key has:

  • An environment (live / test) — test keys are sandboxed for staging.
  • A scope (server / browser) — browser-scoped keys cannot pin version_id on prompts (use labels).
  • A revoke + rotate lifecycle, all audited under Key audit.

Never ship a virtual key to a public client unless its scope is browser and the workspace is locked down with allowed-models + budget caps.

Vault keys (vendor keys)

Real OpenAI / Anthropic / Google / Mistral / xAI / Groq / DeepSeek keys, stored encrypted in the vault. odnoga picks one per request via round-robin and marks exhausted keys cool-down on 429/credit errors. You manage these in Routing → Vault keys.

End users

The x-odnoga-end-user header (≤256 chars; the pre-rebrand x-airouter-end-user and the OpenAI-compatible user body field are also honoured). Drives:

  • Per-end-user metering — flushed to your own Stripe via tenant meter flush.
  • Top end users rollup on Overview.
  • A/B stickiness — same end user always lands in the same prompt-version bucket.

Budgets

Workspace → Budget. Daily / monthly cap in USD. odnoga blocks new requests at 100% with budget_exhausted (HTTP 402). At 80% and 100% it enqueues budget_80 / budget_100 alert events.

Tenant admins can also set a global tenant spend cap (Tenant admin → Spend cap) that applies across every workspace in the tenant. Both caps are checked on every request — the stricter one wins.

Allowed models

Optional allowlist per workspace. When set, requests for any other model return 400 model_not_allowed before any vendor call.

Cache

Opt-in per workspace (Cache card). Cache key is a SHA-256 of the normalized request body. Hits return instantly with x-airouter-cache: hit and don't consume vendor tokens (cost = 0). TTL is per entry, default 3600s.

Response shape (openai vs native)

By default every vendor's answer is translated into the OpenAI shape (choices, usage), so one client works against all of them. A workspace — or its tenant admin, who can lock the choice — can instead select raw vendor responses: you then receive the vendor's own body (Gemini candidates / usageMetadata, Claude content blocks). Never assume the shape: every response carries x-airouter-response-format (openai | native), and the _odnoga block plus the x-airouter-*-tokens / x-airouter-cost-usd headers are attached in both modes — read tokens and cost from those, never from the body. Streamed responses are always OpenAI-shaped, in either mode, because a raw vendor stream carries no usage frame to meter. Configure under Workspace → Models → Response format; see the headers reference.

Batch jobs

Large offline workloads (backfills, re-classification, bulk embeddings) submitted once to /v1/batches instead of looping /v1/chat/completions. When every item is eligible, odnoga hands the job to the vendor's own Batch API — OpenAI, Anthropic and Google are live at a genuine 50% discount, xAI at its own 20% (grok-4.3 and the grok-4.20 family only; the discount is the vendor's term, not ours) — and reports execution_mode: "vendor_batch", billing_tier: "batch". Anything not eligible runs on odnoga's internal queue at standard rates with fallback_reason stating exactly why: odnoga never bills a discount it did not obtain, and per item billed_tier in the results is what that row was actually charged. Track jobs in Workspace → Batch jobs, or over MCP with batches.list / batches.get / batches.cancel; the full contract (per-vendor endpoints, windows, caps) is in the MCP batches.guide tool.

Pricing (GET /v1/pricing)

The price list your workspace pays, margins included — the same numbers the gateway bills from, so an integration can cost a job before running it. Per model: prices per 1M tokens (input, cached input, output) plus per-unit media rates, the service tiers it can route through, and the region uplift. The top-level service_tiers array carries each vendor's tier multiplier — a tier marked delivered: false bills at 100% (standard) until odnoga genuinely obtains it. Authenticated with your API key; end-user tokens are refused — end users buy at the plans you publish, never at your workspace's own terms. The same prices appear per model in MCP models.list.

Prompts (managed)

Prompts have a slug, multiple versions, optional labels (e.g. production, staging). Call by slug + label and you can swap implementations without redeploying.

version_id overrides the label — banned for browser keys and EUT JWTs.

A/B experiments

A prompt can have multiple active versions weighted by traffic share. Bucket assignment is sticky per x-airouter-end-user so a user keeps seeing the same variant across requests.

Fallback

Routing → Failover policies. Ordered list of models per function. On a retryable error (timeout, 5xx, vendor 429), odnoga walks the list and the response carries x-airouter-fallback: 1 plus the actually-used x-airouter-model.

Model lifecycle

A model is active, may get an announced retirement date (still serving; every response carries Sunset / Deprecation headers and the replacement slug), and is then retired (model_deprecated on the old name). Nothing is deleted: history stays attributable. Organisation admins are told by email and in the app when a retirement is announced for a model they call, a week before, on the day, and daily while an integration still sends the old name.

Automatic fallback (Organisation → Defaults → Retired models) serves the catalog's replacement for the old name — same vendor only, allow-lists respected, billed as the model that served — and marks the response with x-odnoga-model-fallback. Off by default: the safe default for a gateway is to say no, not to guess.

Raw capture

Opt-in per workspace. When on, raw vendor request/response payloads are retained for the configured TTL. Use for debugging odd vendor behavior or compliance audits.

EUT (end-user tokens)

Short-lived JWTs your backend mints so a browser can call odnoga directly (without your virtual key leaking). EUTs always carry an end-user id and a budget. See Security.