skip to content
The router

Gateway

The router

Send model: auto and Conifer picks the model for that turn from the catalog your key can call. The receipt names what it picked. Nothing is ever substituted behind a named id.

What it is

Every model in the catalog is behind one key. When you would rather not choose between them, send one of three ids instead of a model name and Conifer chooses for you, turn by turn. It looks at what the request needs, picks the model, keeps a short list of fallbacks in case the first choice cannot be served, and answers in the same request. The receipt headers say which model served.

The three ids are listed in /v1/models with virtual: true.

Model idWhat it optimises for
autoBalanced value. The default way to let Conifer choose, and the same as balanced.
balancedThe best value among the models that can answer the request well. Frontier-class answers at a fraction of frontier cost on most turns.
bestThe most capable model for the request, whatever it costs. Hard proofs and deep systems work land on frontier models here.

Use it

Any Conifer key. Any client that lets you set the model id. The examples below are the same call.

curl

route.sh
curl -s -D - https://api.conifer.build/v1/chat/completions \
  -H "authorization: Bearer $CONIFER_API_KEY" \
  -H "content-type: application/json" \
  -d '{"model":"auto","messages":[{"role":"user","content":"What is 17 * 23?"}],"max_tokens":50}' \
  | grep -i x-conifer

# x-conifer-requested-model: auto
# x-conifer-effective-model: deepseek-v4-flash
# x-conifer-receipt-reason: routed
# x-conifer-cost-nanousd: 10120

The Conifer SDK

router.ts
import { Conifer } from "conifer-sdk";
const conifer = new Conifer();   // reads CONIFER_API_KEY

const answer = await conifer.chat({
  model: "auto",                           // or "balanced" | "best"
  messages: [{ role: "user", content: "What is 17 * 23?" }],
  maxTokens: 50,
});
answer.receipt.effectiveModel;             // "deepseek-v4-flash"
answer.receipt.reason;                     // "routed"

// The decision alone, free, no completion:
const pick = await conifer.route({ query: "…", policy: "best" });
pick.model;                                // "claude-opus-5"
pick.fallbacks;                            // the next picks, in order
router.py
from conifer_sdk import Conifer, ChatRequest, RouteRequest
conifer = Conifer()   # reads CONIFER_API_KEY

answer = conifer.chat(ChatRequest(
    model="auto",
    messages=[{"role": "user", "content": "What is 17 * 23?"}],
    max_tokens=50,
))
answer.receipt.effective_model   # "deepseek-v4-flash"
answer.receipt.reason            # "routed"

pick = conifer.route(RouteRequest(query="…", policy="best"))
pick.model, pick.fallbacks

The openai package

openai.ts
import OpenAI from "openai";
const client = new OpenAI({
  apiKey: process.env.CONIFER_API_KEY,
  baseURL: "https://api.conifer.build/v1",
});

// The model id is the whole integration. The receipt is in the headers.
const { data, response } = await client.chat.completions
  .create({ model: "auto", messages: [{ role: "user", content: "hi" }] })
  .withResponse();
response.headers.get("x-conifer-effective-model");
response.headers.get("x-conifer-receipt-reason");   // "routed"

Codex, Cursor, Cline, anything with a model field

codex.sh
# Codex speaks the Responses wire. Conifer routes it the same way.
export OPENAI_BASE_URL=https://api.conifer.build/v1
export OPENAI_API_KEY=$CONIFER_API_KEY
codex --model auto

The decision without the completion

POST /v1/route returns the pick and stops. It is free. Use it when you want to make the call yourself, log the decision, or send the request somewhere Conifer does not serve.

decision.sh
curl -s https://api.conifer.build/v1/route \
  -H "authorization: Bearer $CONIFER_API_KEY" \
  -H "content-type: application/json" \
  -d '{"query":"Prove the Cauchy-Schwarz inequality rigorously.","policy":"best"}'

# {"model":"claude-opus-5",
#  "fallbacks":["claude-fable-5","kimi-k3","gpt-5.6-sol"],
#  "policy":"best"}
FieldMeaning
queryThe text to route on. Send the last user message, not the whole conversation.
policybalanced or best. Anything else is a 400.
modelA catalog id your key can call right now.
fallbacksUp to three further picks, in order. On a chat turn the gateway walks these itself if the first pick cannot be served.

Read the receipt

On a routed turn x-conifer-requested-model is the id you sent and x-conifer-effective-model is the model that served. x-conifer-receipt-reason tells you why.

receipt-reasonMeaning
routedConifer chose this model for this turn.
as_requestedYou named a model, or the router did not answer in time and the gateway served its default pin under the id you sent.
provider_failoverThe first pick could not be served and one of the fallbacks answered.

You pay the catalog price of the model that served. The router itself is free. A best turn that lands on a frontier model costs what that model costs.

When the router does not answer

You never wait on the router. If it does not answer in time, the gateway serves its default pin for that turn and marks the receipt as_requested. The next turn routes as normal. If you call /v1/route directly and get a 503 saying the router did not answer, retry.

Which doors route

Endpointmodel: auto
/v1/chat/completionsRoutes.
/v1/responsesRoutes. Codex and the other Responses-wire agents get the router.
/v1/messagesServes the default pin. This door relays the native Anthropic wire and cannot carry a pick to a non-Anthropic model. Claude Code users name a model.