ClouisleClouisle

Agent Chat API

Send Agent messages and receive text, sources, tools, and media results

The Chat API allows you to send messages to agents, stream responses, manage runs, and handle conversations. Chat endpoints live under /api/v1/agents; conversation-management endpoints live under /api/v1/conversations and /api/v1/agents/conversations.

Authentication

POST /api/v1/agents/{agent_id}/chat and /chat/stream support either a JWT bearer token or an API key. Conversation management and message-version routes require the authenticated JWT user who owns the conversation.

Required scopes:

  • agent:read - View agents
  • agent:chat - Chat with agents
  • conversation:read - List and view conversations
  • conversation:delete - Delete conversations

The Agent must be published. The API Key needs the matching permission, and if bound to an Agent, it can only access IDs in the allowlist. The Agent's attachment limits, tool iteration caps, and model quotas still apply.

Endpoint Overview

MethodPathPurpose
GET/api/v1/agents/{agent_id}/publicGet public Agent basic info, authentication optional
POST/api/v1/agents/{agent_id}/chatNon-streaming chat
POST/api/v1/agents/{agent_id}/chat/streamSSE streaming chat
POST/api/v1/agents/{agent_id}/chat/runsQueue a durable run, returns 202 Accepted immediately
GET/api/v1/agents/{agent_id}/chat/runs/{run_id}/stream?after_sequence=NSubscribe to a run's event stream (resumable)
POST/api/v1/agents/{agent_id}/chat/runs/{run_id}/stopCooperatively stop a run
POST/api/v1/agents/{agent_id}/messages/{message_id}/regenerateRegenerate
POST/api/v1/agents/{agent_id}/messages/{message_id}/edit/streamEdit a user message and create a branch
GET/api/v1/agents/{agent_id}/messages/{message_id}/versionsQuery message versions
POST/api/v1/agents/{agent_id}/messages/{message_id}/switch-versionSwitch version
GET/api/v1/agents/{agent_id}/chat/runs/{run_id}Run status (owner-scoped)
GET/api/v1/agents/{agent_id}/chat/runs/{run_id}/events?after_sequence=NReplay buffered events after N
POST/api/v1/agents/{agent_id}/chat/runs/{run_id}/inputsQueue steering/follow-up (delivery: steer / follow_up / auto)
POST/api/v1/agents/{agent_id}/chat/runs/{run_id}/answersSubmit structured answers for a waiting ask_user call
GET/api/v1/conversationsList conversations
GET/api/v1/conversations/{conversation_id}Get a conversation and its messages
PATCH/api/v1/agents/conversations/{conversation_id}Update the conversation title
DELETE/api/v1/conversations/{conversation_id}Delete a conversation

Send Message

Send a message to an agent and receive a response.

Endpoint

POST /api/v1/agents/{agent_id}/chat

Path Parameters

ParameterTypeRequiredDescription
agent_idstringYesAgent UUID

Request Body

{
  "message": "What are your business hours?",
  "conversation_id": "conv-123",
  "file_urls": [
    {
      "asset_id": "asset-456",
      "url": "https://your-domain.com/api/v1/upload/files/general/2026/09/7f3a1c9d2b10_a1b2c3d4.pdf",
      "filename": "7f3a1c9d2b10_a1b2c3d4.pdf",
      "size": 1048576,
      "mime_type": "application/pdf"
    }
  ],
  "variables": {
    "customer_tier": "premium"
  }
}

Submit url, filename, size, and mime_type exactly as returned by the upload response; do not construct /upload/files/{asset_id} from the asset ID. A generated image or video asset_ref is valid only within its conversation or workflow-run scope; protected media URLs require the active JWT or API key.

Request Fields

FieldTypeRequiredDescription
messagestringYesUser message (max 32000 chars)
imagesarrayNoVision images (asset_id or asset_ref, type, url)
filesarrayNoParsed files for upload (deprecated, use file_urls)
file_urlsarrayNoAsset metadata from the upload response (asset_id, filename, url, size, mime_type)
conversation_idstringNoConversation UUID (creates new if not provided)
variablesobjectNoVariable values for the chat input form
history_overridearrayNoOverride conversation history (used for version switching/regeneration)

Request Example

curl -X POST "https://your-domain.com/api/v1/agents/agent-123/chat" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "What are your business hours?",
    "conversation_id": "conv-123"
  }'

Non-Streaming Response

Success (200 OK):

