Skip to main content

Chat API Reference

This page documents the request and response schemas for the Chat API. For endpoint URLs and quickstart examples, see Getting Started.

Request Schema​

Both the streaming (POST /chat/stream) and non-streaming (POST /chat/response) endpoints accept the same request body:

{
"agent_identifier": "string (required)",
"conversation": [ChatMessage],
"conversation_context": ConversationContext,
"bot_params": {},
"stateful": false,
"session_id": "string"
}
FieldTypeRequiredDescription
agent_identifierstringYesThe identifier of the agent to invoke
conversationChatMessage[]YesThe conversation history — full history in stateless mode, only the new user message(s) on stateful follow-ups
conversation_contextConversationContextNoTop-level context for the request
bot_paramsobjectNoRuntime parameters for the agent (see below)
statefulbooleanNoPersist the agent's working state under the session (default false) — see Conversations
session_idstringNoThe conversation identifier. On the first turn, omit it (the service assigns one and returns it on the agent's message) or send a new UUID; echo it on later requests, see Conversations

Query Parameters​

ParameterRequiredDescription
tenantYesThe tenant to run the request against
index_idNoIndex identifier passed through to the agent
resumeNoSet true to resume an interrupted stateful turn — see Resuming an Interrupted Turn
message_idNoThe turn to resume; only honored together with resume=true. New turns always get a server-assigned message_id
request_timeoutNoPOST /chat/response only: a budget in seconds; a turn that exceeds it is cancelled and answered with 504
deltaNoStreaming only: set true to receive Open Responses events instead of full snapshots. Also accepted by GET /chat/stream/{message_id}

Send Message Schema​

POST /chat/stream/messages?tenant={tenant} sends a message or approval decisions to a stateful conversation, whether or not a turn is running. See Sending to a Conversation.

{
"session_id": "string (required)",
"agent_identifier": "string (required)",
"message": ChatMessage,
"tool_decisions": [ToolDecision],
"conversation_context": ConversationContext,
"bot_params": {},
"user_agent_id": "string"
}
FieldTypeRequiredDescription
session_idstringYesThe conversation
agent_identifierstringYesThe agent of the turn this request starts when none is running
messageChatMessageNoA user message, with any content parts (text, images, attached documents). Its message_id, when set, comes back on the user_message part where the agent read it
tool_decisionsToolDecision[]NoAnswers to calls waiting for approval
conversation_context, bot_params, user_agent_idNoAs in the request schema; used only by a turn this request starts

ToolDecision:

FieldTypeRequiredDescription
tool_call_idstringYesThe waiting call's tool_call_id
decision"allow_once" / "allow_session" / "deny"YesSee Approving Tool Calls
paramsobjectNoReplaces the call's inputs when allowing

The response is 202 with {"message_id": "string", "started": boolean}: the turn that reads what was sent, and whether this request started it. message_id is absent when nothing was sent, nothing waits, and no turn is running.

ChatMessage​

A single message in the conversation. The sender field is "user" for user messages and "bot" for agent messages:

{
"sender": "user" | "bot",
"content": "string",
"message_id": "string",
"session_id": "string",
"content_parts": [ContentPart],
"evidences": [ChatMessageEvidence],
"function_call_request": FunctionCallRequest,
"function_call_response": FunctionCallResponse,
"function_specs": FunctionSpec,
"image_uri": "string"
}
FieldTypeRequiredDescription
sender"user" or "bot"YesMessage author — "bot" denotes the agent's response
contentstringYesThe text content of the message
message_idstringNoUnique turn identifier (assigned by the server; used for stream reconnection and feedback)
session_idstringNoConversation identifier (the one the client sent on the first turn, or assigned by the server; echo it on follow-up requests, see Conversations)
content_partsContentPart[]NoStructured message parts (context, text, tool calls, tables, images, messages read mid-turn)
evidencesChatMessageEvidence[]NoCitation evidence linking answer spans to sources
function_call_requestobjectNoDeprecated. Legacy function call suggestion — use tool content parts instead
function_call_responseobjectNoDeprecated. Legacy function call result — use tool content parts instead
function_specsobjectNoDeprecated. Function specifications for legacy function call pattern
image_uristringNoDeprecated. Use content parts for media

ContentPart​

Each content part has a type discriminator and one corresponding data field:

TypeData FieldDescription
"text"textA text segment of the agent's response
"context"contextA ConversationContext block (documents the agent found or context the client attached)
"tool"toolA tool call event with lifecycle status
"table"tableStructured tabular data
"image"imageAn image on a user message: {"image_uri": "data:<mime>;base64,<data>"}
"user_message"text, message_idIn an agent message: a message the user sent while the turn ran, where the agent read it, with the message_id it was sent with
{
"type": "text" | "context" | "tool" | "table" | "image" | "user_message",
"text": "string",
"context": ConversationContext,
"tool": ContentPartTool,
"table": ContentPartTable,
"image": {"image_uri": "string"},
"message_id": "string"
}

ContentPartTool​

Represents a single tool call and its lifecycle. Agents stream these to communicate what they are doing:

FieldTypeRequiredDescription
tool_call_idstringYesUnique identifier for this tool invocation
namestringYesName of the tool being called
paramsobjectNoVisible parameters sent to the tool
responseobjectNoTool execution result (present when completed)
display_textstringNoHuman-readable description of what the tool is doing/did
status"running" / "completed" / "error"NoCurrent lifecycle state

A call that needs the user's approval shows as a part whose response.status is confirmation_required until it is decided; a declined call's response.status is confirmation_denied, or confirmation_unavailable where nobody could be asked. See Approving Tool Calls.

See Content Parts for how to render these in a UI.

ContentPartTable​

Structured tabular data returned by an agent:

FieldTypeDescription
format"row" or "columnar"How the data is organized
headersstring[]Column headers
rowsobject[]Row data (when format is "row") — each object maps header → value
columnsobjectColumn data (when format is "columnar") — maps header → value[]

Exactly one of rows or columns must be present.

Example (row format):

{
"type": "table",
"table": {
"format": "row",
"headers": ["Document", "Relevance"],
"rows": [
{"Document": "BERT paper", "Relevance": "High"},
{"Document": "GPT-2 paper", "Relevance": "Medium"}
]
}
}

ConversationContext​

Context scoping the conversation to specific documents, tags, filters, or custom content. Can appear at the top level of the request or inside a content_parts entry with type: "context".

{
"document_context": {
"document_ids": ["string"],
"retrieval_unit": "document" | "chunk"
},
"custom_context": {
"items": [
{"document_id": "string", "content": "string or object"}
]
},
"tag_context": {
"tag_ids": ["string"]
},
"filter_context": {
"filters": {}
},
"user_document_context": {
"enabled": true
}
}

All fields combine freely.

See Passing Context for usage patterns and examples.

ChatMessageEvidence​

A citation linking a span of the answer to a source document:

FieldTypeRequiredDescription
document_hit_urlstringYesURL path to fetch the source document/chunk
text_extractstringNoRelevant passage from the source (with <b> highlights)
anchor_textstringNoThe citation marker in the answer text (e.g., <sup>1</sup>)

See Handling Citations for rendering patterns.

Response Schema​

Non-Streaming Response (ChatResponseItem)​

{
"agent_identifier": "string",
"conversation": [ChatMessage],
"conversation_context": ConversationContext,
"function_specs": [FunctionSpec]
}

The response echoes back the full conversation with the agent's response appended as the last message.

Streaming Response (SSE)​

The streaming endpoint returns Server-Sent Events. Each event has:

event: new_message
id: {message_id}:{event_index}
data: {ChatMessage as JSON}
retry: 15000

Each event contains the complete message so far — as tokens are generated, the content field grows incrementally. The final event contains the fully formed response including evidences and content_parts. Streams replayed after a reconnection end with a stream_status control event reporting the turn's outcome. With delta=true the events are Open Responses events carrying only what changed.

See Streaming for reconnection, turn resumption, cancellation, and protocol details.

bot_params​

The bot_params field allows API clients to pass runtime parameters to the agent at request time. These parameters flow into the agent factory's dependency resolution — specifically, they fill agent constructor parameters that are not already set by the agent's static configuration.

How it works:

  1. The client sends bot_params: {"search_scope": "internal"} in the request.
  2. The handler sanitizes the dict (protected keys like tenant, request_headers, index_id are always stripped).
  3. The agent factory resolves each constructor parameter by checking the agent's static agent_configuration first, then falling back to handler_params (which includes the sanitized bot_params).
  4. If the agent class declares a parameter matching a key in bot_params, and the static configuration does not already set it, the runtime value is used.

Security model:

  • Static agent_configuration always takes precedence — bot_params cannot override values the agent developer has locked in configuration.
  • For dict-typed parameters, values are deep-merged with configuration taking precedence.
  • Protected system keys (tenant, request_headers, index_id, session_id, agent_depth, sub_agent_count, sub_agent_approvals) and every tool rule source's *_rule_source_configuration block are always stripped regardless of what the client sends, and so is the sub_agent_approvals field of the delegation and task tool configuration blocks.
  • The sub-agent limits block sub_agent_limits_configuration is stripped too, so only the agent's static configuration sets it.

When to use:

Use bot_params when the agent developer has designed specific constructor parameters to be overridable at runtime. Common patterns include passing a user-selected scope, language preference, or feature flag. The exact parameters available depend on the specific agent's implementation.

Error Handling​

HTTP Errors​

Errors from the chat service return JSON with a detail field:

{"detail": "Error description"}
StatusMeaningCommon Causes
400Bad RequestUnknown agent_identifier, agent not streamable for the streaming endpoint, malformed request body, mutually exclusive fields both set
401UnauthorizedMissing or invalid API key / Bearer token (returned by the API gateway)
403ForbiddenValid credentials but insufficient permissions for this tenant, or an agent the caller may not use (returned before a stream opens)
404Not Foundmessage_id unknown or expired — or its producer died mid-turn — when reconnecting to or cancelling a stream
409ConflictPOST /chat/stream or a rewind while the conversation has a turn running; the body's detail.message_id names it
410GoneReconnecting to a turn that finished and whose stream slot was superseded or reaped: read it from GET /conversations/{session_id} instead
422Validation ErrorRequest body fails schema validation; or, on POST /chat/response, the model provider's content filter refused the request (body {"detail": "...", "code": "content_filter"}; do not retry the same input)
429Too Many RequestsPOST /chat/response hit the model provider's rate limit; retry after the Retry-After header when present
500Internal Server ErrorUnexpected server-side failure
504Gateway TimeoutPOST /chat/response exceeded the request_timeout the caller set; the turn was cancelled
note

401 responses come from the API gateway and use {"message": "Unauthorized"} rather than {"detail": "..."}.

Validation Errors (422)​

Validation errors include per-field details in the standard FastAPI/Pydantic format:

{
"detail": [
{
"loc": ["body", "conversation", 0, "sender"],
"msg": "value is not a valid enumeration member",
"type": "value_error.enum"
}
]
}

Streaming Errors​

If the turn fails during an active SSE stream, an error event is sent with a JSON data field holding a code (RATE_LIMITED, CONTENT_FILTERED, AGENTS_STORAGE_NOT_CONFIGURED or INTERNAL) and a message. These are the streaming counterparts of the 429, 422 content_filter and 400 responses of POST /chat/response:

event: error
data: {"code": "CONTENT_FILTERED", "message": "..."}

See Error Handling for what each code means and when to reconnect.

Rewinding a Conversation​

POST /conversations/{session_id}/rewind?tenant={tenant} with {"keep_count": n} keeps the conversation's first n messages and drops the rest, so a client can regenerate an answer or resend an edited prompt as the next turn. The agent's stored working state is cut at the same point; messages and approval decisions sent to the conversation and not yet read are dropped, and tools allowed for the rest of the conversation (allow_session) ask again. Only the owner can rewind, and not while a turn is running (409). The response is the conversation as kept.

Stream Control​

EndpointPurpose
GET /chat/stream/{message_id}Reconnect to a running or finished turn, see Reconnection
DELETE /chat/stream/{message_id}Cancel a running turn, see Cancellation
DELETE /chat/stream/messages/{message_id}Take a sent message back out of the conversation's inbox before a turn reads it, see Sending to a Conversation

Realtime Transcription​

WS /chat/ws/openai/v1/realtime?intent=transcription&tenant={tenant} streams microphone audio and returns live transcripts. It speaks the OpenAI Realtime transcription protocol, so an OpenAI Realtime client with its base URL set to <host>/chat/ws/openai/v1 connects unchanged. The server relays events between the client and the provider and changes only session.update: the session type is forced to transcription and the model to the configured one. A client's own language or languages wins over the configured hint. Non-JSON frames are dropped. Authenticate as for the other endpoints, see Getting Started.

Close Codes and Errors​

Errors arrive as an OpenAI Realtime error event ({"type": "error", "event_id": ..., "error": {"type", "code", "message"}}) before the socket closes.

Close codeerror.type / error.codeCause
1000dictation_limit_reached / time_limitThe session reached max_session_seconds of wall-clock time
1000dictation_limit_reached / audio_limitThe client sent audio more than 10 seconds ahead of real time
1000noneThe client or the provider ended the session
1011server_error / transcription_unavailableThe provider failed after the socket opened (bad key, 401/429, unreachable)
4404noneRealtime transcription is not set up for the tenant, or its LLM configuration is missing or not a realtime_transcription model

Configuration​

The endpoint uses an LLM configuration of type realtime_transcription, with vendor openai or azure_openai. On Azure, name is the deployment name.

{
"name": "dictation",
"vendor": "openai",
"vendor_configuration": { "openai": { "openai_api_key_ref": "openai-api-key" } },
"model_configuration": {
"name": "gpt-4o-transcribe",
"type": "realtime_transcription",
"transcription_settings": { "languages": ["nl"], "max_session_seconds": 300 }
}
}
transcription_settings fieldTypeDefaultDescription
languageslist[str] or nullnullLanguage hints such as ["nl", "en"]; null lets the model detect them. whisper-1 and the gpt-4o transcribe models take only the first one
max_session_secondsint300Wall-clock cap per session

In a local project, realtime_transcription_setup.json (next to agent_setups.json) names the configuration from llm_configurations.json; za agents serve mounts the endpoint and reads the file on every session, so edits apply without a restart:

{ "llm_configuration_name": "dictation" }

Install the realtime extra: pip install zetaalpha.agents[realtime]. A custom host (Extending the API Server) mounts the router itself and passes a setup retriever and an LLM configuration store factory to setup_app:

from zav.agents_sdk import setup_app
from zav.agents_sdk.controllers import routers
from zav.agents_sdk.controllers.v1.realtime_transcription import realtime_transcription_router
from zav.agents_sdk.domain.realtime_transcription import FileRealtimeTranscriptionSetupRetriever

app = setup_app(
...,
llm_configuration_store_factory=my_llm_configuration_store_factory,
realtime_transcription_setup_retriever=FileRealtimeTranscriptionSetupRetriever(
"realtime_transcription_setup.json"
),
routers=list(routers) + [("", realtime_transcription_router)],
)

A retriever is a RealtimeTranscriptionSetupRetriever whose get(tenant) returns the tenant's RealtimeTranscriptionSetup, or None to turn the endpoint off for that tenant.

Deprecated Fields​

FieldReplacement
function_call_request / function_call_responsecontent_parts with type: "tool" — see Content Parts
function_specsTool specifications are now configured server-side
image_uriMedia should be handled via content parts