Chat Completions API
/v1/chat/completions implements OpenAI’s Chat Completions format. You send the
whole conversation as a messages array and get one reply back. It is the
default endpoint, what most OpenAI code already speaks, and where
planner mode runs.
If you would rather the server keep the thread for you, and you want to retrieve or chain responses by id, use the Responses API instead.
The request
cURL
Python
Each message carries a role and content:
Carrying a conversation
This endpoint does not keep your conversation. Each request is independent, so a
follow-up turn means sending the earlier turns again, with the model’s own
replies as assistant messages:
That is the same as OpenAI’s Chat Completions API, so existing conversation handling carries over unchanged.
Because you resend the history every turn, it grows, and the whole array counts
against the model’s context window along with the reply. Keep an eye on the
context window for the model you are
using and drop the oldest turns as the conversation gets long. If you would
rather not manage this yourself, the Responses
API reconstructs the thread server
side from previous_response_id.
The reply
finish_reason tells you why generation stopped, and is the field to branch on:
Handle an unrecognized value by treating the reply as finished, so a new value does not break your client.
The rest of the fields:
choices[0].message.content- the assistant’s reply.choices[0].message.reasoning_content- the model’s reasoning:nullwhen reasoning is off, an empty string when the model reasoned without producing text. See Reasoning.choices[0].message.tool_calls- tools the model wants you to run. See Tool Calling.usage.prompt_tokens/completion_tokens/total_tokens- token accounting for cost and limits.usage.prompt_tokens_details.cached_tokens- how much of your prompt was served from cache. Cached tokens still count towardprompt_tokens. See Prompt caching for what keeps a prefix cacheable.usage.prompt_tokens_details.created_cache_tokens- how much of your prompt was written to the cache. It is non-zero on the first request with a new prefix and drops to zero once that prefix is cached, which is what tells a cold cache apart from one that is not being used.usage.completion_tokens_details.reasoning_tokens- tokens spent reasoning before answering. They are part ofcompletion_tokens.
The response may gain fields over time. Read the ones you need and ignore any you do not recognize, rather than depending on the exact shape.
Streaming
Set "stream": true and the reply arrives as server-sent events instead of one
JSON body. Use it for anything a person is waiting on, so they see text appear
rather than a blank screen.
Each frame is a data: line holding one chunk, and the stream ends with a
literal [DONE] sentinel:
Four things to handle:
- Check
choicesis non-empty before indexing it. The final chunk before the sentinel reportsusageand carrieschoices: [], so code that reaches straight forchoices[0]on every chunk will fail on the last one. - Concatenate
choices[0].delta.contentacross chunks to build the reply. Any given chunk may carry no content. - Read
finish_reasonon every chunk. It arrives on a chunk whosedeltais empty, so do not wait for one that also carries content. - Stop on
data: [DONE]. It is a literal string, not JSON, so parse it as a sentinel before attempting to decode the payload.
All four, in a loop over the raw HTTP response:
Passing stream=True to requests.post matters as much as sending it in the
body: without it the client buffers the whole response and you get the text in
one piece at the end.
When reasoning is on, it streams in its own field and generally arrives before
any content:
If you use the OpenAI SDK, its streaming iterator handles the framing and the sentinel for you.
Tool calls also arrive in the delta when streaming. Each call comes complete in
a single chunk, keyed by its index and carrying the whole arguments string,
on a chunk with finish_reason: "tool_calls". You do not need to accumulate
argument fragments. See Tool
Calling for what to do with the
call once you have it.
Image input
Ask about an image by sending an image_url content part alongside your text,
in OpenAI’s format. Every model takes image input, under the same rules: the
same formats, up to ten images per request, and up to 20 MB per image
once decoded. Send the image inline as a data: URI.
cURL
Python
The Python example reuses the client built in The request.
Send images inline as a data: URI. ASI:One does not fetch images from
arbitrary hosts, so a hosted https link is not a substitute. That includes
any link behind authentication or on a private network.
An image that cannot be read is rejected rather than quietly left out, so a
200 means every image you sent was used. The request returns 400 with
code: "unsupported_content_part" and param naming the part, for an
image_url carrying no URL, base64 that does not decode, an image over 20 MB
once decoded, more images than the model accepts, a host ASI:One does not fetch
from, and any content part that is not text or image_url. Correct the part and send it
again; see Errors.
The answer comes back as ordinary text, so nothing else about the request or the
reply changes. /v1/responses
takes images too, under the same limits as here, with OpenAI’s input_image part.
What else this endpoint takes
- Tool calling with
toolsandtool_choice. - Structured data with
response_format. - Reasoning with
reasoning_effort, plusenable_thinkingandthinking_budget. - Planner mode with
planner_mode, which runs here and not on/v1/responses. - Image input with OpenAI’s
image_urlcontent parts, on every model. Send the image inline as adata:URI.
For which OpenAI parameters apply here and which belong on /v1/responses, see
OpenAI Compatibility.
Next steps
- Responses API - The stateful alternative, with retrieve and chaining
- Tool Calling - Let the model call your own functions
- Planner Mode - Multi-step work against Agentverse agents
- Errors and Rate Limits - What each status code means and what to retry