{
  "code": 0,
  "data": {
    "conversation_id": "conv-123",
    "message": {
      "id": "msg-456",
      "conversation_id": "conv-123",
      "role": "assistant",
      "content": "Our business hours are Monday-Friday, 9 AM to 5 PM EST.",
      "tool_calls": [],
      "tool_name": null,
      "model_used": "gpt-4",
      "token_usage": {
        "prompt": 150,
        "completion": 25,
        "total": 175
      },
      "duration_ms": 2300,
      "rag_context": [
        {
          "document_id": "doc-789",
          "document_name": "Business Hours Policy",
          "chunk_id": "chunk-012",
          "content": "Business hours: Monday-Friday, 9 AM to 5 PM EST",
          "score": 0.95,
          "citation_id": "rag_0123456789abcdef"
        }
      ],
      "created_at": "2026-02-11T16:00:00Z",
      "version_number": 1,
      "version_count": 1
    },
    "usage": {
      "prompt": 150,
      "completion": 25,
      "total": 175
    }
  },
  "msg": "success"
}

rag_context is null when retrieval did not run (for example, RAG is off or the Agent has no knowledge-base associations). It is [] when retrieval ran but returned no contexts. Retrieved contexts may include a stable citation_id; cite the exact ID with [[cite:SOURCE_ID]].

Streaming Response

Streaming is a separate endpoint; there is no stream flag on POST /chat.

Endpoint: POST /api/v1/agents/{agent_id}/chat/stream

The request body is the same ChatRequest payload. The response is sent as Server-Sent Events (SSE).

Content-Type: text/event-stream

Event format:

event: message_start
data: {"conversation_id": "conv-123", "message_id": "msg-456"}

event: content_delta
data: {"delta": "Our"}

event: content_delta
data: {"delta": " business"}

event: content_delta
data: {"delta": " hours"}

event: rag_context
data: {"contexts": [{"document_name": "FAQ", "content": "...", "score": 0.95}]}

event: message_end
data: {"usage": {"prompt_tokens": 150, "completion_tokens": 25, "total_tokens": 175}, "timing": {"first_token_ms": 320, "duration_ms": 2300, "tokens_per_second": 10.9}}

See SSE Streaming for details. media_result is a UI event for successfully generated media and is not replayed to the model as text: Clients should preserve received SSE event order. Retrieval, compression, reasoning, tool, media, and text events may interleave; rag_start and rag_context are emitted only when retrieval actually runs.

event: media_result
data: {"kind":"media.image","success":true,"images":[{"image":{"url":"/api/v1/upload/files/generated-images/2026/09/9a8b7c6d5e4f_55667788.png","asset_ref":"a1b2","format":"png"}}]}

Fetch protected generated-image and generated-video URLs with the active JWT or API key. Clients should surface unauthenticated, unauthorized, unavailable, and too-large preview states instead of falling back to an unauthenticated raw URL.

Durable Runs and Control

Chat execution is backed by a durable AgentRun so a browser disconnect never stops the model loop. /chat and /chat/stream wait for the round's result; /chat/runs instead persists the request and enqueues it immediately for a worker, so a dropped connection or page refresh never loses the turn.

Run-scoped events carry an extra envelope on top of the regular SSE events — run_id, sequence, timestamp, round_id, message_id, and type — so clients can resume with after_sequence and render nothing twice.

Additional run events:

EventDescription
run_startRun identity and initial status
run_statusLifecycle transitions (queued → running → …); the run enters waiting when the model calls ask_user, carrying pending_tool_call_id, pending_tool_name, and pending_tool_input
input_acceptedA queued steering/follow-up input was committed
run_endExactly one terminal event per run, with status completed, stopped, failed, or interrupted
MethodPathPurpose
POST/api/v1/agents/{agent_id}/chat/runsPersist and enqueue a run (202 Accepted)
GET/api/v1/agents/{agent_id}/chat/runs/{run_id}/stream?after_sequence=NSSE-subscribe to the run's event stream (resumable)
GET/api/v1/agents/{agent_id}/chat/runs/{run_id}Run status (owner-scoped)
GET/api/v1/agents/{agent_id}/chat/runs/{run_id}/events?after_sequence=NReplay buffered events (non-streaming; handy after packet loss)
POST/api/v1/agents/{agent_id}/chat/runs/{run_id}/inputsQueue steering/follow-up/stop (delivery: steer / follow_up / auto)
POST/api/v1/agents/{agent_id}/chat/runs/{run_id}/answersSubmit structured answers for a waiting ask_user call
POST/api/v1/agents/{agent_id}/chat/runs/{run_id}/stopCooperatively stop a run

