Changelog

2026-10-17

  • Webhooks. odnoga now calls your app, signed, when a user's credits or your organisation's credit run low or out — so you can warn or top up without polling. Add them under Connect → Webhooks; every delivery is shown, failures are retried for 12 hours. See Webhooks.
  • Tests now say whether the answer is as good. When you test two or more models (route editor, Recommendations, routes.test), the first is the reference and a judge model scores every other answer against it — 0–10, better / as good / slightly worse / worse, and one sentence why — instead of only checking that a valid answer came back. A cheaper recommendation can now be approved only when its answer scored 7 or more against the route's current model. See Routes.
  • No more spending the same last dollar twice. While a request runs, its estimated cost is held from your organisation's credit (when you pay from credit) and from the end user's wallet (when wallets block at zero), so requests sent at the same moment can no longer all spend the last of a balance. When a request ends, the hold is replaced by the real charge, or given back if it failed. Image and audio requests now check end-user wallets too. See Per-user credits.
  • Read balances from the API. GET /v1/balance returns your organisation's credit (held and available) and, with ?end_user=, a user's wallet; an end-user token gets only its own wallet, so a browser app can show it directly. GET /v1/ledger?end_user= lists that user's grants, top-ups and spends. See Showing a balance.
  • Your JSON schemas fit every vendor. Tool parameters and json_schema answers are adapted to what the vendor serving the call accepts — for example, $ref definitions written out in place for a vendor that refuses $ref. What each vendor accepts is measured every week by sending each keyword on its own; a keyword the vendor accepts is never touched. Every change is reported in the new x-odnoga-dialect header and on the request in your log. When a fallback model answered, the parameters it could not take are now reported too (x-airouter-stripped), as they always were for the first model.

2026-10-16

  • One engine chooses every model. Every request — a model name, auto or a named route — is now planned by the Route engine, in every workspace. It was compared with the code it replaces on every live request for a week (8,000+ requests, no difference), so nothing you send behaves differently. The automatic chain and each model's fallbacks are stored as routes with versions and history, and routing.preview now answers from the same engine as the gateway, named routes included. Model families, which no workspace could use, are gone (x-airouter-family and the no_route_candidate error with them).
  • Allowed models now cover fallbacks too. A model outside a workspace's allowed list is no longer tried as a fallback — the restriction applies to every model a request can reach, as the Allowed models page says. (No workspace had an allowed list, so no traffic changed.)
  • Routes within limits. Set a ceiling (price per 1M tokens), the capabilities a route needs, vendors, and cheapest / fastest / newest — the editor shows what that means today, and odnoga keeps the route on the best fit. Every change is a recorded version with its reason, never above your limits, undoable from History. Extend and up; AI tools get routes.save_within_limits. See Routes.
  • Route recommendations, with your approval. For each named route odnoga proposes a replacement for a retiring model, a newer version of the same line, or a cheaper model of the same class — with the saving worked out on that route's own last 30 days. A cheaper model is approved only after it passed a test against the current one on your sample; every approval is a new version you can undo. Extend and up; AI tools get routes.recommendations. See Routes.
  • Test a route before you save it. The route editor sends one sample request to each model — a short answer, a tool call, a JSON answer, or your own — and shows the measured time, tokens, cost and whether tools were called with valid JSON, marking the fastest and cheapest. The most it can cost is shown before it runs. AI tools get routes.test. See Routes.
  • Cost by function. Usage by function now has the same cost report as the organisation view, one level down: what each function spent by day, week or month, its share of the workspace's spend, and — pick a function — which models it used and what each cost. Stacked, area, line or share-donut charts, CSV export, a year of history in an instant. The functions table gains a Share column and each function's models show their share of its cost. AI tools get usage.by_function, and functions.list rows carry cost_share_pct. See MCP.
  • Every share in the usage report. The organisation and platform usage tables show each row's share of the total next to its cost.

2026-10-13

  • Named routes are part of the Growth plan. Fallbacks and the automatic chain stay on every plan, Free included. Named routes, their versions and undo, and connecting a function to a route come with Growth and up. Nothing already made is taken away: on a plan without them, existing routes keep serving, read-only. See Routes.

