Create chat completion
POST /v1/chat/completions
Creates a model response for the given chat conversation. OpenAI Chat Completions compatible.
When stream is false (the default), the response is a single JSON chat.completion object (application/json).
When stream is true, the response is a text/event-stream of server-sent events. Each event’s data: field carries one chat.completion.chunk JSON object. The stream is terminated by a final data: [DONE] sentinel, which is a literal marker and not valid JSON.
Reasoning is off by default. Set reasoning_effort to low, medium or high to have the model think before it answers; the reasoning is returned separately from the answer, in choices[].message.reasoning_content (or choices[].delta.reasoning_content when streaming). See x-springboards-reasoning.
Unsupported or unknown request fields are passed through to the upstream inference endpoint unchanged (they are not rejected by the gateway).
The gateway is a drop-in replacement for OpenAI’s Chat Completions API:
point any OpenAI SDK at https://api.springboards.ai/v1 and use your
Springboards API key.
Reasoning
The model can think before it answers. That thinking costs latency and
completion tokens, so it is off by default — set reasoning_effort
when a request is worth it:
reasoning_effort | Behaviour |
|---|---|
none (default) | Answers directly. No reasoning_content in the response. |
low | A short pass. Cheapest level that still deliberates. |
medium | A fuller pass, for multi-step problems. |
high | The longest pass, for hard reasoning and analysis. |
When the effort is anything but none, the reasoning comes back
separately from the answer — in choices[].message.reasoning_content, or
choices[].delta.reasoning_content while streaming. It is billed as
completion tokens and counts against your TPM limit.
Two request forms set the effort and are treated as equivalent, so use whichever your client makes easiest:
{"reasoning_effort": "high"}
{"reasoning": {"effort": "high"}}
If both are present, reasoning_effort wins. Any other value — including
a level the API doesn’t recognise — is treated as none, so the model
only thinks when a request asks it to by name.
Request body
Content type: application/json
| Field | Type | Required | Description |
|---|---|---|---|
model | string | Yes | ID of the model to use (alias or dated version). |
messages | array of object | Yes | The conversation so far, as a list of messages. |
chat_template_kwargs | object | No | Springboards extension: key/value pairs forwarded to the upstream model’s chat template. See x-springboards-chat-template-kwargs. |
frequency_penalty | number | No | Penalty between -2 and 2 for tokens based on their frequency so far. |
max_completion_tokens | integer | No | Upper bound on the number of tokens generated for the completion. |
max_tokens | integer | No | Deprecated. Maximum number of tokens to generate. Superseded by max_completion_tokens. |
n | integer | No | Number of completions to generate for each prompt. |
presence_penalty | number | No | Penalty between -2 and 2 for tokens based on whether they appear so far. |
reasoning | object | No | Object form of reasoning_effort, for clients that send OpenAI Responses-style options. Equivalent to the reasoning_effort field; if both are present, reasoning_effort wins. |
reasoning_effort | string (none, low, medium, high) | No | How much internal reasoning the model does before it answers. Defaults to “none”: thinking is off unless a request asks for it. Reasoning is returned separately from the answer, in message.reasoning_content (or delta.reasoning_content when streaming). See x-springboards-reasoning. |
seed | integer | No | Best-effort deterministic sampling seed. |
stop | array of string | No | Up to 4 sequences where the API stops generating further tokens. |
stream | boolean | No | If true, partial deltas are streamed as server-sent events terminated by a [DONE] sentinel. |
stream_options | object | No | Options that apply only when stream is true. |
temperature | number | No | Sampling temperature between 0 and 2. |
top_p | number | No | Nucleus sampling probability mass. |
user | string | No | Stable identifier for the end user, for abuse monitoring. |
messages[]
| Field | Type | Required | Description |
|---|---|---|---|
role | string (system, user, assistant, tool) | Yes | The role of the message author. |
content | string | Yes | The contents of the message. |
name | string | No | Optional name to disambiguate participants with the same role. |
reasoning
| Field | Type | Required | Description |
|---|---|---|---|
effort | string (none, low, medium, high) | No | How much internal reasoning the model does before it answers. |
stream_options
| Field | Type | Required | Description |
|---|---|---|---|
include_usage | boolean | No | If true, a final chunk carrying token usage for the whole request is emitted before the [DONE] sentinel. Streamed responses omit usage otherwise. |
Fields not listed above are accepted and passed through to the upstream model unchanged.
Responses
200
A chat completion. Returned as a single JSON object, or as an SSE stream when stream=true.
Content type: application/json
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique identifier for the completion. |
object | string | Yes | The object type, always “chat.completion”. |
created | integer | Yes | Unix timestamp (seconds) of when the completion was created. |
model | string | Yes | The model ID the request asked for, echoed back — the alias or dated ID as sent, so it can be reused on the next request. |
choices | array of object | Yes | The list of completion choices. |
usage | object | No | Token usage statistics for the request. |
choices[]
| Field | Type | Required | Description |
|---|---|---|---|
index | integer | Yes | The index of this choice in the list. |
message | object | Yes | The generated message. |
finish_reason | string (stop, length, content_filter, tool_calls) | Yes | Why generation stopped. |
choices[].message
| Field | Type | Required | Description |
|---|---|---|---|
role | string | Yes | The role of the author, always “assistant”. |
content | string | Yes | The contents of the message. |
reasoning_content | string | No | The model’s internal reasoning, present only when reasoning_effort is something other than “none”. |
usage
| Field | Type | Required | Description |
|---|---|---|---|
prompt_tokens | integer | Yes | Tokens in the prompt. |
completion_tokens | integer | Yes | Tokens in the generated completion. |
total_tokens | integer | Yes | Total tokens used (prompt + completion). |
Streaming (text/event-stream)
Each server-sent event is a bare data: line carrying one JSON payload. The stream is terminated by a final data: [DONE] sentinel — a literal marker, not JSON:
data: {"id":"chatcmpl-123","object":"chat.completion.chunk",...}
data: {"id":"chatcmpl-123","object":"chat.completion.chunk",...}
data: [DONE]
Event payload:
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique identifier for the completion; shared across all chunks. |
object | string | Yes | The object type, always “chat.completion.chunk”. |
created | integer | Yes | Unix timestamp (seconds) of when the completion was created. |
model | string | Yes | The model ID the request asked for, echoed back. |
choices | array of object | Yes | The list of streamed choice deltas. Empty on the final usage chunk. |
usage | object | No | Token usage for the whole request. Sent on a final chunk only when the request set stream_options.include_usage. |
choices[]
| Field | Type | Required | Description |
|---|---|---|---|
index | integer | Yes | The index of this choice in the list. |
delta | object | Yes | The incremental message content for this chunk. |
finish_reason | string (stop, length, content_filter, tool_calls) | Yes | Why generation stopped, or null while streaming. |
choices[].delta
| Field | Type | Required | Description |
|---|---|---|---|
content | string | No | The token(s) appended by this chunk. |
reasoning_content | string | No | The reasoning token(s) appended by this chunk. Reasoning streams before content, and only when reasoning_effort is something other than “none”. |
role | string | No | Present on the first chunk of a choice. |
usage
| Field | Type | Required | Description |
|---|---|---|---|
prompt_tokens | integer | Yes | Tokens in the prompt. |
completion_tokens | integer | Yes | Tokens in the generated completion. |
total_tokens | integer | Yes | Total tokens used (prompt + completion). |
400
The request body was malformed or missing the required model field.
Content type: application/json
| Field | Type | Required | Description |
|---|---|---|---|
error | object | Yes | The failure. |
error
| Field | Type | Required | Description |
|---|---|---|---|
message | string | Yes | Human-readable description of the failure. |
type | string | Yes | OpenAI error taxonomy value. |
param | string | Yes | The request parameter at fault, when one applies. |
code | string | Yes | Machine-readable error code, when one applies. |
401
The API key is missing, malformed, unknown or revoked (code: invalid_api_key).
Content type: application/json
| Field | Type | Required | Description |
|---|---|---|---|
error | object | Yes | The failure. |
error
| Field | Type | Required | Description |
|---|---|---|---|
message | string | Yes | Human-readable description of the failure. |
type | string | Yes | OpenAI error taxonomy value. |
param | string | Yes | The request parameter at fault, when one applies. |
code | string | Yes | Machine-readable error code, when one applies. |
404
The requested model does not exist.
Content type: application/json
| Field | Type | Required | Description |
|---|---|---|---|
error | object | Yes | The failure. |
error
| Field | Type | Required | Description |
|---|---|---|---|
message | string | Yes | Human-readable description of the failure. |
type | string | Yes | OpenAI error taxonomy value. |
param | string | Yes | The request parameter at fault, when one applies. |
code | string | Yes | Machine-readable error code, when one applies. |
413
The request body exceeds the 5 MiB limit.
Content type: application/json
| Field | Type | Required | Description |
|---|---|---|---|
error | object | Yes | The failure. |
error
| Field | Type | Required | Description |
|---|---|---|---|
message | string | Yes | Human-readable description of the failure. |
type | string | Yes | OpenAI error taxonomy value. |
param | string | Yes | The request parameter at fault, when one applies. |
code | string | Yes | Machine-readable error code, when one applies. |
429
Rate limit or quota exhausted. code is rate_limit_exceeded when the workspace’s requests- or tokens-per-minute bucket is empty — retry after the Retry-After header — or insufficient_quota when the workspace is out of billing quota, which retrying will not fix.
Content type: application/json
| Field | Type | Required | Description |
|---|---|---|---|
error | object | Yes | The failure. |
error
| Field | Type | Required | Description |
|---|---|---|---|
message | string | Yes | Human-readable description of the failure. |
type | string | Yes | OpenAI error taxonomy value. |
param | string | Yes | The request parameter at fault, when one applies. |
code | string | Yes | Machine-readable error code, when one applies. |
502
The upstream inference endpoint could not be reached. Retryable.
Content type: application/json
| Field | Type | Required | Description |
|---|---|---|---|
error | object | Yes | The failure. |
error
| Field | Type | Required | Description |
|---|---|---|---|
message | string | Yes | Human-readable description of the failure. |
type | string | Yes | OpenAI error taxonomy value. |
param | string | Yes | The request parameter at fault, when one applies. |
code | string | Yes | Machine-readable error code, when one applies. |
503
The API key could not be validated right now. Transient and retryable; the key is not at fault.
Content type: application/json
| Field | Type | Required | Description |
|---|---|---|---|
error | object | Yes | The failure. |
error
| Field | Type | Required | Description |
|---|---|---|---|
message | string | Yes | Human-readable description of the failure. |
type | string | Yes | OpenAI error taxonomy value. |
param | string | Yes | The request parameter at fault, when one applies. |
code | string | Yes | Machine-readable error code, when one applies. |
Examples
Basic request
curl https://api.springboards.ai/v1/chat/completions \
-H "Authorization: Bearer $SPRINGBOARDS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "flint-alpha",
"messages": [{"role": "user", "content": "Hello!"}]
}'
Streaming
curl -N https://api.springboards.ai/v1/chat/completions \
-H "Authorization: Bearer $SPRINGBOARDS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "flint-alpha",
"messages": [{"role": "user", "content": "Hello!"}],
"stream": true
}'
Reasoning
curl https://api.springboards.ai/v1/chat/completions \
-H "Authorization: Bearer $SPRINGBOARDS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "flint-alpha",
"messages": [{"role": "user", "content": "How many r's are in strawberry?"}],
"reasoning_effort": "high"
}'
# -> choices[0].message.reasoning_content holds the thinking,
# choices[0].message.content holds the answer.
OpenAI Python SDK
from openai import OpenAI
client = OpenAI(
base_url="https://api.springboards.ai/v1",
api_key=os.environ["SPRINGBOARDS_API_KEY"],
)
completion = client.chat.completions.create(
model="flint-alpha",
messages=[{"role": "user", "content": "Hello!"}],
)
print(completion.choices[0].message.content)
Notes
chat_template_kwargs is a Springboards (non-OpenAI) extension. Its key/value pairs are forwarded verbatim to the upstream model’s chat template, allowing per-request template variables that OpenAI’s schema has no field for.
Reasoning effort accepts none, low, medium or high, and defaults to none — the model answers directly unless a request asks it to think. Higher levels trade latency and tokens for more deliberation; reasoning tokens are billed as completion tokens and count against the workspace’s TPM limit.
Two request forms set the effort, and they are equivalent \u2014 the gateway normalises whichever one it receives before calling the model:
{"reasoning_effort": "high"}
{"reasoning": {"effort": "high"}}
reasoning_effort wins over reasoning.effort when both are present. Only a recognised level turns reasoning on: a missing, null or unrecognised value resolves to none, so the model never picks a thinking level of its own.