Docs · OpenAI-compatible API
Chat completions
The OpenAI Chat Completions dialect. It is stateless: you send the whole conversation on every call, and nothing is stored as a conversation. Calls are still metered, so they count toward your quota.
POST/v1/chat/completions#
- Auth: your API key, in
Authorization: BearerorX-API-Key. - Limit: 20 requests per minute per account by default, one counter that all the
/v1endpoints share with the other endpoints that have a per-minute number of their own. See rate limits and quotas. - Body: JSON, with
Content-Type: application/json.
Request#
| Field | Type | Required | Notes |
|---|---|---|---|
model | string | yes | a model id from models, at most 320 characters |
messages | array | yes | 1 to 200 messages, see below |
stream | boolean | no | true for server-sent events. Defaults to false |
stream_options.include_usage | boolean | no | adds a final chunk with token counts to a streamed reply |
tools | array | no | function tools, see tool calls |
tool_choice | string or object | no | auto, none, required, or {"type":"function","function":{"name":"…"}} |
reasoning_effort | string | no | low, medium, high, xhigh or max, on models that support it. Any other value is dropped |
collections | array of integers | no | ids of your collections, 1 to 5. The pieces of them that bear on the last user message are added to the system message. Not part of the OpenAI API: an SDK sends it as an extra body field |
| sampling and other OpenAI fields | no | applied or ignored depending on the model, see below |
A malformed request returns 422 in the OpenAI error shape, with code: null.
Messages#
Each message has a role and a content:
roleissystem,user,assistantortool.contentis a string, ornullon an assistant message that carriestool_calls. At most 200000 characters per message.- An array of content parts, the form used to send images, is rejected with
422(The messages.0.content field must be a string.). Images are not supported on this endpoint. - An assistant message may carry
tool_calls, and atoolmessage carries thetool_call_idit answers.
Which parameters are applied#
What happens to the sampling parameters depends on the driver of the model, a field of GET /api/v1/profiles. A model whose driver is ollama is served by the operator's own Ollama servers. Every other driver, which covers the cloud providers and also any OpenAI-compatible server that the operator runs, takes the second column.
| Parameter | driver is ollama | any other driver |
|---|---|---|
temperature, top_p, max_tokens, max_completion_tokens, stop, seed, frequency_penalty, presence_penalty | forwarded to the model server | ignored |
response_format, n, parallel_tool_calls, user, logit_bias, logprobs, top_logprobs | forwarded as they are. The model server decides whether they have any effect | ignored |
tools, tool_choice | forwarded | applied |
reasoning_effort | not applied | applied when the model has an effort setting |
stream_options.include_usage | always on for streams | applied |
An ignored parameter is not an error: the request succeeds and the parameter has no effect. If the output length or the randomness matters to you, check the model's driver first.
Response#
{
"id": "chatcmpl-0b0f3c0e-6c4e-4d6f-9a0e-2f3b1d7a9c11",
"object": "chat.completion",
"created": 1767225600,
"model": "claude-sonnet-4-6@anthropic",
"choices": [
{ "index": 0, "message": { "role": "assistant", "content": "Hello! Nice to meet you." }, "finish_reason": "stop" }
],
"usage": { "prompt_tokens": 14, "completion_tokens": 8, "total_tokens": 22 }
}modelis the id that the gateway resolved for the request. It shows a substitution for a bare name the gateway did not know, but it does not show a fallback to your default model. See models.- There is one choice on cloud models: do not send
n. For models that are not onollama,finish_reasonisstoportool_calls. Forollamamodels it is whatever the model server reports, which includeslength. - The reasoning text that some models produce is never returned.
Headers#
| Header | Meaning |
|---|---|
X-Request-Id | identifies the HTTP request. Send your own, up to 64 letters, digits, - and _, and it is reused |
X-Inference-Request-Id | identifies the model call. Give it to support |
X-Inference-Model, X-Inference-Server | the resolved target, for information |
X-Inference-Collections, X-Inference-Excerpts | only when the call named collections: the ids that were used, and how many pieces went into the prompt. See collections |
X-RateLimit-Limit, X-RateLimit-Remaining | your request limit, and how much is left of it |
Streaming#
With "stream": true the response is text/event-stream. Each event is one line data: {…} followed by a blank line, and the stream ends with data: [DONE].
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","created":1767225600,"model":"claude-sonnet-4-6@anthropic","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","created":1767225600,"model":"claude-sonnet-4-6@anthropic","choices":[{"index":0,"delta":{"content":"Hello"},"finish_reason":null}]}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","created":1767225600,"model":"claude-sonnet-4-6@anthropic","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","created":1767225600,"model":"claude-sonnet-4-6@anthropic","choices":[],"usage":{"prompt_tokens":14,"completion_tokens":8,"total_tokens":22}}
data: [DONE]The usage chunk, the one with an empty choices, appears only when you set stream_options.include_usage, except for ollama models where it is always present. For ollama models the chunks are the model server's own, so the leading role-only chunk of the sample is not guaranteed. Read the stream as events, not as lines of JSON: the gateway does not send comments or keep-alive lines.
Tool calls#
Declare functions with tools. When the model wants to call one, the reply has tool_calls and finish_reason is tool_calls.
curl -s "$SHVIA_URL/v1/chat/completions" \
-H "Authorization: Bearer $SHVIA_API_KEY" -H "Content-Type: application/json" \
-d '{
"model": "'"$SHVIA_MODEL"'",
"messages": [{ "role": "user", "content": "What is the weather in Lisbon?" }],
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Current weather for a city",
"parameters": {
"type": "object",
"properties": { "city": { "type": "string" } },
"required": ["city"]
}
}
}]
}'{
"id": "chatcmpl-…",
"object": "chat.completion",
"created": 1767225600,
"model": "claude-sonnet-4-6@anthropic",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{ "id": "call_1", "type": "function", "function": { "name": "get_weather", "arguments": "{\"city\":\"Lisbon\"}" } }
]
},
"finish_reason": "tool_calls"
}
],
"usage": { "prompt_tokens": 61, "completion_tokens": 18, "total_tokens": 79 }
}Run the function yourself, then send the conversation back with the call and its result:
{
"model": "claude-sonnet-4-6@anthropic",
"messages": [
{ "role": "user", "content": "What is the weather in Lisbon?" },
{ "role": "assistant", "content": null, "tool_calls": [
{ "id": "call_1", "type": "function", "function": { "name": "get_weather", "arguments": "{\"city\":\"Lisbon\"}" } }
] },
{ "role": "tool", "tool_call_id": "call_1", "content": "18 degrees, clear" }
],
"tools": [ … the same tools … ]
}In a stream, each tool call arrives whole in a single delta.tool_calls entry rather than as incremental fragments of the arguments.
Errors#
Errors use the OpenAI shape: {"error": {"message", "type", "param", "code"}}. Switch on the HTTP status and on code; the text of message can be in another language and can change.
| Status | code | When |
|---|---|---|
| 401 | invalid_api_key | the key is missing, wrong, expired or revoked |
| 403 | permission_denied | your group may not use this model, a quota is used up, or the group may not use remote servers. Do not retry blindly |
| 404 | model_not_found | the id carries a server and the gateway has no such model there, or the model is not exposed |
| 422 | null | the body failed validation |
| 422, 404, 403 | invalid_collections, collection_not_found, collections_blocked_by_lgpd | the collections field could not be used. See collections |
| 429 | none | request limit reached. The body is {"message": "Too Many Attempts."} when you send Accept: application/json; read Retry-After |
| 502 | empty_response | the model returned nothing at all. Not for ollama models |
| 502, 503, or the provider's own status | service_unavailable | the model server, or every server in the chain, could not be reached or refused the call. If every model in the chain fails, a cloud provider's refusal keeps the provider's status, for example 401 when your provider key is rejected. If another model can answer, it does, and you get its 200 (see models) |
| 504 | read_timeout | the model stopped answering mid-reply. The body adds a partial object with the reply that arrived, and the header X-Inference-Incomplete: read_timeout is set. Not for ollama models |
For ollama models, an empty answer is an ordinary 200 with an empty content, and a model that stalls ends, after a long wait, as a 502 with service_unavailable. A refusal of the gateway's own credentials by the model server is reported as 502, never as 401, so that you do not regenerate an API key that is fine.
Not supported#
- Images and other content parts in messages.
- The legacy
/v1/completionsand the OpenAI/v1/embeddings. Embeddings are at/api/v1/embeddings. - More than one choice (
n). - Retrieving a model or a past completion by id.
Data handling#
- Nothing is stored as a conversation. Each call is recorded for metering, with the text of your last message masked and shortened. See your data.
- When the model is not on the operator's own infrastructure, common identifiers in the text of the messages, such as e-mail addresses, CPF and CNPJ numbers and phone numbers, are replaced by placeholders such as
[EMAIL]and[CPF]before it leaves. The masking is pattern-based: it does not recognize names or free-form data, and it does not touch the arguments of tool calls. Do not use such a model to process those identifiers.