2026-10-12

  • Routes: one name, the models behind it chosen in odnoga. Send "model": "odnoga/smart" and odnoga serves it from the models you picked, in your order, failing over between providers. Pick models from a list, drag them into order, see exactly what a request will do before saving, restore any earlier version. Every response says which route and version served it (x-odnoga-route, x-odnoga-route-version). See Routes.
  • Connect a route to a function — change a process's AI without a code change. Every call from a connected function is served by its route, whatever model its code sends; each route shows which functions it serves. Responses carry x-odnoga-route-connected.
  • No more typing model names. The automatic chain ("model": "auto") and per-model fallbacks are edited on the Routes page with the same picker; the old Routing rules page is gone and its address opens Routes.
  • AI tools (MCP): new routes.* tools — routes.guide first — so a connected assistant lists, previews, changes and connects routes with the same checks as the page.

2026-10-11

  • Gemini tools take the full JSON Schema you send. Tool definitions sent to Gemini models now keep additionalProperties, $ref/$defs and nullable types such as ["integer", "null"] — the shape OpenAI strict tools require — instead of being refused with a 400 ("Unknown name additionalProperties"). This completes the 2026-10-04 change, which covered structured outputs only. Nothing changes for tools that already worked.

2026-10-06

  • The cost on every response is what you are charged. x-airouter-cost-usd and _odnoga.cost_usd now report the full charged amount for the call — the model's price plus odnoga's fees (fees only when the call ran on your own vendor key) — the same figure your balance and the Requests page show. Previously they showed the model's price before fees, so totals added up from the header came out slightly low. A new x-airouter-cost-basis header (and _odnoga.cost_basis) says billed; in the rare case the charge cannot be confirmed in time it says list and the header shows the model's price only. See Headers.
  • A call odnoga cannot charge for correctly is stopped before it starts. Before a request is sent to a model, odnoga now checks that everything the request will use — tokens, an image, a web search — can be billed for that model. If it cannot, the call is not sent: you get 503 billing_unavailable with a ticket reference in x-odnoga-ticket, nothing is charged, and the odnoga team is alerted at once. With a fallback chain the next model simply answers. See Errors.
  • Gemini image models are billed per image. Images from gemini-*-image models are now counted and charged at the published per-image price.

2026-10-05

  • Docs now say exactly what the API does. A pass over the documentation, plan pages and MCP tool descriptions, correcting every place that promised more (or something different) than you get:
    • sk_test_ keys are not a sandbox. Test and live keys behave the same: both call the real vendor and both bill your organisation. The environment is a label — keep one key per environment so usage is attributed separately and each can be revoked on its own.
    • One base URL: https://api.odnoga.com/v1. Every example now uses it, with ODNOGA_API_KEY and ODNOGA_BASE_URL. The longer URLs from earlier examples keep working.
    • Errors lists the codes the gateway actually returns, with their real HTTP status and type — including the 429 limit codes (workspace_concurrency_limit, tenant_concurrency_limit, workspace_rpm_limit, workspace_tpm_limit, capacity_unavailable), the managed-prompt codes, and 409/422 for idempotency. Codes that were never returned are gone.
    • Timeouts: the model gets up to 300 seconds whether you stream or not; streaming avoids the ~150-second idle cut-off, it does not extend the deadline. x-airouter-timeout-ms (now documented) shortens it per request.
    • Idempotency is documented for non-streamed requests, under Idempotency-Key, x-odnoga-idempotency-key or x-airouter-idempotency-key, with the x-airouter-idempotent-replay header. Streams are not deduplicated.
    • Headers now cover the requests-per-minute, tokens-per-minute and organisation session headers, x-airouter-skipped-models and x-airouter-key-source. x-ratelimit-reset is only sent when a budget window exists. Browser callers can now read all of these, and can send x-odnoga-stream and the idempotency headers.
    • Plans: the Free plan no longer lists prompts (prompt management starts on Growth, and Growth now says so); the public status page is open to everyone, so it is no longer listed as a Growth feature; Enterprise SSO/SCIM, on-prem/VPC and audit-log export are marked on request.

