Skip to main content

Agent Execution

Execute agents via Server-Sent Events (SSE) streaming and manage conversation sessions.

Authentication​

All execution endpoints support hybrid authentication:

MethodHeader
JWTAuthorization: Bearer <jwt> + x-tenant-id
API KeyX-Api-Key: YOUR_API_KEY

POST /api/v2/agents/:agent_id/stream​

Execute an agent with real-time SSE streaming.

Path Parameters:

ParamTypeDescription
agent_idUUIDAgent ID

Request Body:

{
"message": "List active contracts",
"session_id": "uuid (optional)",
"attachments": [
{
"url": "https://example.com/image.png",
"type": "image"
}
],
"consumer_key": "string (optional, admin/owner API key)",
"context": { "cliente": "Ana" },
"bindings": { "idSolicitante": "u-4821" },
"session_from": ["idSolicitante"],
"options": {
"timeout_ms": 60000
},
"metadata": {
"trace_id": "string",
"correlation_id": "string"
}
}
FieldTypeRequiredDescription
messagestringYesUser message
session_idUUIDNoSession ID to maintain conversation context
attachmentsarrayNoMultimodal files (see Media)
consumer_keystringNoPerson identity. Admin/owner API key only
contextobjectNoData the agent reads. Admin/owner API key only
bindingsobjectNoAuthoritative tool arguments, never shown to the model. Admin/owner API key only
session_fromstring[]NoDerives the session from context/bindings fields. Do not combine with session_id
optionsobjectNoOnly options.timeout_ms has effect (shortens the turn deadline). Other keys, such as temperature and max_tokens, are ignored and reported in ignored_fields — agent settings live in llm_config
client_message_idUUIDNoId of this message generated by your client. Resending the same id after a finished turn replays the answer without running the agent again (24 h). See the contract
metadataobjectNoCorrelation only (trace_id…). Stored on the execution, never sent to the model or tools; it does not change agent behavior

Full rules, limits and examples: Agent invocation contract.

Attachments​

FieldTypeRequiredDescription
urlstringYesFile URL or base64 data URI
typestringNoimage, audio, document (auto-detect if omitted)
filenamestringNoFilename (helps type detection)

Supported types:

  • image: jpg, jpeg, png, gif, webp, bmp
  • audio: mp3, wav, ogg, flac, m4a, webm, mp4
  • document: pdf, xlsx, xls, docx, csv, txt

SSE Response​

See Streaming for the complete event format.

The metadata event carries the turn's execution_id; the response and done events carry execution_id, user_message_id and assistant_message_id (the same ids the session messages pagination returns; null when nothing was recorded). A second message in the same conversation while the first is being answered gets error with code: "SESSION_BUSY". See Agent invocation contract.

Errors:

CodeStatusDescription
VALIDATION_ERROR400Missing message field
AGENT_NOT_FOUND403Agent not available
AGENT_ARCHIVED—Archived agent — execution blocked
SESSION_BUSY409Another turn is still being answered in this conversation (or the same client_message_id is in flight)
IDEMPOTENCY_CONFLICT409The client_message_id was already used with a different message
STREAM_ERROR500Error during streaming

GET /api/v2/agents​

List the agents available to the caller. Each item includes id, name, commercial_name, bio, type, status, tags, tools_count, system_label and avatar_url (the last two are null when not set). avatar_url and system_label are returned for every role, including admin, editor and owner.


GET /api/v2/agents/:agent_id/sessions​

List conversation sessions for an agent.

Query Parameters:

ParamTypeDefaultDescription
limitinteger20Max sessions returned
offsetinteger0Pagination offset
include_expiredbooleanfalseInclude expired sessions

Response:

{
"success": true,
"data": [
{
"id": "ee59c2b3-1466-4367-a365-7d62a9d93a2d",
"created_at": "2026-01-14T10:30:00.000Z",
"updated_at": "2026-01-14T11:45:00.000Z",
"message_count": 8,
"last_message_preview": "Found 5 active contracts for the client...",
"last_message_role": "assistant"
}
],
"count": 2,
"agent_id": "uuid"
}

Session Management​

Sessions are managed through the /api/v1/sessions endpoints.

Session Expiration (TTL by Plan)​

PlanSession TTL
free24 hours
basic7 days
pro7 days
enterprise30 days
  • Each user interaction renews expires_at based on the current plan
  • Expired sessions are deleted by a cleanup job (every 6 hours)

Key Endpoints​

