ShvIA

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: Bearer or X-API-Key.
  • Limit: 20 requests per minute per account by default, one counter that all the /v1 endpoints 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#

FieldTypeRequiredNotes
modelstringyesa model id from models, at most 320 characters
messagesarrayyes1 to 200 messages, see below
streambooleannotrue for server-sent events. Defaults to false
stream_options.include_usagebooleannoadds a final chunk with token counts to a streamed reply
toolsarraynofunction tools, see tool calls
tool_choicestring or objectnoauto, none, required, or {"type":"function","function":{"name":"…"}}
reasoning_effortstringnolow, medium, high, xhigh or max, on models that support it. Any other value is dropped
collectionsarray of integersnoids 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 fieldsnoapplied 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:

  • role is system, user, assistant or tool.
  • content is a string, or null on an assistant message that carries tool_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 a tool message carries the tool_call_id it 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.

Parameterdriver is ollamaany other driver
temperature, top_p, max_tokens, max_completion_tokens, stop, seed, frequency_penalty, presence_penaltyforwarded to the model serverignored
response_format, n, parallel_tool_calls, user, logit_bias, logprobs, top_logprobsforwarded as they are. The model server decides whether they have any effectignored
tools, tool_choiceforwardedapplied
reasoning_effortnot appliedapplied when the model has an effort setting
stream_options.include_usagealways on for streamsapplied

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#

json
{
  "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 }
}
  • model is 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 on ollama, finish_reason is stop or tool_calls. For ollama models it is whatever the model server reports, which includes length.
  • The reasoning text that some models produce is never returned.

Headers#

HeaderMeaning
X-Request-Ididentifies the HTTP request. Send your own, up to 64 letters, digits, - and _, and it is reused
X-Inference-Request-Ididentifies the model call. Give it to support
X-Inference-Model, X-Inference-Serverthe resolved target, for information
X-Inference-Collections, X-Inference-Excerptsonly 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-Remainingyour 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].

sse
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.

bash
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"]
        }
      }
    }]
  }'
json
{
  "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:

json
{
  "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.

StatuscodeWhen
401invalid_api_keythe key is missing, wrong, expired or revoked
403permission_deniedyour group may not use this model, a quota is used up, or the group may not use remote servers. Do not retry blindly
404model_not_foundthe id carries a server and the gateway has no such model there, or the model is not exposed
422nullthe body failed validation
422, 404, 403invalid_collections, collection_not_found, collections_blocked_by_lgpdthe collections field could not be used. See collections
429nonerequest limit reached. The body is {"message": "Too Many Attempts."} when you send Accept: application/json; read Retry-After
502empty_responsethe model returned nothing at all. Not for ollama models
502, 503, or the provider's own statusservice_unavailablethe 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)
504read_timeoutthe 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/completions and 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.