Skip to main content
A turn is one user message plus the model loop it starts: the model may call platform tools, hand a call to your application, or pause for a confirmation, and it usually ends with an answer. One turn can span several requests. Each request runs one segment of the turn and streams its frames back as Server-Sent Events (SSE). POST /api/v1/conversation-instances/{instance_slug}/conversations/{conversation_id}/turns Credential: an organization API key, or a scoped key with conversations:use.

How it works

  1. You post message. The platform resolves the instance’s latest published version, checks guardrails, stores the user message, and runs model rounds.
  2. Each round may call platform tools, which run on the server and loop back to the model.
  3. The segment ends with a turn_end frame. Its stop_reason says whether the turn ended (for example end_turn), handed client tool calls to you (tool_use), or paused for a confirmation (pending_confirmation). A failure ends the segment with an error frame instead.
  4. After a handoff you post tool_results. After a confirmation pause you post confirmations. Each resume is a new request that runs the next segment of the same turn.

Request

Send exactly one of message, tool_results, or confirmations. Sending the retired depth field returns 400 invalid_request with “depth is no longer supported; every assistant turn uses gpt-6-sol.” Unknown fields other than depth are ignored.

The stream

A 200 response has these headers: Content-Type: text/event-stream, Cache-Control: no-cache, Connection: keep-alive, X-Accel-Buffering: no. Each frame is:
The JSON always carries type, equal to the event name.

Frames

Every stream ends with exactly one turn_end or one error frame. Nothing follows it. Example frames:

Usage

turn_end.usage has input_tokens, output_tokens, total_tokens, cached_input_tokens, cache_creation_input_tokens, and cache_read_input_tokens. It is cumulative across the segments of the turn, so the turn_end of the final segment reports the whole turn. cached_input_tokens is the part of input_tokens the provider served from its cache. Configured assistants run on an OpenAI model, so the two cache_* fields, which only Anthropic models fill, are 0.

Turn states

This is the one state model for a turn. Other pages link here. After an error frame, post the next message. If a pause is still open, that request returns 409 pending_turn, described below. A new message while a turn is paused returns 409 pending_turn. Its details say which resume is owed:
pause_kind: "confirm" means show the approval prompts again for the listed ids and post confirmations. pause_kind: "handoff" means post tool_results for the listed ids. A resume of the wrong kind, or a resume when nothing is paused, returns 409 turn_state_conflict.

Stop reasons

When the model uses every one of limits.max_tool_rounds, it gets one more round with no tools to write its answer.

Which config a turn runs

A new message runs the latest published version of the instance; the instance must have one (409 instance_not_published). A resume (tool_results or confirmations) runs the version the turn started on, even if a newer version was published during the pause. Every stored message carries the config_version_hash it ran under.

Errors before and after the first frame

The response status is decided when the first frame is written.
  • Before the first frame, a failure is a normal HTTP error with the JSON envelope {code, message, details?}. Nothing was streamed.
  • After the first frame, the status is already 200. A failure arrives as a terminal error frame with code, message, and, for provider_error, reason. Assistant text already streamed in that segment may be incomplete.
A turn writes its conversation frame before it calls the model, so a model provider failure arrives as an error frame, not as an HTTP status. Its code is provider_error with a reason from a closed list (rate_limited, context_overflow, model_unavailable, invalid_request, provider_auth, provider_fault), or provider_unavailable when the provider circuit is open after repeated failures. A model call that fails with a transient error (a provider rate limit or 5xx, or a network failure) is attempted up to three times in total, waiting at most 5 seconds between attempts, as long as the provider has streamed nothing for that call. Once the provider has streamed output for that call, the failure is final.

Disconnects and timeouts

  • The turn keeps running when your client disconnects. It completes and is stored; fetch the conversation to see the result.
  • One segment may run for up to 10 minutes on the server. Set your client read timeout above that.
  • Turns are exempt from the 30-second request timeout most routes have.
  • One segment runs per conversation at a time. A hold left by a segment that died without releasing it (a crash or deploy) can be taken over after 10 minutes.
  • Turn requests have their own rate limit bucket per org, separate from the default bucket other routes use. Its limit is 300 requests per minute, or the deployment’s default limit when that is higher. A 429 rate_limited carries Retry-After.
  • If a previous turn was interrupted between a tool call and its result, the platform answers the orphaned calls with errors before the next message turn runs.

Errors

A code raised after the first frame, such as attachment_changed, invalid_conversation_history, or internal_error, arrives in an error frame on the 200 stream instead of as this status. The full error envelope is described on Errors.

Client tools and confirmations

Resume a handoff or a confirmation pause.

Take a turn (API reference)

The generated endpoint contract.
Last modified on September 25, 2026