Start a Run

POST /api/v1/agents/{agent_id}/chat/runs

The request body is identical to POST /api/v1/agents/{agent_id}/chat (the same ChatRequest); the difference is that it returns 202 Accepted immediately instead of waiting for the assistant reply. The response data is a RunStartOut:

{
  "code": 0,
  "data": {
    "run_id": "8f3b1c9d-2b10-4a1b-9c3d-7f3a1c9d2b10",
    "conversation_id": "conv-123",
    "user_message_id": "msg-456",
    "status": "queued",
    "stream_url": "/agents/agent-123/chat/runs/8f3b1c9d-2b10-4a1b-9c3d-7f3a1c9d2b10/stream"
  },
  "msg": "success"
}
FieldTypeDescription
run_idstringDurable run UUID
conversation_idstringConversation UUID (created when the request omitted conversation_id)
user_message_idstringThe persisted user message UUID
statusstringRun status at enqueue time (see the status values below)
stream_urlstringRelative path of the run's SSE stream, without the /api/v1 prefix

stream_url is relative and only contains /agents/... (/embed/agents/... for embed). You must prepend /api/v1 yourself when building the subscription URL.

Stream a Run

GET /api/v1/agents/{agent_id}/chat/runs/{run_id}/stream?after_sequence=N

Returns text/event-stream: it first replays every buffered event whose sequence is greater than after_sequence (default 0), then follows live events until the run's terminal event. Accepts a JWT bearer token or an API key.

Reconnection semantics: track the highest sequence you have processed and, after a disconnect, request again with that value as after_sequence to continue with no duplicates and no gaps. Negative after_sequence values are clamped to 0. For a non-streaming catch-up, read the same buffered events from GET .../events?after_sequence=N.

Run status values (RunOut.status / RunStartOut.status): queued, running, stopping, completing, waiting, completed, stopped, failed, interrupted. Of these, completed, stopped, failed, and interrupted are terminal.

Stop a Run

POST /api/v1/agents/{agent_id}/chat/runs/{run_id}/stop

No request body. Cooperatively stops a queued, running, or waiting run and returns the updated RunOut; the event stream then receives a terminal run_end with status: "stopped". Stopping a waiting run (blocked on an ask_user answer) works the same way.

RunOut fields: id, agent_id, conversation_id, mode, status, source_message_id, canonical_message_id, active_round_id, error_code, error_message, started_at, finished_at, plus the pending ask_user interaction (pending_tool_call_id, pending_tool_name, pending_tool_input).

Access boundaries: every run route is owner-scoped (agent-scoped for an API key). An unknown run — or one that does not belong to the {agent_id} in the path — returns 404 (code 4000, run_not_found); a run whose conversation belongs to another user returns 403 (code 1004, access_denied); superusers bypass the ownership check.

Submit answers for a waiting ask_user call:

POST /api/v1/agents/{agent_id}/chat/runs/{run_id}/answers

Request body:

{
  "tool_call_id": "call-123",
  "answers": {
    "deployment_target": "cloud"
  },
  "skipped": false
}
FieldTypeRequiredDescription
tool_call_idstringYesPending tool-call ID from the run_status waiting event
answersobjectNoID-keyed answer map; each value is the chosen option or a custom string
skippedbooleanNoExplicitly skip every question (sends { "answers": {}, "skipped": true })

The run must be in waiting status and the tool_call_id must match the pending interaction. On success the run resumes from its paused state.

While a run is active, the composer can queue steering (mid-work) or follow-up (final boundary) instead of being disabled; stop sends a server command and waits for the terminal run_end event.

Regenerate Response

Regenerate an assistant message. This endpoint lives under the agents router and is addressed by agent_id + message_id (no conversation_id in the path).

Endpoint

POST /api/v1/agents/{agent_id}/messages/{message_id}/regenerate

Path Parameters

ParameterTypeRequiredDescription
agent_idstringYesAgent UUID
message_idstringYesMessage UUID to regenerate

Request Body

{
  "variables": {}
}

Request Example

curl -X POST "https://your-domain.com/api/v1/agents/agent-123/messages/msg-002/regenerate" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "variables": {}
  }'

