API reference
Chat completions
POST /v1/chat/completions: request fields, headers, the response and the cairva metering object.
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.
/v1/chat/completionsRequires 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
}'import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.CAIRVA_API_KEY,
baseURL: "https://cairva.xyz/v1",
});
const completion = await client.chat.completions.create({
model: "gemma4:e2b",
messages: [
{ role: "system", content: "Answer in one sentence." },
{ role: "user", content: "What does a metered allowance include?" },
],
max_tokens: 200,
});
console.log(completion.choices[0].message.content);
console.log(completion.cairva);import os
from openai import OpenAI
client = OpenAI(api_key=os.environ["CAIRVA_API_KEY"], base_url="https://cairva.xyz/v1")
completion = client.chat.completions.create(
model="gemma4:e2b",
messages=[
{"role": "system", "content": "Answer in one sentence."},
{"role": "user", "content": "What does a metered allowance include?"},
],
max_tokens=200,
)
print(completion.choices[0].message.content)
print(completion.model_extra["cairva"])Request body
| Field | Type | Notes |
|---|---|---|
model | string, required | Must equal the model id your allocation covers. Get it from |
messages | array, required | 1 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_tokens | integer | 1 to 32,768. Defaults to 1,024. Lowered to your remaining output allowance if that's smaller |
max_completion_tokens | integer | Same limits. Wins over max_tokens when both are set |
temperature | number | 0 to 2. Passed to the provider |
top_p | number | 0 to 1. Passed to the provider |
stop | string or array | Up to 4 stop sequences |
stream | boolean | Stream server-sent events. See Streaming |
user | string | Up 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
| Header | Direction | Notes |
|---|---|---|
Authorization | Request | Bearer and your key |
x-request-id | Request, optional | Your 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-id | Response | Cairva's id for the request. Sent on non-streaming responses |
Response
{
"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
| Field | Meaning |
|---|---|
request_id | Cairva's id for this request. The response id is chatcmpl- followed by it |
allocation_id | The allocation that was charged, as a UUID |
service_mode | live or sandbox |
usage_source | provider_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 |
remaining | Input 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.