2026-10-04

  • Final-only streaming — beat the ~150-second limit without parsing thousands of frames. Send x-odnoga-stream: final with "stream": true on /v1/chat/completions. The connection opens at once and stays alive with : keepalive comments every 15 seconds, so a Supabase Edge Function or any platform with an idle limit never times out waiting on a long reasoning call. When the answer is ready you receive one frame, the complete chat.completion with content, tool calls, usage and the _odnoga cost block, then [DONE]. Billing, logging and request ids are exactly the same as a normal call. The response carries x-odnoga-stream: final. See Streaming → Final-only mode.
  • Every model capability is now measured, not assumed. We test each capability a model lists (JSON mode, json_schema structured outputs, tools, image input) against the vendor itself, and a model can only be switched on once its capabilities are complete. Corrections from this pass: gpt-6.1-sol and claude-sonnet-4-5 now accept json_schema structured outputs; Perplexity sonar accepts json_schema; Gemini structured outputs now take the full JSON Schema you send (formats, $ref, additionalProperties) rather than a reduced subset; gpt-6.1-sol supports batch.
  • Tools on GPT-5.6 / GPT-6 models just work. OpenAI accepts tools on gpt-5.6-luna, gpt-5.6-sol and gpt-6-sol only with reasoning_effort: "none". Leave reasoning_effort out and odnoga sets it for you (reported on x-airouter-set); set a different value and you get a clear 400 required_with_tools instead of a vendor error.
  • Output caps work on Gemini. max_tokens (and max_completion_tokens) from an OpenAI SDK were not passed on to Gemini models, so a capped call could run to its natural length. They now become Gemini's own output cap, reported on x-airouter-renamed.
  • json_schema structured outputs work on every model that offers them, including models with no separate JSON mode (Perplexity Sonar). And as with OpenAI, a schema without "strict" is no longer refused for not meeting strict-mode rules: it is sent strict when it can be, non-strict otherwise. An explicit "strict": true is still checked in full.
  • Complete parameter rules for gemini-3.8-flash, grok-4.7 and perplexity/low. Parameters these models do not accept are removed and reported on x-airouter-stripped instead of reaching the vendor; temperature/top_p on Perplexity's perplexity/low preset are among them.
  • A model that cannot be served says so. Models that a vendor offers only through another API, or that are awaiting activation, now answer 400 model_not_serving with a plain explanation instead of a generic 503. Nine such models were switched off; models.list lists only models that serve.
  • Brief capacity limits are retryable. A request that arrives while odnoga is momentarily at capacity now gets 503 capacity_unavailable with Retry-After: 2 — which every official SDK retries automatically — instead of 500 internal. Nothing reaches a vendor and nothing is charged.
  • Repair headers readable from the browser. x-airouter-stripped, -clamped, -set, -renamed, x-odnoga-stream and Retry-After are now exposed to front-end code.
  • MCP: platform.endpoints documents keepalive and final-only streaming; platform.error_lookup explains capacity_unavailable, model_not_serving, missing_capability, required_with_tools and tool-forcing refusals.

