Chat completions
POST /v1/chat/completions accepts a validated subset of the OpenAI chat request. Fields that would change what is generated or billed are rejected rather than silently ignored.
Request
POST https://api.example.com/v1/chat/completions
Authorization: Bearer sk-sv-user-…
Content-Type: application/json
{
"model": "sevren-lite-27b",
"messages": [
{"role": "system", "content": "You are terse."},
{"role": "user", "content": "Name three prime numbers above 100."}
],
"max_tokens": 4096,
"temperature": 0.7,
"stream": false
}Authorization: a session token or another key for paid models. See Authentication.Content-Type: application/json. The body must be one JSON object; its size is limited and a larger body answers413 request_too_large.X-Sevren-Entitlement: only for token-gated models.
Accepted fields
Fields set to null are treated as absent. A value outside its range answers 400 invalid_parameter.
| Field | Rule |
|---|---|
model | Required. A catalog id, at most 128 characters. Unknown or disabled ids answer 404 model_not_found. |
messages | Required, non-empty, with at least one user message and a bounded number of messages (too_many_messages). See below. |
tools | Up to 128 function tools. See Tool calling. An empty array is accepted and dropped. |
tool_choice | "none", "auto", "required", or {"type": "function", "function": {"name": "…"}} naming a declared tool. Without tools only "none" and "auto" are accepted. |
parallel_tool_calls | Boolean. Whether the model may call several tools in one answer. No effect without tools. |
stream | Boolean. true returns server-sent events. |
stream_options.include_usage | Boolean. Adds a final chunk with the token usage to a stream. |
max_tokens, max_completion_tokens | Positive integers. If both are sent, the smaller wins. See output limits below. |
temperature | 0 to 2. |
top_p | Greater than 0, up to 1. |
top_k | Integer, -1 (off) or more. |
min_p | 0 to 1. The current models only accept 0; any other value answers 400. |
presence_penalty, frequency_penalty | -2 to 2. |
repetition_penalty | Greater than 0, up to 2. |
seed | Integer. |
stop | A string, or up to 4 strings, each 1 to 256 bytes. |
Messages
- Roles are
system,user,assistantandtool.developeris treated assystem. Any other role answers400 invalid_role. - An
assistantmessage may carrytool_calls, each with anid,type: "function"and afunctionwith itsnameandargumentsas a JSON string; itscontentmay then benull. Atoolmessage needs thetool_call_idit answers and text content. Only assistant messages may carrytool_calls. contentis a string or an array of text parts,[{"type": "text", "text": "…"}], which are joined. Images, audio, files and any other part type answer400 unsupported_content. Content must be valid UTF-8.- Other message fields, such as
name, are ignored.
Rejected fields
These fields answer 400 unsupported_parameter unless they carry the value that does nothing, which is accepted and dropped:
| Field | Accepted only as |
|---|---|
n | 1 |
best_of | 1 |
logprobs | false |
top_logprobs | 0 |
echo | false |
functions (legacy; use tools) | [] |
function_call (legacy; use tool_choice) | "none" or "auto" |
response_format | {"type": "text"} |
logit_bias | {} |
min_tokens | 0 |
ignore_eos | false |
modalities | ["text"] |
use_beam_search | false |
prompt_logprobs, structured_outputs, guided_*, audio, prediction, prompt | Never accepted. |
Dropped fields
Every other field, for example user, metadata, reasoning_effort or store, is dropped without an error and never forwarded. Nothing that could identify you reaches the model: no key, no account, no IP address, no headers — only the messages, the tools, the output cap and the sampling fields above.
Response
Without stream, the answer is one chat.completion object. Reasoning models add message.reasoning with the thinking text. finish_reason is stop when the model finished, length when the output cap was reached and tool_calls when the model called tools. Every response, including errors, carries an X-Request-Id header; quote it when you report a problem.
{
"id": "chatcmpl-…",
"object": "chat.completion",
"created": 1791547200,
"model": "sevren-lite-27b",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "101, 103 and 107.",
"reasoning": "The user wants primes above 100…"
},
"finish_reason": "stop"
}
],
"usage": {"prompt_tokens": 31, "completion_tokens": 412, "total_tokens": 443}
}Streaming
With "stream": true the response is text/event-stream. Every event is one data: line holding a chat.completion.chunk, followed by a blank line. Reasoning models send delta.reasoning chunks before the delta.content chunks. If you set stream_options.include_usage, the last chunk before data: [DONE] has an empty choices array and the usage object.
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","created":1791547200,"model":"sevren-lite-27b","choices":[{"index":0,"delta":{"role":"assistant","reasoning":"The user wants"}}]}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","created":1791547200,"model":"sevren-lite-27b","choices":[{"index":0,"delta":{"reasoning":" primes above 100…"}}]}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","created":1791547200,"model":"sevren-lite-27b","choices":[{"index":0,"delta":{"content":"101, 103 and 107."}}]}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","created":1791547200,"model":"sevren-lite-27b","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","created":1791547200,"model":"sevren-lite-27b","choices":[],"usage":{"prompt_tokens":31,"completion_tokens":412,"total_tokens":443}}
data: [DONE]- When the output cap is reached, a chunk with
finish_reason: "length"ends the output, then the usage chunk (if requested) and[DONE]. - An error after the stream has started cannot change the HTTP status, which is already
200. It arrives as an event with the usual error object, followed by[DONE]:
data: {"error":{"message":"The model backend failed.","type":"server_error","code":"upstream_error"}}
data: [DONE]Check every event for an error key. Thinking arrives in delta.reasoning; if your parser expects delta.reasoning_content, read both.
Tool calling
Tool calling works as in the OpenAI API. Declare functions in tools; the model may answer with calls to them instead of text. Your client runs the calls and sends the results back. Sevren never runs a tool.
- Each tool is
{"type": "function", "function": {…}}with anameof 1 to 64 letters, digits,_or-, unique in the request, an optionaldescription,parametersas a JSON schema object, and an optionalstrict. Up to 128 tools. Anything else answers400 invalid_tools. tool_choicemust be"none","auto","required"or name a declared function; otherwise it answers400 invalid_tool_choice.- The legacy
functionsandfunction_callfields are not supported. Images and other media are not supported either, with or without tools.
{
"model": "sevren",
"messages": [{"role": "user", "content": "What is the weather in Ljubljana?"}],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Current weather for a city.",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}
}
],
"tool_choice": "auto"
}When the model calls a tool, message.tool_calls holds the calls and finish_reason is tool_calls. arguments is a JSON string; parse it before use.
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{"id": "call_…", "type": "function", "function": {"name": "get_weather", "arguments": "{\"city\": \"Ljubljana\"}"}}
]
},
"finish_reason": "tool_calls"
}
]To continue, send the conversation again with the assistant message that made the calls, followed by one tool message per call with its tool_call_id and the result as text.
"messages": [
{"role": "user", "content": "What is the weather in Ljubljana?"},
{"role": "assistant", "content": null, "tool_calls": [
{"id": "call_…", "type": "function", "function": {"name": "get_weather", "arguments": "{\"city\": \"Ljubljana\"}"}}
]},
{"role": "tool", "tool_call_id": "call_…", "content": "14 °C, light rain"}
]In a stream, calls arrive as delta.tool_calls chunks. The first chunk of a call carries its index, id, type and function.name; later chunks with the same index add pieces of function.arguments. The last chunk has finish_reason: "tool_calls".
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","created":1791547200,"model":"sevren","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"call_…","type":"function","function":{"name":"get_weather","arguments":""}}]}}]}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","created":1791547200,"model":"sevren","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\"city\": \"Ljubljana\"}"}}]}}]}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","created":1791547200,"model":"sevren","choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}]}
data: [DONE]Tool definitions, tool calls and tool messages are part of the prompt. They count as input tokens and are billed like any input, so a long list of tools makes every request more expensive.
Output limits and max_tokens
Omit max_tokens and max_completion_tokens to use the provider's default. The web terminal does this automatically. Thinking counts as output. The edge still reserves enough credit before generation and enforces the model's context window.
- If you send
max_tokensormax_completion_tokens, the smaller value is used. Otherwise no synthetic output parameter is sent when provider defaults are enabled. - A positive
maxOutputTokensconfigures an additional cap;0leaves output settings to the provider. The context window minus the prompt remains the physical upper bound. A prompt that fills the window answers400 context_length_exceeded. - For paid models, it holds the prompt plus the full cap from your free tokens on that model and your credit: the holder free try first, then the plan's free tokens of the day, prompt and output tokens counted separately, then credit at the model's prices (see Billing). If they cannot pay for the whole cap, the cap is lowered to what they can pay for. If they cannot pay for the prompt and one output token, the request answers
402 insufficient_quota.
While the model writes, output stops at the cap, or when the cost reaches the amount held, with finish_reason: "length". When the request ends you are charged for the usage the model reported and the rest of the hold is released. The Billing page explains holds and free tokens.
What happens to a request
- Access: the key, the account and the model's access rule are checked, and rate limits are applied.
- Screening: a narrow classifier checks the input for child sexual exploitation content and nothing else. Such requests answer
400 content_blocked; input too long to screen answers400 input_too_long_for_screening. Screening keeps no record of clean or blocked requests. - Counting: the prompt is counted and checked against the context window.
- Hold: for paid models, the budget for the request is held.
- Generation: the request runs on the model and the answer is relayed to you.
- Metering: the reported usage is charged and the hold is released.
Requests refused before generation are not charged. If the model received the request and then failed, you are charged for the usage it reported, or for the prompt alone when it reported none. A request that runs longer than the server's time limit is stopped with 504 timeout.
Sevren never logs prompts or completions, and never stores them, with one exception. When a request answers 400, Sevren keeps it for up to 7 days to debug the error: the request body (up to 64 KiB), the error answer and, if the model refused the request, the model's answer. No IP address, API key or account is stored with it. Requests that do not get a 400, and requests the screening refuses, are never stored. See Privacy.
