curl --request POST \
--url https://api.usenexio.com/api/v1/conversation-instances/{instance_slug}/conversations/{conversation_id}/turns \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"end_user": "<string>",
"message": "<string>",
"edit_of_message_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"attachment_ids": [
"3c90c3cc-0d44-4b50-8888-8dd25736052a"
],
"tool_results": [
{
"tool_call_id": "<string>",
"content": "<unknown>",
"is_error": true
}
],
"confirmations": [
{
"tool_call_id": "<string>",
"approved": true,
"reason": "<string>"
}
],
"page_context": "<unknown>"
}
'import requests
url = "https://api.usenexio.com/api/v1/conversation-instances/{instance_slug}/conversations/{conversation_id}/turns"
payload = {
"end_user": "<string>",
"message": "<string>",
"edit_of_message_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"attachment_ids": ["3c90c3cc-0d44-4b50-8888-8dd25736052a"],
"tool_results": [
{
"tool_call_id": "<string>",
"content": "<unknown>",
"is_error": True
}
],
"confirmations": [
{
"tool_call_id": "<string>",
"approved": True,
"reason": "<string>"
}
],
"page_context": "<unknown>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
end_user: '<string>',
message: '<string>',
edit_of_message_id: '3c90c3cc-0d44-4b50-8888-8dd25736052a',
attachment_ids: ['3c90c3cc-0d44-4b50-8888-8dd25736052a'],
tool_results: [{tool_call_id: '<string>', content: '<unknown>', is_error: true}],
confirmations: [{tool_call_id: '<string>', approved: true, reason: '<string>'}],
page_context: '<unknown>'
})
};
fetch('https://api.usenexio.com/api/v1/conversation-instances/{instance_slug}/conversations/{conversation_id}/turns', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));"<string>"{
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
}{
"code": "unauthorized",
"message": "Missing or invalid API key"
}{
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
}{
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
}{
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
}{
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
}{
"code": "rate_limited",
"message": "Rate limit exceeded"
}{
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
}{
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
}Take a turn
Send a message, tool results, or confirmations and stream the turn as Server-Sent Events.
curl --request POST \
--url https://api.usenexio.com/api/v1/conversation-instances/{instance_slug}/conversations/{conversation_id}/turns \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"end_user": "<string>",
"message": "<string>",
"edit_of_message_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"attachment_ids": [
"3c90c3cc-0d44-4b50-8888-8dd25736052a"
],
"tool_results": [
{
"tool_call_id": "<string>",
"content": "<unknown>",
"is_error": true
}
],
"confirmations": [
{
"tool_call_id": "<string>",
"approved": true,
"reason": "<string>"
}
],
"page_context": "<unknown>"
}
'import requests
url = "https://api.usenexio.com/api/v1/conversation-instances/{instance_slug}/conversations/{conversation_id}/turns"
payload = {
"end_user": "<string>",
"message": "<string>",
"edit_of_message_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"attachment_ids": ["3c90c3cc-0d44-4b50-8888-8dd25736052a"],
"tool_results": [
{
"tool_call_id": "<string>",
"content": "<unknown>",
"is_error": True
}
],
"confirmations": [
{
"tool_call_id": "<string>",
"approved": True,
"reason": "<string>"
}
],
"page_context": "<unknown>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
end_user: '<string>',
message: '<string>',
edit_of_message_id: '3c90c3cc-0d44-4b50-8888-8dd25736052a',
attachment_ids: ['3c90c3cc-0d44-4b50-8888-8dd25736052a'],
tool_results: [{tool_call_id: '<string>', content: '<unknown>', is_error: true}],
confirmations: [{tool_call_id: '<string>', approved: true, reason: '<string>'}],
page_context: '<unknown>'
})
};
fetch('https://api.usenexio.com/api/v1/conversation-instances/{instance_slug}/conversations/{conversation_id}/turns', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));"<string>"{
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
}{
"code": "unauthorized",
"message": "Missing or invalid API key"
}{
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
}{
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
}{
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
}{
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
}{
"code": "rate_limited",
"message": "Rate limit exceeded"
}{
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
}{
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
}Behavior
The response is a Server-Sent Events stream, not JSON. Each frame isevent: <type>, then data: <json>, then a blank line, and every stream ends with exactly one turn_end or error frame. A failure before the first frame is a normal JSON error; after it, the status is already 200 and the failure arrives as an error frame.
tool_results and confirmations must cover exactly the call ids the paused turn is waiting for: no missing ids, no extras, no duplicates. A new message while a turn is paused is 409 pending_turn with details.pause_kind and details.pending_call_ids.
The turn keeps running if the client disconnects. One segment may run for up to 10 minutes. Turn requests have their own rate limit bucket per org: 300 requests per minute, or the deployment’s default limit when that is higher.
Every frame, the turn state model, and every stop reason are on Turns and streaming. Resumes are on Client tools and confirmations.Authorizations
Send the key as Authorization: Bearer <key>. Two kinds of key exist.
Organization keys are issued in the portal (Settings, then API keys),
each bound to one environment, shaped nx_<environment slug>_<64 hex>. They carry
no capabilities and pass every capability check, with one exception:
routes under /api/v1/records, /api/v1/engines/{id}/opportunities,
/api/v1/graph and /api/v1/catalog/documents accept an
organization key only when its environment is live, and refuse any
other with 403 scoped_key_required. Revocation takes effect within 60
seconds.
Scoped keys are issued by Nexio on request, shaped
nxsk_v1_<24 hex key id>_<43 character secret>. Each is bound to one
org, one environment, a set of engines and a set of capabilities. A
malformed, unknown or revoked nxsk_ key fails with 401 and is never
retried as an organization key. Revocation takes effect on the next
request. A scoped key without a route's capability gets 403
insufficient_capability; a scoped key not bound to the engine gets 403
engine_binding_forbidden.
Key-grantable capabilities: engines:read, runs:write, runs:read,
runs:defensibility:read, runs:test, catalog:read,
catalog:documents:read, webhooks:manage, conversations:use,
conversations:export, records:read, records:opportunities:run,
actions:write, actions:read, graph:read, records:analyze.
Routes that accept organization keys only (every scoped key gets 403
insufficient_capability): environment management, engine create,
update, configuration and publish, and conversation instance
authoring. Each operation description names the capability a scoped
key needs.
Path Parameters
Conversation instance identifier slug (e.g. platform-assistant).
Body
The asserted end-user identity the conversation must belong to; required on every turn. A missing assertion is a 400, a mismatch a 404.
New end-user input (at most 32000 characters; longer
messages reject with message_too_long). Leading and
trailing spaces are trimmed; a blank message answers
invalid_turn_request. Exactly one of the three starters.
32000Revises the referenced user message: the value of message
is stored as a sibling version of that message, and the new
branch is served from then on. Requires message; omit it
on an ordinary turn.
Attachments this message carries. Each must already be
uploaded to this conversation and belong to the same end
user; the platform loads the bytes and hands the files to
the model. Requires message, and is rejected alongside
tool_results or confirmations.
A named attachment that is missing, out of scope, or not
yet readable fails the whole turn rather than running it
with fewer documents than were attached. An attachment
that is a container (an email or a zip) resolves to the
files inside it. The combined bytes are capped at the
provider request limit; over it returns 413.
Client-tool results completing a tool_use handoff.
Untrusted consumer page context, any JSON value, at most 16384 bytes. Sent to the model as a delimited data block on every round of this segment; never stored.
Response
A Server-Sent Events stream of the instance turn. Frames are
event: <type> with a JSON data: payload.
SSE frames, each event: <type> then data: <json> then a
blank line. Every payload carries type.
attachment ({type, message_id, attachments: [{id, name, media_type, notice?}]}) comes first, only when the message
carries files. conversation ({type, conversation_id, turn_id, user_message_id?}) comes first otherwise;
user_message_id is absent on a resume. Then any of
guardrail ({type, rule_id, decision}, decision
refused, escalated, output_check_triggered),
text_delta ({type, text}), tool_activity
({type, tool_call_id, name, execution, phase}, execution
platform or client, phase started, completed,
failed), tool_use ({type, tool_call_id, name, input},
client tools only), component ({type, component, version, props}), pending_confirmation ({type, tool_call_id, name, input, reason, execution}). Exactly one terminal frame ends
the segment: turn_end ({type, stop_reason, usage}) or
error ({type, code, message, reason?}; codes include
provider_error, provider_unavailable,
invalid_conversation_history, attachment_changed,
attachment_read_failed, attachments_unavailable and
internal_error). usage is
cumulative across the turn's segments and has the six token
fields of the stateless converse route. stop_reason is a
provider value (end_turn, max_tokens) or one of
tool_use, pending_confirmation, refusal, escalated,
output_check_triggered, max_output_tokens.