Skip to content

API reference

Chat completions

POST /v1/chat/completions: request fields, headers, the response and the cairva metering object.

On this page

Runs a chat completion against your allocation's model and meters it. The request and response follow the OpenAI chat completions format, so existing OpenAI SDK code works once you change the base URL, the key and the model id.

POST/v1/chat/completions

Requires the inference scope.

Example

curl https://cairva.xyz/v1/chat/completions \
  -H "Authorization: Bearer $CAIRVA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "x-request-id: ticket-4812" \
  -d '{
    "model": "gemma4:e2b",
    "messages": [
      {"role": "system", "content": "Answer in one sentence."},
      {"role": "user", "content": "What does a metered allowance include?"}
    ],
    "max_tokens": 200
  }'

Request body

FieldTypeNotes
modelstring, requiredMust equal the model id your allocation covers. Get it from
messagesarray, required1 to 200 messages. role is system, user or assistant. content is a string or an array of {"type": "text", "text": "..."} parts, which are joined without a separator
max_tokensinteger1 to 32,768. Defaults to 1,024. Lowered to your remaining output allowance if that's smaller
max_completion_tokensintegerSame limits. Wins over max_tokens when both are set
temperaturenumber0 to 2. Passed to the provider
top_pnumber0 to 1. Passed to the provider
stopstring or arrayUp to 4 stop sequences
streambooleanStream server-sent events. See Streaming
userstringUp to 200 characters. Accepted for SDK compatibility, not forwarded or stored

Any other field is dropped before the request reaches the provider. That includes tools, n, response_format, logprobs and seed. A request with an image part, or a tool, function or developer role, fails with 400 validation_failed. Details in OpenAI compatibility.

Headers

HeaderDirectionNotes
AuthorizationRequestBearer and your key
x-request-idRequest, optionalYour own identifier, truncated to 120 characters. Stored as client_request_id and returned in usage events, so you can join Cairva's meter to your logs
x-cairva-request-idResponseCairva's id for the request. Sent on non-streaming responses

Response

JSON
{
  "id": "chatcmpl-7d0c2f4e-6a51-4c43-9f0e-2b8f5a1c9e30",
  "object": "chat.completion",
  "created": 1793520000,
  "model": "gemma4:e2b",
  "choices": [
    {
      "index": 0,
      "message": { "role": "assistant", "content": "..." },
      "finish_reason": "stop"
    }
  ],
  "usage": { "prompt_tokens": 31, "completion_tokens": 18, "total_tokens": 49 },
  "cairva": {
    "request_id": "7d0c2f4e-6a51-4c43-9f0e-2b8f5a1c9e30",
    "allocation_id": "c1a9e1d2-0b7f-4f9e-a3c4-58e2d6b0f114",
    "service_mode": "sandbox",
    "usage_source": "provider_reported",
    "remaining": { "input": 999969, "output": 499982 }
  }
}

There's always exactly one choice. finish_reason is whatever the provider returned, usually stop or length.

The cairva object

FieldMeaning
request_idCairva's id for this request. The response id is chatcmpl- followed by it
allocation_idThe allocation that was charged, as a UUID
service_modelive or sandbox
usage_sourceprovider_reported when the provider returned token counts, sandbox_estimate on a stand-in sandbox route that calls no model, or pending when no counts came back
remainingInput and output tokens left after this request, net of other requests still running

When usage_source is pending, usage is null. Cairva never fills in an estimate. The request's hold stays in place until Cairva operations reconcile it from provider metering. See Rate limits and holds.

Output tokens you don't see

Some models reason before they answer, and that reasoning counts as output tokens. The gateway returns only the message content, so reasoning isn't included in the response. Ollama, which serves the sandbox route, doesn't return reasoning as content either.

If max_tokens runs out while the model is still reasoning, you can get an empty or very short content with finish_reason: "length". The provider still reports the output tokens it generated, and they're metered. Give reasoning models enough max_tokens for both the reasoning and the answer.

Tip

If you see empty answers with finish_reason: "length", raise max_tokens before anything else.

Errors

Admission errors such as model_not_permitted, allocation_exhausted and rate_limited come back as an HTTP error before the provider is called. If the provider fails, you get 502 upstream_error, the hold is released, and nothing is charged. All codes are in Errors.