2026-09-16

  • Tell odnoga when a stop is expected. Some stops are the job finishing rather than the job breaking — a backfill that has drained, a migration that has completed, a feature you retired. Mark the caller expected, from the alert or from the list, and it stops being reported. Not a mute: watching stays on, and by default the expectation lasts only until that caller calls again, so if the job ever runs and stops again you are still told. Give it a number of days instead when you are pausing something on purpose. Through MCP: traffic.expected.set / .clear.

  • Traffic health reads correctly on a new caller, and on longer windows. A caller's normal is the median of its own past windows, and windows from before that caller existed were being counted as silence — which dragged a young caller's normal towards zero and could report a busy new function as having too little traffic to judge, most visibly when comparing over a day rather than six hours. A caller's history now starts at its first call, and each one shows how many windows it has and when it was first seen, so "not judged yet" says when it will be. The last-call time is now the actual last call rather than the start of the window it fell in, and an alert carries the window it was judged at.

  • Traffic health — odnoga tells you when one of your callers stops. Every app and function you send through the gateway is measured against its own normal. If one stops calling, or suddenly calls far more than usual, you are told — with the number that is normal for it, how far off it is now, and, crucially, how many calls odnoga refused in the same window. When that number is zero we say so plainly: every request we received succeeded, so the change is on your side and you know where to look in the first minute. Under Workspace → Functions, organisation-wide under Tenant admin → Traffic health, and through MCP as traffic.health. Included on Extend and Enterprise; available as an add-on on Growth.

  • Tuned so it does not cry wolf: a caller is compared against the median of its own recent windows, must be under the threshold for two windows in a row before anyone is emailed, has a volume floor beneath which it is not judged at all, and a cooldown. You are told once when traffic recovers, so silence never has to be interpreted. Nothing is ever sent while an odnoga incident is open.

  • Concurrent sessions are now a thing you can see, size and buy. Tenant admin → Finance shows your pool, where each session came from (included with your plan, granted by odnoga, or purchased), what every workspace is using, and the busiest moment in the last 30 days — so you can tell "we need more" from "we peak at 5 of 100". Extra sessions go on your existing subscription, prorated; remove them the same way.

  • Going over your session pool no longer breaks your product. By default the call is served and the response carries x-odnoga-pool-exceeded plus a readable Warning header; nothing extra is charged. Your admins see it counted per day next to the smallest purchase that would have covered it. If you would rather be refused than served over your ceiling, ask support and we will enforce it for you.

  • A new error code, capacity_unavailable: odnoga itself is at capacity, not you. Retry after the Retry-After; nothing on your side will fix it and nothing you buy will either.

  • Batch traffic is now attributed. Jobs submitted with x-odnoga-app / x-odnoga-function carry that identity onto every request they produce, so batch work shows up in the functions list, in the request-log filters and in traffic health instead of pooling into "unattributed".

  • New doc: Concurrent sessions — what a session is, the two ceilings and which one bit, the 429 contract, and how to size a worker pool. Headers reference updated.

2026-08-21

  • Response shape is now a workspace choice: OpenAI-translated (default) or the raw vendor body, with a tenant-level default and lock. Every response carries x-airouter-response-format (openai | native); streamed responses are always OpenAI-shaped. Configure under Workspace → Models → Response format.
  • Token headers are the billing record: every non-streamed response carries x-airouter-prompt-tokens / x-airouter-completion-tokens / x-airouter-total-tokens alongside x-airouter-cost-usd — read usage from these, never by parsing the body (whose shape is now a setting).
  • Vendor batch routing: /v1/batches jobs are handed to the vendor's own Batch API at a genuine 50% discount — OpenAI, Anthropic and Google live. Honest billing semantics: billing_tier is what you are actually charged, fallback_reason says why a job lost the vendor route, fault + odnoga_note read the vendor's verdict for you, and per item billed_tier in the results is authoritative.
  • New Batch jobs page (Workspace → Batch jobs) showing status, execution mode, billed tier and fallback reason per job.
  • _odnoga echo hardened: adds vendor_model (the dated snapshot the vendor reports serving — provenance, never a pricing key). End-user token (eut_) responses are redacted: cost basis and vendor identity never reach your end-users.
  • Prepaid-always billing: usage is always debited from prepaid credits at your plan's margin (Free 6%, Growth 5%, Extend 4%); paid plans ($5/mo Growth, $49/mo Extend) are the base fee only and buy the lower margin. A lapsed base fee keeps plan rates for a 2-day grace window, then reverts to Free rates — credits never freeze.
  • Docs: headers reference, concepts, streaming and cost-control updated for all of the above.
  • xAI batch is live at the vendor's real 20% (grok-4.3 and the grok-4.20 family only — the discount is model-gated by xAI, not by odnoga; other models fall back to standard with the reason on the record). grok-4.3 (1M context) joined the catalogue.
  • New pricing API — GET /v1/pricing: the price list your workspace pays, margins included, with per-vendor service-tier multipliers and region uplifts. End-user tokens are refused; end users buy at your published plans.

2026-05-30

  • Docs rewrite: full restructure into Quickstart / Concepts / Best practices / SDKs / Ops / Headers / Errors / Troubleshooting.
  • New in-app Docs route at /workspace/:slug/odnoga/docs.
  • Promoted x-airouter-end-user to a first-class step.
  • Added callodnoga() shared-helper pattern.
  • Added per-SDK cookbooks for OpenAI Node/Python, Anthropic, LangChain, Vercel AI SDK, @odnoga/node.