Chat completions

Chat completions

POST /api/v1/chat/completions

The endpoint most clients use. If your tool speaks OpenAI, this is the one.

Request

Field Type Notes
model string An id from GET /models. Omit it and the first published model is used.
messages array Required, at least one. See the message shapes below.
max_tokens integer Completion ceiling. Leave it out unless you need a specific cap.
temperature number Passed through to the provider.
top_p number Passed through.
stop string or array Stop sequences.
stream boolean Server-sent events.
tools array Tool schemas. Only send to a model whose capabilities.tools is true.
tool_choice string or object auto, none, required, or one named function. Omit it and the model decides.
reasoning_effort string For reasoning models. Values are clipped to what the model implements.

Message shapes

{ "role": "system", "content": "You are terse." }
{ "role": "user", "content": "Name three prime numbers." }
{ "role": "assistant", "content": "2, 3, 5." }

Multimodal input uses content parts:

{
  "role": "user",
  "content": [
    { "type": "text", "text": "What is in this image?" },
    { "type": "image_url", "image_url": { "url": "data:image/png;base64,..." } }
  ]
}

An assistant turn carrying tool calls:

{
  "role": "assistant",
  "content": null,
  "tool_calls": [
    {
      "id": "call-1",
      "type": "function",
      "function": { "name": "read_file", "arguments": "{\"path\":\"/etc/hostname\"}" }
    }
  ]
}

function.arguments may be a JSON string or an object. Both are accepted and
normalised before the upstream call, because clients disagree and forwarding an object
verbatim produced provider 400s.

A tool result:

{ "role": "tool", "tool_call_id": "call-1", "content": "vps-prod" }
Leave max_tokens out unless you need it

Input and output share the model’s window. A cap near the window leaves no room for your
prompt, and the provider answers 400 asking you to reduce the length. When a request
carries no cap, the gateway does not invent one; the provider applies its own default.

Response

Full example on API overview. The fields that matter:

choices[0].finish_reason is stop, length, or tool_calls. Read it before deciding
whether to continue: a truncated answer that upstream labelled stop is corrected to
length here, and every OpenAI SDK checks that value before offering a continue action.

choices[0].message.content may be empty with finish_reason: "stop" on a reasoning
model that spent its budget thinking. That is a finished turn. Retrying it pays twice for
an answer already delivered.

Streaming

curl -N https://hisoka.ai/api/v1/chat/completions \
  -H "Authorization: Bearer $HISOKA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"glm-5.3-flash","stream":true,"messages":[{"role":"user","content":"Count to five."}]}'

Tokens are forwarded as they arrive rather than buffered until the turn ends. The final
frame carries the same hisoka block, so billing is readable on a stream too.

A stream that ends without content, tool calls or reasoning is a failure; one with only
reasoning is a finished empty answer and still emits its terminator, so the client is not
left holding an open turn.

Tool calling

{
  "model": "glm-5.3-flash",
  "messages": [{ "role": "user", "content": "Read /etc/hostname" }],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "read_file",
        "description": "Read a file from disk.",
        "parameters": {
          "type": "object",
          "properties": { "path": { "type": "string" } },
          "required": ["path"]
        }
      }
    }
  ]
}

The tool name is sent once per call, on the frame that opens it. A client that assigns
rather than concatenates therefore still has the name, which is the difference between an
agent working and an agent reporting “the model returned no valid output”.

Errors specific to this path

Code Means
modality_not_supported an image or video sent to a model that does not take it
context_length_exceeded prompt plus cap overflow the window
max_tokens_exceeded the cap is above what the model allows
usage_unavailable the reply arrived but could not be metered, so it was not billed