EndpointMethodDescription
GET /api/v1/sessionsGETList sessions with filters: status, agent_id, start_date, end_date, control_mode (engaged|suspended|manual_handoff), consumer_key, page, limit, order_by, order_dir. An unknown status returns 400 listing the accepted values; meta.has_more tells whether there is a next page
GET /api/v1/sessions/activeGETCurrently executing sessions
GET /api/v1/sessions/statsGETAggregated metrics
GET /api/v1/sessions/:sessionIdGETSession details with conversation history, system prompt, control_mode, control_metadata, and active_turn (see Turn in progress)
GET /api/v1/sessions/:sessionId/tool-callsGETGranular tool call logs (input, output, duration)
GET /api/v1/sessions/:executionId/liveGETReal-time SSE stream
POST /api/v1/sessions/:sessionId/archivePOSTArchive session
POST /api/v1/sessions/:sessionId/clearPOSTClear conversation history
POST /api/v1/sessions/:sessionId/compactPOSTCompact session (replace old messages with LLM summary)
POST /api/v1/sessions/:sessionId/suspendPOSTSuspend the agent for this session (control_mode='suspended'). Optional body: {reason?, message?}. Requires sessions:update.
POST /api/v1/sessions/:sessionId/resumePOSTResume the agent (control_mode='engaged').
POST /api/v1/sessions/:sessionId/handoffPOSTMark the session as taken by a human operator (control_mode='manual_handoff').
POST /api/v1/sessions/:sessionId/cancelPOSTCancel the in-progress turn (mid-LLM, mid-tool). Optional body: {reason?, execution_id?}. Returns {cancelled, local_aborted, published, reason, session_id, scope, execution_id?}.
POST /api/v1/sessions/:sessionId/manual-messagePOSTSend a manual operator message through the agent's outbound channel. Requires control_mode != engaged. Body: {message: string}. Header Idempotency-Key optional (5 min cache for safe retries).
POST /api/v1/sessions/batch/archivePOSTArchive multiple sessions (max 50)
POST /api/v1/sessions/batch/compactPOSTBatch compact eligible sessions

Session Control Modes​

Sessions have a control_mode field that controls whether the agent processes new messages:

ModeBehavior
engaged (default)Agent processes messages normally
suspendedAgent loop is bypassed for new messages. Use to pause temporarily (review, investigation). Optional control_metadata.expires_at triggers automatic resume.
manual_handoffSame bypass as suspended, but signals that a human operator is handling the conversation. Use POST /:sessionId/manual-message to send messages through the agent's outbound channel.

When a message arrives for a non-engaged session, the platform records the control change for operator dashboards and returns an empty acknowledgment to the channel (no LLM call, no quota consumed).

Cancel vs Suspend​

  • /suspend blocks the next turn on this session. The current turn (if in progress) completes normally.
  • /cancel aborts the current turn in flight (mid-LLM, mid-tool, mid-stream). The next message is processed normally unless the session is also suspended.