Response

Success (200 OK):

{
  "code": 0,
  "data": {
    "conversation_id": "conv-123",
    "message": {
      "id": "msg-003",
      "conversation_id": "conv-123",
      "role": "assistant",
      "content": "Our business hours are Monday through Friday, from 9:00 AM to 5:00 PM Eastern Standard Time.",
      "token_usage": {
        "total": 180
      },
      "created_at": "2026-02-11T16:15:00Z",
      "version_number": 2,
      "version_count": 2
    },
    "usage": {
      "total": 180
    }
  },
  "msg": "success"
}

Edit User Message and Regenerate (SSE)

Edit a user message, create a new version, and stream the regenerated reply:

POST /api/v1/agents/{agent_id}/messages/{message_id}/edit/stream

Request body:

{"content": "What are your holiday hours?"}

Only user messages can be edited. The response is an authenticated SSE stream (text/event-stream) using the chat stream event format; the final message_end event includes edited_version_number and edited_version_count.

Message Versions

List Versions

List the versions in a message's version group. This route requires the authenticated owner of the conversation.

GET /api/v1/agents/{agent_id}/messages/{message_id}/versions

The 200 OK response is an array of message-version objects containing id, version_number, is_active, content, and created_at.

Switch Version

Activate a different version in the same message group. Versions after the selected message are deactivated because they were based on the previous branch.

POST /api/v1/agents/{agent_id}/messages/{message_id}/switch-version

Request body:

{"version_id": "msg-version-003"}

The 200 OK response returns the selected MessageOut, including version information. The authenticated user must own the conversation.

Conversation Management

List Conversations

GET /api/v1/conversations
ParameterTypeRequiredDefaultDescription
pageintegerNo1Page number
page_sizeintegerNo20Items per page (max: 100)
team_idstringNo-Filter by team
agent_idstringNo-Filter by agent ID
user_idstringNo-Filter by user (admin/dashboard access only)
searchstringNo-Match the conversation title (does not search message content)
untitled_onlybooleanNofalseShow only untitled conversations (takes precedence; search is ignored when both are supplied)

Success (200 OK):

{
  "code": 0,
  "data": {
    "items": [
      {
        "id": "conv-123",
        "agent_id": "agent-456",
        "agent_name": "Customer Support Agent",
        "agent_icon": "🤖",
        "title": "Business Hours Inquiry",
        "message_count": 5,
        "created_at": "2026-02-11T16:00:00Z",
        "updated_at": "2026-02-11T16:05:00Z",
        "user_id": "user-123",
        "user_name": "alice"
      }
    ],
    "total": 42,
    "page": 1,
    "page_size": 20
  },
  "msg": "success"
}

Get Conversation

GET /api/v1/conversations/{conversation_id}

Success (200 OK):

{
  "code": 0,
  "data": {
    "id": "conv-123",
    "agent_id": "agent-456",
    "agent_name": "Customer Support Agent",
    "agent_icon": "🤖",
    "title": "Business Hours Inquiry",
    "variables": {},
    "message_count": 5,
    "token_usage": 875,
    "created_at": "2026-02-11T16:00:00Z",
    "updated_at": "2026-02-11T16:05:00Z",
    "messages": [
      {
        "id": "msg-001",
        "conversation_id": "conv-123",
        "role": "user",
        "content": "What are your business hours?",
        "created_at": "2026-02-11T16:00:00Z",
        "version_number": 1,
        "version_count": 1
      },
      {
        "id": "msg-002",
        "conversation_id": "conv-123",
        "role": "assistant",
        "content": "Our business hours are Monday-Friday, 9 AM to 5 PM EST.",
        "tool_calls": [],
        "rag_context": [
          {
            "document_id": "doc-789",
            "document_name": "Business Hours Policy",
            "score": 0.95
          }
        ],
        "created_at": "2026-02-11T16:00:02Z",
        "version_number": 1,
        "version_count": 1
      }
    ]
  },
  "msg": "success"
}

Paginated GET /conversations/{id}/messages is not implemented / Roadmap. Messages are included inline in GET /api/v1/conversations/{conversation_id}.

Update Conversation

Update conversation details (e.g. rename). This endpoint lives under the agents router.

PATCH /api/v1/agents/conversations/{conversation_id}

Request body:

{
  "title": "Updated Conversation Title"
}

Success (200 OK):

