Chat completions
The one endpoint that does the work. Same contract as OpenAI.
Endpoint
Send a JSON body with Content-Type: application/json. Non-streaming requests return a JSON completion; set stream: true for server-sent events (see Streaming).
Request
Fields ThaiRouter itself reads or rewrites. Everything else is forwarded to the model server unchanged; see Parameters.
| Field | Type | Description |
|---|---|---|
| modelrequired | string | A live ThaiRouter model id, e.g. thairouter/glm-5.3-flash. Unknown or coming-soon ids return 404. |
| messagesrequired | array | Non-empty list of { role, content }. Roles system, user, assistant. |
| max_tokens | integer | Completion cap. Clamped to the model's max_output_tokens and to the context room left after the prompt. Omitted = the model's full output budget. Also sizes the credit reservation. |
| stream | boolean | Stream tokens as SSE. Usage is always included in the final chunk.Default false. |
| reasoning_effort | string | Thinking budget on reasoning models: none, minimal, low, medium, high, xhigh, max. Snapped to the nearest level the model supports. See Reasoning. |
| reasoning | object | OpenRouter-style alternative: { "effort", "enabled", "exclude" }. exclude: true strips thinking from the response. |
| temperature | number | Forwarded. 0 to 2. |
Response
A standard chat completion object. model is the ThaiRouter id you sent.
{
"id": "chatcmpl-9c3e…",
"object": "chat.completion",
"created": 1789000000,
"model": "thairouter/glm-5.3-flash",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Connection pooling คือ…",
"reasoning": "The user asks in Thai about…"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 31,
"completion_tokens": 188,
"total_tokens": 219
},
"thairouter": {
"usage_id": "7d0a2b6c-…",
"cost": 0.002975,
"reasoning_effort": "max"
}
}finish_reason is stop for a natural end and length when max_tokens cut the answer. Reasoning models may include reasoning (older server builds: reasoning_content); see Reasoning.
The thairouter object
| Field | Type | Description |
|---|---|---|
| usage_id | string (uuid) | Id of this request in your logs and credit ledger. On streams it arrives as the x-thairouter-usage-id response header instead. |
| cost | number | null | Credits charged for this request (1 credit = ฿1), computed from the returned usage. null if the model server sent no usage. |
| reasoning_effort | string | null | The thinking level actually used after snapping to what the model supports. null on non-reasoning models. On streams it is the x-thairouter-reasoning-effort header. |
thairouter is harmless. In Python it is available via r.model_extra["thairouter"]; in TypeScript cast the response or read it from the raw JSON.Request lifecycle
1. Validate
Body size, JSON, model, messages, reasoning fields. Failures here are 4xx and cost nothing.
2. Reserve
We estimate prompt tokens from the message bytes, add max_tokens (the full output budget if you omitted it), price both at the model's rates and deduct that amount. If the balance can't cover it you get 402 insufficient_quota and nothing is charged.
3. Proxy
The body is forwarded to vLLM with model rewritten to the upstream name and the reasoning request translated into what the model's chat template understands. Bodies are never persisted.
4. Settle
Actual cost is computed from the usage the model server reports and the difference is credited back (or debited if the estimate was low). An upstream error refunds the full reservation. Details in Billing.
Examples
curl https://api.thairouter.ai/v1/chat/completions \
-H "Authorization: Bearer $THAIROUTER_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "thairouter/glm-5.3-flash",
"messages": [
{"role": "system", "content": "ตอบเป็นภาษาไทย กระชับ"},
{"role": "user", "content": "อธิบาย connection pooling"}
],
"temperature": 0.7,
"max_tokens": 512
}'