Sevren docsAPI reference

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

HTTP
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 answers 413 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.

Accepted request fields
FieldRule
modelRequired. A catalog id, at most 128 characters. Unknown or disabled ids answer 404 model_not_found.
messagesRequired, non-empty, with at least one user message and a bounded number of messages (too_many_messages). See below.
toolsUp 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_callsBoolean. Whether the model may call several tools in one answer. No effect without tools.
streamBoolean. true returns server-sent events.
stream_options.include_usageBoolean. Adds a final chunk with the token usage to a stream.
max_tokens, max_completion_tokensPositive integers. If both are sent, the smaller wins. See output limits below.
temperature0 to 2.
top_pGreater than 0, up to 1.
top_kInteger, -1 (off) or more.
min_p0 to 1. The current models only accept 0; any other value answers 400.
presence_penalty, frequency_penalty-2 to 2.
repetition_penaltyGreater than 0, up to 2.
seedInteger.
stopA string, or up to 4 strings, each 1 to 256 bytes.

Messages

  • Roles are system, user, assistant and tool. developer is treated as system. Any other role answers 400 invalid_role.
  • An assistant message may carry tool_calls, each with an id, type: "function" and a function with its name and arguments as a JSON string; its content may then be null. A tool message needs the tool_call_id it answers and text content. Only assistant messages may carry tool_calls.
  • content is a string or an array of text parts, [{"type": "text", "text": "…"}], which are joined. Images, audio, files and any other part type answer 400 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:

Fields rejected unless they have their no-op value
FieldAccepted only as
n1
best_of1
logprobsfalse
top_logprobs0
echofalse
functions (legacy; use tools)[]
function_call (legacy; use tool_choice)"none" or "auto"
response_format{"type": "text"}
logit_bias{}
min_tokens0
ignore_eosfalse
modalities["text"]
use_beam_searchfalse
prompt_logprobs, structured_outputs, guided_*, audio, prediction, promptNever 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.

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

SSE
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]:
SSE
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 a name of 1 to 64 letters, digits, _ or -, unique in the request, an optional description, parameters as a JSON schema object, and an optional strict. Up to 128 tools. Anything else answers 400 invalid_tools.
  • tool_choice must be "none", "auto", "required" or name a declared function; otherwise it answers 400 invalid_tool_choice.
  • The legacy functions and function_call fields are not supported. Images and other media are not supported either, with or without tools.
JSON
{
  "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.

JSON
"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.

JSON
"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".

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

  1. If you send max_tokens or max_completion_tokens, the smaller value is used. Otherwise no synthetic output parameter is sent when provider defaults are enabled.
  2. A positive maxOutputTokens configures an additional cap; 0 leaves output settings to the provider. The context window minus the prompt remains the physical upper bound. A prompt that fills the window answers 400 context_length_exceeded.
  3. 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

  1. Access: the key, the account and the model's access rule are checked, and rate limits are applied.
  2. 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 answers 400 input_too_long_for_screening. Screening keeps no record of clean or blocked requests.
  3. Counting: the prompt is counted and checked against the context window.
  4. Hold: for paid models, the budget for the request is held.
  5. Generation: the request runs on the model and the answer is relayed to you.
  6. 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.