skip to content
API reference

API

API reference

Routes, parameters, headers and errors for api.conifer.build. One key and one base URL cover every route below.

HostBase URLAuth
Gatewayhttps://api.conifer.buildAuthorization: Bearer $CONIFER_API_KEY

Credentials go in Authorization: Bearer or x-api-key. The Azure SDK’s api-key header is accepted too. The OpenAI routes sit under /v1; the Anthropic base URL is the host with no suffix. conifer serve answers the same wire on http://127.0.0.1:8080 with no auth on loopback. See Serve an endpoint.

terminal
curl https://api.conifer.build/v1/chat/completions \
  -H "Authorization: Bearer $CONIFER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-haiku-4-5",
    "max_tokens": 1024,
    "messages": [{"role": "user", "content": "three names for a build cache"}]
  }'

Models differ in what they support. Read caps on GET /v1/models before you send tools or images. Reasoning traces and cache details appear in a response only when the model produced them.

terminal
curl -s https://api.conifer.build/v1/models \
  -H "Authorization: Bearer $CONIFER_API_KEY" \
  | jq '.data[] | {id, caps, context_window, pricing}'

Routes

RouteWireNotes
POST /v1/chat/completionsOpenAI chatEvery model in the catalog.
POST /v1/messagesAnthropic MessagesAnthropic model ids. Prompt caching and live streaming on this route.
POST /v1/responsesOpenAI ResponsesThe stateless tool-loop wire Codex uses. Same auth, receipts and errors.
POST /v1/completionsOpenAIThe legacy text-completions wire.
POST /v1/embeddingsOpenAIModels whose caps include embeddings. Same key, same receipt, same ceiling.
GET /v1/catalogConiferThe public catalog with ids, caps, context windows and prices. No key needed.
GET /v1/modelsOpenAIThe same list, scoped to your key.
GET /v1/balanceConiferYour balance, as integers.

Parameters

FieldBehavior
modelAn exact id from GET /v1/models. An unknown id is a 404. Conifer never swaps in another model.
max_tokensOptional on the chat wire, where 4096 applies when it is absent. Required on /v1/messages.
streamAccepted on every completion route. See Streaming below.
toolsPassed to the model. Check caps on the catalog first. A model without tools answers 400 naming the missing capability, not 402 and not 429.

Streaming

stream: true returns SSE on every completion route. Tokens arrive as the model produces them. A few providers hand back the whole answer at the end, in the same chunk shape. The cost header is absent on a stream because the response starts before the cost is known. On /v1/messages the settled cost arrives as a final conifer_receipt event after message_stop. On the chat and Responses wires, send stream_options.include_usage and price the final chunk’s usage from the catalog. An in-band stream error has the same shape as the buffered envelope.

Time bounds

A buffered call has a fixed time limit of a few minutes. A stream is bounded by the idle time between frames instead. For very long outputs, stream or split the request.

Receipt headers

Every response carries a receipt in its headers. On a stream the identity headers are present and the cost header is absent, because the head is sent before the cost settles.

HeaderMeaning
x-conifer-requested-modelThe id you asked for.
x-conifer-effective-modelThe model that answered and was billed. The same as the requested id unless you let Conifer choose with auto or default, a fallback you named served, or you sent a namespaced spelling.
x-conifer-cost-nanousdThe settled cost of this request, in nanodollars (1e-9 USD).
request-idSame value as x-conifer-request-id.
x-request-idSame value as x-conifer-request-id.
x-conifer-request-idThe request id. Quote it in support requests.

One header goes the other way. x-conifer-max-cost-nanousd sets a hard ceiling for the request. If it could cost more, Conifer refuses with a 402 before the model runs and nothing is charged.

Errors

StatusMeaning
400type invalid_request_error, code context_length_exceeded when the prompt is over the model’s window. Some providers report this themselves, which relays as a 422 upstream_error naming the token counts. Other 400s name the problem in the body, such as tools on a model whose caps omit them or a non-Anthropic id on /v1/messages. Capability refusals stay 400.
401type invalid_request_error, code invalid_api_key. Every auth failure returns this one 401. The header is WWW-Authenticate: Bearer.
402type insufficient_allowance, code insufficient_quota when the balance cannot cover the request. A ceiling refusal is type cost_ceiling_exceeded, and a key at its own cap is key_spend_cap_exceeded. Nothing is charged for any of the three, and none is remapped to 429. See Billing & caching.
404code model_not_found, param model. The id is not in your catalog.
429type rate_limit_error, code rate_limit_exceeded. The header is Retry-After: 1. Conifer does not send invented x-ratelimit-* figures.

Reasoning traces

On POST /v1/chat/completions, the trace is at choices[0].message.reasoning, the OpenRouter and OpenAI-compat name. Some models also set choices[0].message.reasoning_content, DeepSeek’s name. Both fields stream on choices[0].delta. Not every model emits either field. A missing field means there was no trace.

Two optional request fields control reasoning. Send reasoning as { effort | max_tokens }, or send reasoning_effort as one of none | minimal | low | medium | high | xhigh | max. One vocabulary covers every provider. Where a model offers fewer levels, Conifer maps yours to the nearest one it has. On Anthropic models the effort becomes a thinking budget, so set max_tokens high enough to leave room for the answer after the thinking.

terminal
curl https://api.conifer.build/v1/chat/completions \
  -H "Authorization: Bearer $CONIFER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-haiku-4-5",
    "max_tokens": 8192,
    "reasoning": {"effort": "medium"},
    "messages": [{"role": "user", "content": "three names for a build cache"}]
  }'

On POST /v1/messages, a thinking block is a content item with type: "thinking".

response.content[]
{
  "type": "thinking",
  "thinking": "the model scratch work",
  "signature": "…"
}

Prompt caching

On the chat wire, cache counts appear under usage.prompt_tokens_details as cached_tokens and cache_write_tokens. Both are 0 when nothing was cached, and the details object itself may be absent. Usage reports only the fields that exist.

response.usage
{
  "prompt_tokens_details": {
    "cached_tokens": 80,
    "cache_write_tokens": 0
  }
}

On /v1/messages, send cache_control breakpoints. Usage reports cache_read_input_tokens and cache_creation_input_tokens. Cached input is priced at the model’s cache-read rate, and the receipt reflects it.

terminal
curl https://api.conifer.build/v1/messages \
  -H "x-api-key: $CONIFER_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-haiku-4-5",
    "max_tokens": 1024,
    "system": [{
      "type": "text",
      "text": "You are a build-cache namer. Keep answers to three words.",
      "cache_control": {"type": "ephemeral"}
    }],
    "messages": [{"role": "user", "content": "three names for a build cache"}]
  }'
response.usage
{
  "input_tokens": 120,
  "cache_creation_input_tokens": 80,
  "cache_read_input_tokens": 0,
  "output_tokens": 24
}