Chat Completions
The chat completions API is ScaiGrid's flagship inference endpoint. Send a conversation, get an assistant response back. Supports streaming, tool calls, multimodal input (text + images + audio), and all the parameters you expect.
Endpoint: POST /v1/inference/chat
Basic request#
1 2 3 4 5 6 7 8 9 10 11 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
Request parameters#
| Field | Type | Notes |
|---|---|---|
model |
string (required) | Frontend model slug |
messages |
array (required) | Conversation history |
max_tokens |
integer | Max output tokens. Capped by the model's max_output_tokens if set |
temperature |
float | 0.0 (deterministic) to 2.0 (creative) |
top_p |
float | Nucleus sampling, 0.0–1.0 |
stop |
string or array | Stop sequences |
seed |
integer | For reproducibility (provider-dependent) |
stream |
boolean | See Streaming below |
tools |
array | Tool definitions; see Tool calls |
tool_choice |
string or object | auto, none, required, or {"type": "function", "function": {"name": "..."}} |
thinking |
object | Per-request reasoning toggle; see Reasoning mode |
metadata |
object | Passed through to backends that support it |
Messages#
Each message has a role and content:
system— behavioral instructions. Usually the first message.user— human or upstream-system input.assistant— prior assistant responses (for multi-turn).tool— result of a tool call. Requirestool_call_id.
Multi-turn conversations#
1 2 3 4 5 6 | |
The full history is sent every time — ScaiGrid is stateless. For long-running conversations, use Sessions to store history server-side.
Multimodal content#
For vision or audio models, content can be an array of typed parts:
1 2 3 4 5 6 7 | |
Supported part types:
text— plain textimage_url—{"url": "...", "detail": "auto" | "low" | "high"}image_base64—{"data": "<base64>", "media_type": "image/png"}audio_url— for audio input on models like GPT-4o audioaudio_base64—{"data": "<base64>", "media_type": "audio/wav"}
ScaiGrid rewrites base64 images to proxy URLs on the fly, so you can send them inline without bloating your request.
Streaming#
Set "stream": true to receive tokens as they arrive. The response is Server-Sent Events (Content-Type: text/event-stream).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | |
SSE event types#
data: {...}— a chat completion chunk.choices[0].delta.contentaccumulates. Reasoning-mode requests may also carrychoices[0].delta.reasoning_contenton chunks before the visible answer starts; see Reasoning mode.data: [DONE]— stream end. Close your reader.event: error\ndata: {...}— an error occurred mid-stream. Payload has{code, message}.
Any stream always ends with data: [DONE] whether it completed normally or errored.
Tool calls#
Declare tools the model can invoke:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | |
When the model wants to call a tool, the response has tool_calls instead of content. Execute the tool, add both the assistant turn and the tool result to your history, then call again:
1 2 3 4 5 6 7 8 9 10 11 | |
The model's second call produces a plain text response using the tool result.
Reasoning mode#
Modern reasoning models — Claude Opus 4.x, GPT-5, o3, Gemini 2.5, DeepSeek-R1 — spend an internal chain-of-thought before answering. ScaiGrid exposes a single provider-neutral toggle so you can enable, budget, and surface that reasoning from any client.
Send thinking on any chat request:
1 2 3 4 5 6 7 8 9 10 11 12 | |
| Field | Type | Notes |
|---|---|---|
enabled |
boolean | Master toggle. Object present but enabled=false acts as if thinking were omitted |
budget_tokens |
integer | Max tokens the model may spend on reasoning. Provider-specific floor (Anthropic clamps to ≥1024) |
effort |
string | minimal | low | medium | high. Used by OpenAI o-series / GPT-5. Cross-provider translation table below |
include_thoughts |
boolean | If true, response carries the raw reasoning blocks; if false (default) they're stripped |
Omit the thinking field entirely and behavior is identical to any pre-reasoning request.
Which models support it#
Query GET /v1/models and filter on capabilities. Any slug tagged with thinking accepts the toggle.
1 2 3 4 5 6 7 8 9 | |
At time of writing, 41+ slugs are tagged (all Gemini 2.5+ and 3.x variants, all GPT-5.x, o1/o3/o4-mini, Qwen QwQ, Qwen QVQ). Claude Opus 4.x / Sonnet 5 and self-hosted DeepSeek-R1 slugs pick up the tag as they're registered.
Sending thinking to a non-supporting model#
ScaiGrid doesn't reject the request — the field is silently ignored on providers that don't understand it. Two things happen instead:
usage.reasoning_tokenson the response is0.- The response carries
X-Scaigrid-Thinking-Unsupported: trueas an HTTP header. Surface it in your UI as a soft warning if you want end-users to know their reasoning intent was dropped.
Budget vs effort#
Providers disagree on the shape. Send whichever you have; ScaiGrid derives the other:
| Effort | Budget floor (used when only effort is sent) |
|---|---|
| minimal | 512 |
| low | 2048 |
| medium | 8192 |
| high | 24576 |
When only budget_tokens is sent to an effort-based provider (OpenAI o-series), ScaiGrid maps it back:
| Budget | Effort tier used |
|---|---|
| < 2048 | low |
| < 8192 | medium |
| ≥ 8192 | high |
The response#
Non-streaming responses gain a nested reasoning count on usage (present but 0 when thinking was off):
1 2 3 4 5 6 7 8 | |
Reasoning tokens are billed at the same per-token price as completion tokens. A "deep-think" answer can spend 20k+ reasoning tokens; surface the split in your UI so users understand where the cost went.
When include_thoughts=true the response also carries an ordered list of the raw reasoning blocks:
1 2 3 4 5 6 | |
redacted: true marks blocks the provider returned in opaque form (Anthropic occasionally does this for safety-adjacent chains). signature is populated for Anthropic-signed blocks and is required for tool-use continuity (see next section).
Streaming reasoning#
When you set stream: true and thinking.include_thoughts=true, reasoning chunks arrive on choices[0].delta.reasoning_content before the visible answer starts. Ordinary content deltas continue as usual:
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
If include_thoughts is false (or omitted), reasoning still happens on the provider side but no reasoning_content chunks are sent — the visible content stream is identical to a non-thinking request.
Multi-turn tool-use (Anthropic only, opt-in)#
To continue a tool-use conversation from an assistant turn that included thinking, the raw thinking block MUST be echoed back verbatim on the next assistant-role message. Anthropic verifies the signature and errors otherwise.
Only relevant if: the model is anthropic/* AND your flow uses tool-use AND you want thinking on those turns.
Pattern:
- Send the initial request with
thinking.include_thoughts=true. - Save
response.thinkingalongsideresponse.contentin your conversation log. - On the next request, attach
thinking_blocksto the assistant-role message when replaying history:
1 2 3 4 5 6 | |
ScaiGrid round-trips thinking_blocks to Anthropic verbatim. Non-Anthropic providers ignore the field, so this only matters when the flow can hit Claude.
Provider-specific notes#
- Anthropic requires
temperature = 1.0when thinking is enabled. ScaiGrid silently overrides any other value you sent, logging the override so it shows up in your request trace. - Google Gemini 2.5+ bundles reasoning tokens inside
completion_tokensin some responses. You'll seereasoning_tokens=0there — nothing is wrong; the count is folded into completion. - OpenAI o-series / GPT-5 doesn't stream reasoning content in real time (as of writing).
include_thoughts=trueproduces reasoning blocks on the final response but noreasoning_contentdeltas mid-stream. - Self-hosted reasoning models (via ScaiInfer, e.g. DeepSeek-R1) will support the same shape when deployed. If a self-hosted node hasn't wired reasoning yet,
X-Scaigrid-Thinking-Unsupported: truefires and the request runs without reasoning.
Cost UX guidance#
- Default to off. Enabling thinking silently in a paid product is a support-ticket generator.
- Show the split. "Answer: 340 tokens · Reasoning: 6.2k tokens" makes the cost visible.
- Guardrail large budgets. If your product has tenant-level budget caps, warn before a big reasoning spend: "Reasoning could cost up to $X extra."
Response shape#
Non-streaming response:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
usage.reasoning_tokens is present on every response and is 0 unless the caller enabled thinking on a supporting model. See Reasoning mode.
finish_reason values:
stop— model finished naturallylength— hitmax_tokenstool_calls— model wants to call a toolcontent_filter— upstream safety filter triggered
What's next#
- Embeddings — vectorize text for search.
- OpenAI Compatibility — drop-in replacement for OpenAI SDK.
- Sessions and Rooms — server-side conversation storage.
- Errors — handling failures.