Change two lines. Route every model.
public.supply is an OpenAI-compatible inference router that shows its work: the tier it picked, every provider it tried, and what the request actually cost.
The numbers we can stand behind
- lines you change
2
baseURL and apiKey. Nothing else moves.
- task classes measured
17
A closed vocabulary, extended only by amendment.
- models per multi-mode run
10
One prompt, one reservation, one stop button.
- conformance assertions
65
C1–C65. Reddening one rejects the pull request.
There are no traffic figures here because there is no traffic yet. Every number above is a property of the specification or the repository, and it is checkable — which is the only kind of number worth putting on a landing page.
Two lines, and your existing code is on the router.
The official openai SDKs for TypeScript and Python work against our endpoint with no change beyond baseURL and apiKey. Fields OpenAI accepts are accepted and never rejected. Everything of ours is additive: strip every Supply-specific field and the request still runs.
import OpenAI from "openai"; const client = new OpenAI({- baseURL: "https://api.openai.com/v1",- apiKey: process.env.OPENAI_API_KEY,+ baseURL: "https://public.supply/api/v1",+ apiKey: process.env.SUPPLY_API_KEY, }); const res = await client.chat.completions.create({ model: "anthropic/claude-sonnet-4.5", messages: [{ role: "user", content: "Summarise this." }], }); "model": "anthropic/claude-sonnet-4.5", "usage": { "total_tokens": 60, "cost": 0.000225 }, "supply": { "routing": { "servedProvider": "bedrock/us-east-1", "attempts": [ { "provider": "anthropic-direct", "error": "429", "ms": 120 }, { "provider": "bedrock/us-east-1", "ok": true, "ms": 80 } ] }, "cost": { "cogsMicros": 180, "chargedMicros": 225, "marginRuleId": "rule_default" } }What we do not claim, said here rather than in a support ticket: byte-identical completions, or OpenAI-account resources such as fine-tuning, assistants and vector stores. Those return a clearnot_supportedtelling you what to use instead — never a confusing 404. The claim is kept honest by a conformance suite that installs the real openai packages and runs a scripted corpus against our server on every merge.
Four things a gateway usually gets wrong.
A router that actually routes
Provenance, not opinion
The real cost, every time
Failures that tell the truth
Each of these is a test that blocks a merge.
The right-hand column is the assertion or invariant that holds the promise up. If a later feature conflicts with one of them, the contract wins.
| Contract | What it means | Held up by |
|---|---|---|
| Cancellation stops billing | A client disconnect aborts the upstream call and settles at the tokens actually produced. Where a provider cannot be aborted, the receipt says estimated_uncancellable instead of charging you as though it ran to completion. | C52 |
| No silent failover | Every attempt is enumerated in the response with its provider, its error and its milliseconds. Failover is allowed before the first byte, capped at three attempts. After the first byte, nothing switches. | C4 · I3 |
| A 401 is only ever a bad key | 401 is emitted only after a successful lookup returns a definitive answer. A failed cache, catalog or wallet read is 503 overloaded, classified twice — in the adapter and again at the edge. | C1 · C2 · I2 |
| We never invent a price | A catalog or pricing lookup that fails is a 503, not a silent default. Charging you against a guessed number is worse than failing the request. | I2 |
| Money is an integer | Integer micros of USD end to end, with one conversion point in the codebase. Rounding is always ceil when we charge you and always floor when we credit you. | I1 |
| A hard ceiling fails closed | max_price rejects candidates rather than overspending. Latency and throughput preferences only reorder them. The distinction is stated at every mention because guessing wrong about it shows up on an invoice. | C56 |
Tiers are computed, never typed.
A tier table someone wrote from memory in March is a liability by June. Ours is a versioned table of model against task class, where a model can be large for code generation and small for chitchat — which is the entire point, and something one global tier column cannot express.
Every metric carries source, method, measuredAt, sampleSize and modelVersion. Nothing is published yet: no cells are seeded, and until a cell has provenance it stays unknown and cannot influence routing. It is still available to anyone who asks for that model by name.
17 task classes
closed vocabulary- chitchat
- simple_qa
- classification
- extraction
- summarization
- translation
- rewriting
- structured_output
- code_generation
- code_review
- math_reasoning
- long_context_reasoning
- agentic_tool_use
- creative_writing
- vision_ocr
- vision_reasoning
- safety_sensitive
- qualityScore
- The tier axis.
- instructionFollowing
- A model that ignores format instructions is useless in a pipeline.
- jsonValidityRate
- Gates structured output outright.
- toolCallSuccessRate
- Gates agentic tool use — the most common real failure.
- refusalRate
- A cheap model that refuses is not cheap.
- contextDegradation
- Where accuracy actually falls off, well below the advertised window.
- p50TtftMs
- The latency term.
- p50TokensPerSec
- The throughput term.
- blendedCostPerMTokens
- The savings term.
One prompt, up to ten models, side by side.
Pick the targets, send the prompt once, and watch the columns stream as answers arrive rather than after the slowest model finishes. Attachments upload once and are shared across every leg.
One reservation
One stop button
One failure is not the run
- run.start
- stream.start
- stream.delta
- stream.usage
- stream.error
- stream.done
- run.done
Point an OpenAI client at it and read the receipt.
Sign in with Google, mint a key, and buy credits. The gateway is still being wired; the account, wallet and keys are live now.