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_idis now required. Messages are stored undersessions/api-{chat_id}/on disk.Breaking change:
session_idnow resumes a session and nothing else. An id the gateway has never issued returns404 SESSION_NOT_FOUNDinstead of quietly becoming a brand-new session under that name. Clients that minted their own ids must either callPOST /sessionsfirst and use the id it returns, or omitsession_idand adopt the one in the response.
Request body:
| Field | Required | Description |
|---|---|---|
message | Yes | Message text (max 10,000 chars), or a slash command (e.g. /session, /clear) |
chat_id | Yes | Caller identity — used to namespace sessions (e.g. "myapp", "user-123") |
session_id | No | Resume 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 |
stream | No | true to enable SSE streaming (default false) |
timeout_ms | No | Override the default response timeout in milliseconds (default 60000) |
media_files | No | Array of mediaPath strings returned by the Media Upload endpoint |
store_user_message | No | Set 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_params | No | Composer-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_params | No | Composer-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:
| Command | Description |
|---|---|
/session | Return current session info (name, message count, context %) |
/clear | Clear the session history |
/compact | Summarise old history and keep only recent messages |
/stop | Interrupt the in-flight turn |
/restart | Gracefully restart the session |
/model | Return the current model for this agent |
Command response:
{
"command": "/session",
"session_id": "da19d84a-6a36-4f57-b419-d322d82c4db8",
"result": {
"name": "My Project Discussion",
"messageCount": 42,
"contextPercent": 18
}
}New session:
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{
"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" }
]
}
attachmentsis only present when the agent captured images during the turn (e.g. viabrowser_screenshot). Each entry hastype: "image"and aurlthat can be fetched viaGET /api/v1/agents/:agentId/media/*.
Continue a session:
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 | jqError responses:
| Status | When |
|---|---|
| 400 | Empty or too-long message, or missing chat_id |
| 401 | Missing API key |
| 403 | Invalid key or key has no access to that agent |
| 404 | Agent ID not found, or session_id names no session in this chat_id (code: "SESSION_NOT_FOUND") |
| 409 | Session is busy processing another request |
| 504 | Agent did not respond within timeout (default 60s) — sync mode only |
| 500 | Internal error |
session_idis 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_idkeeps returning409until the hard cap (a further 10 min) — the subprocess is still finishing that turn, so a retry would interleave. Omitsession_idto start a fresh session immediately, or stay with this one and read the turn out viaGET …/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-terminaltimeoutevent — the turn is still running and the stream stays open.