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.

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.
POST /v1/systemoneTypeSafe System OneModels whose caps include decisions (Jev): program state plus typed questions in, typed answers with probabilities out. Input-billed, output free. Not a chat model; the chat routes refuse it by name. Jev is TypeSafe AI's model. Pin a version (jev-1.14, jev-1.13) or use jev-latest, which follows the newest release; the response's model field is the id of the version that answered, such as jev-1.14.0.
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 /v1/chat/completions, /v1/messages and /v1/responses. /v1/completions refuses it with a 400 naming the chat 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 the chat, Messages and Responses routes. Tokens arrive as the model produces them, /v1/responses included. A few providers hand back the whole answer at the end, in the same chunk shape.

A stream is never silent for more than 15 seconds. When the first frame is slow (routing, a slow first token, a failover), the gateway sends the 200 head with the identity receipt headers and then a : keep-alive SSE comment every 15 seconds until the answer flows. SSE parsers skip comments. A failure after that head arrives in band, in the route’s own grammar: data: {"error":{…}} then [DONE] on chat, event: error on Messages, and response.failed on Responses. The error object is the same one the buffered envelope carries.

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 x-conifer-stream-receipt: true to get the same conifer_receipt event, with the settled cost_nanousd, at the end of the stream (before [DONE] on chat). It is not an OpenAI event type, and openai-node’s responses.stream() helper rejects unknown events, so read an opted-in Responses stream with raw event iteration. Without the opt-in, send stream_options.include_usage and price the final chunk’s usage from the catalog. A missing receipt event means the cost was not confirmed, never that it was zero.

Time bounds

Each upstream call is bounded at 280 seconds. A stream is bounded by the same 280 seconds of upstream silence between frames, and the keep-alives above hold the connection open meanwhile.

A buffered answer that is still running at 285 seconds (a failover walk, or routing plus a slow model) is committed early so no proxy cuts it: the gateway sends 200 with x-conifer-keepalive: committed, then a space every 10 seconds, then the answer. That 200 is provisional. The body ends with one extra member, "conifer_receipt": {"status": …, "headers": {…}}, holding the real status and the receipt headers a fast answer would have carried. If the call fails without being charged, the connection is closed before the body ends, so your client sees a transport error and can retry with the same Idempotency-Key. A charged error ends normally, with its error status in conifer_receipt.status; do not retry it. For very long outputs, stream.

Finish reasons

On the chat wire, stop and tool_calls are finished turns. length means the output or context budget ran out, and content_filter is a refusal. Two more values mark a turn that did not finish: pause_turn when the provider paused it, and incomplete for any other or missing stop. Partial tool calls are withheld on all of these. The choice may also carry provider_stop_reason (the provider’s own words) and provider_stop_details. The list can grow, so treat any value you do not recognise as unfinished, never as success. On /v1/responses the same outcomes are status: "incomplete" with incomplete_details.reason.

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. On a response marked x-conifer-keepalive: committed the same headers travel in the body’s conifer_receipt.headers instead (see Time bounds). The full list is on Routing.

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).
x-conifer-list-cost-nanousdOn a bring-your-own-key request only: the usage at the provider's list rates, in nanodollars. A list-rate estimate of what the provider bills your own key; a discounted provider contract bills less. Conifer charges none of it. Absent when the catalog does not price the model, such as an Azure deployment name. On a stream it arrives as list_cost_nanousd on the final conifer_receipt event when you send x-conifer-stream-receipt: true.
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
}