Docs
Models
Name a model or let Autopilot choose, test models on your own cases and ask several at once.
Models and routingGET/v1/models
GET /v1/models lists every model with its price per million tokens, its context length and its max output. The 10 antimatter/auto ids are routing policies. Antimatter picks the model.
"model": "antimatter/auto" balanced
"model": "antimatter/auto:cheap" cheapest
"model": "antimatter/auto:fast" fastest
"model": "antimatter/auto:code" code
"model": "antimatter/auto:extract" extraction
"model": "antimatter/auto:reason" reasoning
"model": "antimatter/auto:long" long context
"model": "antimatter/auto:vision" vision
"model": "antimatter/auto:tools" tool calling
"model": "antimatter/auto:json" structured outputThe task modes use only what was measured or published: the catalog's context and image input, the capability probes for tool calls and JSON, and our own task probes for :code, :extract and :reason. Those probes are small fixed tasks run on a schedule: a function run against test cases in a sandbox, fields checked exactly, and a puzzle with one answer. A model with too few verdicts never qualifies, and the x-antimatter-route header names the evidence. On any Autopilot id, a request with an image needs a model that reads images, and a long request needs a context that fits it.
Give models as a list and Antimatter tries each in turn on a 429, a 5xx or a dropped connection. Send x-antimatter-fallback: comparable (or turn it on for the key). When that chain fails or its provider's breaker is open, the nearest model that costs no more on either price and can do what the request needs answers instead. x-antimatter-fallback names it. A 4xx from the provider is never retried. A saved preset works as a model id too: preset/<name>. The x-antimatter-model response header always names what answered.
Where the operator connects a provider directly (xAI, Mistral, Groq, Cerebras, Together or Fireworks), a model that provider serves at no more than the aggregator's price, on input and on output, goes to that provider, with the aggregator behind it if the provider fails. A more expensive provider never takes a model. The price is the provider's own published price with the usual margin. A provider whose data handling our data policy rules out serves none of our traffic.
{
"model": "inclusionai/ling-3.0-tiny",
"models": ["meta-llama/llama-3.1-8b-instruct"],
"messages": [{"role": "user", "content": "Hello"}]
}Your own testsPOST/v1/evals
Route to the cheapest model that passes your own tests. A suite is up to 50 cases, each a prompt or messages and a check: exact, contains, regex, json_schema, number. No model grades another. A case may carry a weight.
A run sends every case to every model (up to 12, or the cheapest under max_price_per_1m) as a real request billed to your account, with no fallback and no cache. Each case holds its worst case against budget_micros before it is sent, so a run never spends past it. The run reports each model's pass rate, billed cost per case and median latency, the cost and pass rate frontier, and the cheapest model over your threshold, with every failure and what the model wrote. You can see all of it at /suites.
Then send antimatter/auto:suite/<suite id> as the model. It goes to the cheapest model whose latest run passed the suite's threshold (or @95 after the id for 95%), priced at today's rates on the tokens it used in the run, with the next 2 passing models as fallbacks. The note= in x-antimatter-route names the run and the score. Editing the cases sets the results aside until the suite runs again. The suite tells you when to run it: changed cases, moved prices or a cheaper model nobody tested. Suites, runs and answers are yours alone, exported with the account and erased with it. Reading takes the read scope. Writing and running take chat. A run counts as a request from the key that started it. A weekly run counts as a request from the key that last saved the suite (its policy, its balance, its caps), checked again each week. A revoked or expired key stops the schedule, and the suite says why until it is saved again. An agent's key cannot use suites.
curl https://useantimatter.com/v1/evals \
-H "Authorization: Bearer $ANTIMATTER_API_KEY" \
-H "content-type: application/json" \
-d '{"name": "support", "pass_threshold": 0.9, "cases": [
{"prompt": "Capital of France? One word.", "check": {"type": "exact", "value": "Paris"}},
{"prompt": "17 * 3?", "check": {"type": "number", "value": 51}}
]}'
curl https://useantimatter.com/v1/evals/$SUITE/runs \
-H "Authorization: Bearer $ANTIMATTER_API_KEY" \
-H "content-type: application/json" \
-d '{"max_price_per_1m": 2, "budget_micros": 500000}'Shadow testsPOST/v1/shadow
Try a cheaper model on your own traffic before you move to it. A shadow test replays a sampled share of the requests a key already keeps under its own flight recorder capture, and only those. A key that never turned capture on has nothing to replay, and the test is refused with capture_required. Of what the test's model answered, whole and with a 2xx, a sample_rate of at most 10% is replayed, at most daily_cap requests a UTC day (up to 1000), to 1 to 5 candidates you name, or "suggest" for the cheapest comparable models (no more expensive on either rate, with the original's context, tool calling and JSON). This works on personal keys only. An organization's key and an agent's key are refused.
Each replay is an ordinary request billed to you as the key that made the original, under its policy, its balance and its caps, inside monthly_ceiling_micros, which you must set. Every replay's worst case is held against what the month has left before it is sent, so the replays never go over, and a full month pauses the test until the next one. Replays run in the background, never stream, never fall back, never touch the cache, and are never captured themselves. They show in your usage under the session shadow:<test id>, and never count toward rankings or reliability. A frozen account, or a key that is revoked, expired or lost its chat scope, stops the test.
The comparison is computed. By default (compare.method: "auto") an answer that is JSON or a tool call is compared field by field (the share of leaf fields that match, and they agree when every field does). Any other answer is compared as normalized text, ignoring case, white space and a final full stop, and they agree only when the two are the same. For prose, embedding with an embedding_model embeds both answers through the billed embeddings path and agrees at a cosine of cosine_threshold (0.9 unless set) or more. judge asks a judge_model you name, billed like any request. Nothing else ever asks a model to grade an answer. With suite and a suite_id, your suite runs on the original and every candidate inside suite_budget_micros, and its own checks decide.
GET /v1/shadow/{id}/report gives, for each candidate, the agreement rate with its sample count and its 95% Wilson interval, what the replays cost against what the originals were billed, latency p50 and p95 beside the original's, and the failures. A candidate is ready once at least min_samples (30 unless set) were compared and the interval's lower end reaches threshold (0.9 unless set). The webhook event shadow.candidate_ready fires once when it is. Nothing switches by itself unless the test opted in. POST /v1/shadow/{id}/switch with a candidate writes the key's policy so its requests naming the test's model are served by the candidate (stated in x-antimatter-route-warning). revert puts it back, and both are in the audit log. A key sees and acts on its own tests. The dashboard sees all of them and can test every personal key at once. Results are kept 90 days, never a copy of a prompt or an answer.
Auto savings: POST /v1/shadow/{id}/auto with {"enabled": true} lets the gateway make that switch itself, once a candidate is ready, to the ready candidate the replays measured cheapest, and only one cheaper than the original (shadow.auto_switched says when). Every Monday the requests a switch moved the week before are read from their own usage rows: what they were billed, and what they would have cost on the original at the test's measured cost ratio. The difference is the week's saving, on the test and in the webhook event shadow.savings_report. revert undoes it in one step and also turns the opt in off.
curl https://useantimatter.com/v1/shadow \
-H "Authorization: Bearer $ANTIMATTER_API_KEY" \
-H "content-type: application/json" \
-d '{"model": "inclusionai/ling-3.0-tiny", "candidates": "suggest", "sample_rate": 0.05,
"daily_cap": 200, "monthly_ceiling_micros": 5000000}'
curl https://useantimatter.com/v1/shadow/$TEST/report -H "Authorization: Bearer $ANTIMATTER_API_KEY"Distill my trafficPOST/v1/distill
Distillation is not on here. When it is, a key's own traffic on one model can teach a small open model, proven before any switch. A distillation reads one key's flight recorder captures of one teacher in a window of up to 30 days, and only those. A key without capture is refused with capture_required. Each capture answered with a 2xx, whole on both sides and not a replay becomes one chat format example (the prompt's messages, then the teacher's answer, its tool calls kept where the base model trains them), deduped by prompt. The newest 10% (holdout, 0.05 to 0.3) is held out for the proof. At least 50 training examples and 10 held out are needed. This works on personal keys only.
POST /v1/distill/estimate shows what the window would produce and what the training would cost, and spends nothing. Training is a LoRA on a base model you pick, at the provider's published price per million training tokens times the tokens times epochs (3 unless set), at least its minimum a run, with the margin on top: meta-llama/Meta-Llama-3.1-8B-Instruct-Reference $0.34 (at least $4), Qwen/Qwen3.5-9B $0.34 (at least $4), openai/gpt-oss-20b $0.4 (at least $4), Qwen/Qwen3.5-27B $1.05 (at least $4), meta-llama/Llama-3.3-70B-Instruct-Reference $2.03 (at least $4), read off the provider's pages on 2026-10-18. POST /v1/distill holds that on a funded balance (the free allowance never pays), then uploads the training file. Only its counts and a SHA-256 digest are kept here. Once the provider has read the file, its own estimate is asked and a training estimated above the hold is refused before it starts. The hold is settled at the provider's estimate, never above it, released when training fails, and a cancel is charged for the share of steps the provider says ran. The training file is deleted at the provider when training ends, and the model too when you delete the distillation or erase the account.
The finished model is private, antimatter/distilled/<id>, which only your personal keys and your agents' keys can use. To anyone else it is an unknown model. No provider serves a fine tuned model per token any more, so each model runs on its own replica for the hours you name. POST /v1/distill/{id}/light with hours prices the hardware at the provider's published hourly price with the margin, holds the hours, then starts it. Its requests carry no token charge (x-antimatter-cost-micros: 0). You are charged for the minutes it ran when it stops: at the end of its hours, on POST /v1/distill/{id}/darken, or when the provider stops it after an hour with no requests. With keep_lit, one more hour is held each hour. A model that is not running answers 503 upstream_unavailable with the code world_dark.
When training ends the model runs for 1 hour and the held out requests are replayed to it as your billed requests, compared with what the teacher answered the way shadow tests compare (exact, or compare by embeddings or a judge you name). With shadow testing on, this is a shadow test of the teacher with the model as its candidate, so the switch is the shadow test's one click switch, reverted on its own when the model stops running. The proof gives the agreement with its 95% interval, latency beside the teacher's, and a request's cost before and after, based on the hourly price and your own rate of requests to the teacher. Webhook events distill.completed and distill.proof_ready fire once each. Nothing switches by itself.
curl https://useantimatter.com/v1/distill/estimate \
-H "Authorization: Bearer $ANTIMATTER_API_KEY" \
-H "content-type: application/json" \
-d '{"teacher": "inclusionai/ling-3.0-tiny", "base_model": "meta-llama/Meta-Llama-3.1-8B-Instruct-Reference",
"from": 1792000000, "to": 1792600000}'EnsemblesPOST/v1/ensemble
Send one request to 2 to 8 models at once. The answer carries every member's reply with its cost and latency, and one combined answer by a rule you pick. Name the members in members (model ids or autopilot ids, each optionally with its own max_tokens and temperature) or a preset: tiers is antimatter/auto:cheap, antimatter/auto, antimatter/auto:quality. The rest of the body is a chat request every member shares.
We compute the combined answer by rule. majority takes the answer most members gave, ignoring case, wrapping quotes and a closing full stop ("match": "exact" compares as written). A tie goes to the cheaper answer. min_agreement refuses a thin plurality. json-consensus needs a JSON response_format. The answers that parse and pass the schema vote field by field, and a merge that breaks the schema gives way to the whole answer most members gave. first-valid takes the cheapest answer that passes the JSON check, a pattern, or both. judge runs only when you name judge_model. That model sees the conversation and the answers and picks one. It is billed as one more member, and the answer says "chosen_by": "model_judgment".
Before anything is sent, every member and the judge are priced at their worst case, as a single request is held. If the total is over max_cost_micros, over the key's per request ceiling or over what the account can spend, it is refused and nothing runs. Each member is then its own request: its own request id, hold, settlement and usage row, all under the ensemble's id as their session, and x-antimatter-cost-micros is the total. Presets and aliases are not applied and no member falls back, so the model priced is the model that runs. A member that fails or passes member_timeout_ms (120 seconds unless set, 240 at most) is reported and the rest carry on. Only when every member fails does it return 502, ensemble_failed. A combined answer exists only once every member has answered, so an ensemble does not stream.
curl https://useantimatter.com/v1/ensemble \
-H "Authorization: Bearer $ANTIMATTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"members": ["inclusionai/ling-3.0-tiny", "meta-llama/llama-3.1-8b-instruct"],
"combine": {"rule": "majority"},
"max_cost_micros": 50000,
"max_tokens": 8,
"messages": [{"role": "user", "content": "Positive or negative: Loved it. One word."}]
}'Private inferenceGET/api/attestations
Private inference is not on here. When it is, the private/ models will run inside AMD SEV-SNP confidential machines operated by Tinfoil, and the gateway will prove it before it sends them anything. Every few minutes it will read the attestation of the router at inference.tinfoil.sh and check five links (the hardware report and its signature by the chip's own key, that key's chain to AMD's Genoa root, the measurement against the signed release of tinfoilsh/confidential-model-router, that release's signature in Sigstore's public Rekor log, and the TLS key the report binds), and trust a verified verdict for 30 minutes.
A request to a private model will leave only under a current verified verdict, and only over a connection whose server key is the one the hardware signed. Without one the answer will be 503 attestation_unavailable, with nothing sent and nothing charged. Until then a private/ id is an unknown model here.