Skip to content

Messages API

POST /api/v1/agents/:agentId/messages

Send a message to an agent. Returns a JSON response or SSE stream.

Breaking change (PR #69): chat_id is now required. Messages are stored under sessions/api-{chat_id}/ on disk.

Breaking change: session_id now resumes a session and nothing else. An id the gateway has never issued returns 404 SESSION_NOT_FOUND instead of quietly becoming a brand-new session under that name. Clients that minted their own ids must either call POST /sessions first and use the id it returns, or omit session_id and adopt the one in the response.

Request body:

FieldRequiredDescription
messageYesMessage text (max 10,000 chars), or a slash command (e.g. /session, /clear)
chat_idYesCaller identity — used to namespace sessions (e.g. "myapp", "user-123")
session_idNoResume an existing session under this chat_id; omit to start a new one. Must already exist — an unknown id is 404, never a new session
streamNotrue to enable SSE streaming (default false)
timeout_msNoOverride the default response timeout in milliseconds (default 60000)
media_filesNoArray of mediaPath strings returned by the Media Upload endpoint
store_user_messageNoSet to false to skip persisting the user message in session history — only the assistant response is stored. Requires a write or admin key. Useful for proactive/trigger prompts where the user trigger should be invisible.
image_paramsNoComposer-selected image-generation options, surfaced to the agent so it calls the built-in generate_image tool with them. An object with optional string fields model, quality, size, aspect_ratio, image_ref and optional positive number n. Empty/whitespace strings are ignored; a non-object (or n < 1) returns 400. The latest sent value is persisted to session meta as imageConfig (see the sessions list endpoint) so a web client can restore the selection on reload.
video_paramsNoComposer-selected video-generation options, surfaced to the agent so it calls the built-in generate_video tool with them (model/duration/aspect made authoritative — no invented cap, no scene split). An object with optional string fields model, resolution, aspect_ratio, image_ref (source frame for image-to-video) and optional positive integer duration. Empty/whitespace strings are ignored; a non-object (or duration < 1) returns 400. The latest sent value is persisted to session meta as videoConfig (see the sessions list endpoint) so a web client can restore the selection on reload.

Slash command dispatch

If message starts with /, the endpoint executes the command instead of forwarding to Claude:

CommandDescription
/sessionReturn current session info (name, message count, context %)
/clearClear the session history
/compactSummarise old history and keep only recent messages
/stopInterrupt the in-flight turn
/restartGracefully restart the session
/modelReturn the current model for this agent

Command response:

json
{
  "command": "/session",
  "session_id": "da19d84a-6a36-4f57-b419-d322d82c4db8",
  "result": {
    "name": "My Project Discussion",
    "messageCount": 42,
    "contextPercent": 18
  }
}

New session:

bash
curl -X POST \
  -H "X-Api-Key: my-secret-key-123" \
  -H "Content-Type: application/json" \
  -d '{"message": "Hello! What can you help me with?", "chat_id": "myapp"}' \
  http://localhost:10850/api/v1/agents/alfred/messages | jq
json
{
  "request_id": "550e8400-e29b-41d4-a716-446655440000",
  "agent_id": "alfred",
  "response": "Hello! I'm Alfred, your personal assistant. I can help you with...",
  "session_id": "da19d84a-6a36-4f57-b419-d322d82c4db8",
  "duration_ms": 2341,
  "attachments": [
    { "type": "image", "url": "/v1/agents/alfred/media/api-sess-id/browser_shot_default_1234567890.jpg" }
  ]
}

attachments is only present when the agent captured images during the turn (e.g. via browser_screenshot). Each entry has type: "image" and a url that can be fetched via GET /api/v1/agents/:agentId/media/*.

Continue a session:

bash
curl -X POST \
  -H "X-Api-Key: my-secret-key-123" \
  -H "Content-Type: application/json" \
  -d '{"message": "What did I just ask you?", "chat_id": "myapp", "session_id": "da19d84a-6a36-4f57-b419-d322d82c4db8"}' \
  http://localhost:10850/api/v1/agents/alfred/messages | jq

Error responses:

StatusWhen
400Empty or too-long message, or missing chat_id
401Missing API key
403Invalid key or key has no access to that agent
404Agent ID not found, or session_id names no session in this chat_id (code: "SESSION_NOT_FOUND")
409Session is busy processing another request
504Agent did not respond within timeout (default 60s) — sync mode only
500Internal error
  • session_id is optional — omit for a stateless one-shot call
  • Sessions idle-timeout after idleTimeoutMinutes (default 30 min); history is restored automatically on next message
  • Error 409 = session is currently processing a request — wait and retry
  • After a soft timeout, the same session_id keeps returning 409 until the hard cap (a further 10 min) — the subprocess is still finishing that turn, so a retry would interleave. Omit session_id to start a fresh session immediately, or stay with this one and read the turn out via GET …/sessions/:sessionId/stream, which is never a conflict.
  • The soft timeout only ends the request in sync mode (504). In streaming mode it is a non-terminal timeout event — the turn is still running and the stream stays open.

Claude Code inside.