{
  "code": 0,
  "data": {
    "id": "conv-123",
    "title": "Updated Conversation Title",
    "updated_at": "2026-02-11T16:10:00Z"
  },
  "msg": "Conversation updated successfully"
}

Delete Conversation

Permanently delete a conversation (messages cascade). DELETE /api/v1/conversations is mounted on the platform conversation router and requires conversation:delete; superusers/admins can delete any conversation in an accessible team, while an ordinary member can only delete their own (someone else's conversation returns 404).

DELETE /api/v1/conversations/{conversation_id}

Success (200 OK):

{
  "code": 0,
  "data": {
    "id": "conv-123"
  },
  "msg": "Conversation deleted successfully"
}

Batch Delete Conversations

DELETE /api/v1/conversations?ids=conv-123&ids=conv-456

ids is a required repeated query parameter (list[UUID]) that deletes several conversations at once; the permission rules match single deletion, and any inaccessible target rejects the whole batch. When none of the targets exist the call returns 404 (6210).

curl -X DELETE "https://your-domain.com/api/v1/conversations?ids=conv-123&ids=conv-456" \
  -H "Authorization: Bearer YOUR_TOKEN"

Delete a Single Message

The platform conversation router has no per-message delete endpoint; to delete one message use the user-scoped endpoint under the agents router:

DELETE /api/v1/agents/conversations/{conversation_id}/messages/{message_id}

It requires conversation:delete, and the message must belong to the caller's conversation (see Agent Conversations in the Agent API).

Route capability differences: the platform /api/v1/conversations router only exposes GET list, GET detail, DELETE single, and DELETE batch. Renaming (PATCH) and deleting a single message exist only under the agents router (/api/v1/agents/conversations/...). The platform list's search also matches the conversation title only and never message content.

For user-scoped conversation endpoints (e.g. GET /api/v1/agents/conversations/my), see Conversations API.

Error Codes

CodeMessageDescription
6200Agent not foundAgent does not exist
6210Conversation not foundConversation does not exist
6211Message not foundMessage does not exist
4000Run not foundDurable run does not exist or does not belong to the agent in the path
1004Access deniedThe run's conversation belongs to another user
3000Permission deniedInsufficient permissions
1001Validation failedInvalid request data

No per-endpoint rate limits are implemented. There is no rate-limit middleware on these endpoints.

For full error handling and retry strategies, see API Errors and Retries.

Best Practices

Message Handling

✅ Do:

  • Keep messages concise and clear
  • Provide context when needed
  • Use conversation_id to maintain context
  • Handle streaming for better UX
  • Implement retry logic for failures

❌ Don't:

  • Send extremely long messages
  • Create new conversation for each message
  • Ignore error responses
  • Skip error handling
  • Spam the API

Conversation Management

✅ Do:

  • Use descriptive conversation titles
  • Clean up old conversations
  • Monitor conversation count

❌ Don't:

  • Create unnecessary conversations
  • Keep all conversations forever
  • Forget to delete test conversations

Code Examples

Python

import requests

def chat_with_agent(agent_id, message, conversation_id=None):
    """Send message to agent."""
    url = f"https://your-domain.com/api/v1/agents/{agent_id}/chat"
    headers = {
        "Authorization": "Bearer YOUR_TOKEN",
        "Content-Type": "application/json"
    }
    data = {
        "message": message,
        "conversation_id": conversation_id
    }

    response = requests.post(url, headers=headers, json=data)
    result = response.json()

    if result['code'] == 0:
        return result['data']
    else:
        raise Exception(f"Error: {result['msg']}")

# Usage
response = chat_with_agent(
    agent_id="agent-123",
    message="What are your business hours?",
    conversation_id="conv-123"
)

print(f"Response: {response['message']['content']}")
print(f"Conversation ID: {response['conversation_id']}")

JavaScript

async function chatWithAgent(agentId, message, conversationId = null) {
  const url = `https://your-domain.com/api/v1/agents/${agentId}/chat`;

  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer YOUR_TOKEN',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      message: message,
      conversation_id: conversationId,
    }),
  });

  const result = await response.json();

  if (result.code === 0) {
    return result.data;
  } else {
    throw new Error(result.msg);
  }
}

// Usage
const response = await chatWithAgent(
  'agent-123',
  'What are your business hours?',
  'conv-123'
);

console.log('Response:', response.message.content);
console.log('Conversation ID:', response.conversation_id);

How is this guide?

On this page