Cancelling a turn​

  • The user who owns the conversation (including the member role) can cancel their own turn. An operator can cancel any session of the company. A session from another company returns 404.
  • Body: { "reason": "…", "execution_id": "…" }, both optional. With execution_id (the one in the stream's metadata event), only that turn is cancelled, and it also works if the request arrives before the turn has started. Without it, the turn in progress in the session is cancelled.
  • The response keeps cancelled, local_aborted, published and reason, and adds session_id, scope (session or execution) and execution_id (when sent).
  • Closing the /stream connection still cancels the turn. For an explicit "Stop" button, prefer calling /cancel with the execution_id.

Session Compaction​

Compaction replaces old messages with an LLM-generated summary, preserving recent messages intact.

Compaction thresholds scale with your plan — higher plans keep longer histories and preserve more recent messages before a summary kicks in.

Derived Sessions (Webhooks)​

Sessions created via webhooks with session_strategy.mode: 'derive' use deterministic session IDs derived from the sender's identity: the same user always generates the same session_id.

When an archived derived session receives a new message:

  1. The same deterministic session_id is generated
  2. No active session is found
  3. A new session is created with clean history
  4. The session is reactivated automatically

Turn in progress​

GET /api/v1/sessions/:sessionId returns data.session.active_turn: { "execution_id": "...", "started_at": "..." } while a turn is running in that conversation, or null otherwise (and when it could not be determined). It only reflects conversations driven through POST /api/v2/agents/:agent_id/stream. Use the execution_id to cancel that specific turn.

Interrupted replies​

When the user stops a reply, or the turn hits its time limit, the agent message is kept in the history with metadata.interrupted set to user or timeout. The partial text is stored only when it is safe to do so; otherwise the message is a short platform note saying the reply was interrupted. The agent is aware that its previous reply was cut.

GET /api/v1/me/events​

Server-Sent Events stream with the signed-in user's conversation list changes (user token only). Events:

EventWhenMain fields
session_updatedA turn ended and the conversation changedsession_id, agent_id, last_message_role, last_message_preview, execution_id (the turn that caused it), at
turn_startedA turn started in one of the user's conversationssession_id, agent_id, execution_id, started_at, at
turn_finishedThe turn ended (including with an error)session_id, agent_id, execution_id, outcome, at

turn_started and turn_finished are emitted only for turns started through /stream. A client that sent the turn itself can match execution_id to ignore its own session_updated.

GET /api/v1/me/unread-by-tenant​

Unread conversations and pending approvals for each company the signed-in user is an active member of. Requires a user token (JWT); an API key returns 403.

{
"success": true,
"data": [
{ "tenant_id": "uuid", "tenant_name": "Acme", "unread": 3, "pending_approvals": 1 }
],
"meta": { "total": 1 }
}
FieldDescription
unreadConversations with unread agent messages
pending_approvalsApprovals awaiting this user's decision

GET /api/v1/sessions/:sessionId/tool-calls​

Get detailed tool call logs for a session. Each entry includes the tool's input arguments, output result, execution duration, and success/failure status.

Query Parameters:

ParamTypeDefaultDescription
iterationinteger—Filter by agent loop iteration
tool_namestring—Filter by tool name

Response:

{
"success": true,
"data": [
{
"id": "uuid",
"tool_name": "search_contracts",
"tool_call_id": "call_abc123",
"tool_input": { "status": "active", "limit": 10 },
"tool_output": { "rows": [...], "count": 5 },
"output_preview": "5 rows returned",
"success": true,
"duration_ms": 450,
"error_message": null,
"iteration": 1,
"call_index": 0,
"execution_id": "uuid",
"created_at": "2026-02-19T14:30:01Z"
}
],
"count": 1,
"session_id": "a1b2c3d4-..."
}
tip

Tool call logs are recorded starting from a previous release. Sessions from before this update will return an empty array.

Conversation History Normalization​

Starting from a previous release, the agent loop persists the complete tool call sequence internally (including role: 'tool' messages and assistant messages with tool_calls). However, all session endpoints normalize the conversation_history before returning:

  • Messages with role: 'tool' are filtered out (internal to the agent loop)
  • Assistant messages that only contain tool_calls (no text content) are filtered out
  • Technical fields (tool_calls, tool_call_id, name) are stripped from remaining messages
  • The message_count in session lists reflects only renderable messages (user + assistant with content)

This ensures API consumers always receive the familiar {role: 'user'|'assistant', content: string} format.

Template Variables in System Prompt​

The agent's system prompt supports dynamic template variables that are resolved at execution time. This allows personas to reference current date/time without manual updates.

VariableDescriptionExample
{{current_date}}Today's date (pt-BR format)02/03/2026
{{current_time}}Current time (pt-BR format)14:30:00
{{current_weekday}}Weekday name (pt-BR)segunda-feira
{{agent_name}}The agent's nameSales Assistant

Variables are interpolated after the system prompt is assembled and before it is sent to the LLM. Unrecognized variables (e.g., {{custom_field}}) are preserved as-is.

System Prompt Snapshot​

The GET /api/v1/sessions/:sessionId response includes a top-level system_prompt field containing the exact system prompt used in the first turn of the session. This is captured once and never overwritten, even if the agent's persona changes later.

{
"data": {
"session": { "..." },
"conversation_history": [ "..." ],
"metrics": { "..." },
"logs": [ "..." ],
"system_prompt": "# Agent Name\n\n## Identidade\n..."
}
}
Scenariosystem_prompt value
Session created after this feature was introducedFull system prompt string
Session created in older versionsnull
Session with no interactions yetnull

GET /api/v2/agents/:agent_id/tools​

Get the complete JSON Schema for each tool available to the agent.

Response:

{
"success": true,
"data": [
{
"name": "list_contracts",
"description": "List active client contracts",
"parameters": {
"type": "object",
"properties": {
"customer_id": {
"type": "string",
"description": "Client ID"
}
},
"required": ["customer_id"]
}
}
],
"count": 39,
"agent_id": "uuid"
}

Examples​

Execute Agent (Stream)​

curl -N "https://llm.zihin.ai/api/v2/agents/AGENT_ID/stream" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"message": "List active contracts"}'

Execute with Attachment​

curl -N -X POST "https://llm.zihin.ai/api/v2/agents/AGENT_ID/stream" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": "What do you see in this image?",
"attachments": [
{ "url": "https://example.com/photo.jpg", "type": "image" }
]
}'

Continue Conversation​

curl -N "https://llm.zihin.ai/api/v2/agents/AGENT_ID/stream" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type": "application/json" \
-d '{"message": "Show more details", "session_id": "SESSION_ID"}'