Docs/Send a message
API reference

Send a message

POST /v1/agents/message

View as Markdown

Sends a task to Sai on one computer. Returns at once with wait: false, or streams the turn with wait: true.

Request

json
{
  "message": "Open Notepad, type hello, save it to the desktop as hello.txt",
  "machineId": "m_7f2a",
  "wait": false,
  "attachments": [
    {
      "path": "uploads/report-1790000000000.pdf",
      "name": "report-1790000000000.pdf",
      "mime": "application/pdf",
      "size": 48213,
      "fileId": "f_3kd9"
    }
  ]
}
FieldTypeRequiredNotes
messagestringyesThe task, in plain language.
machineIdstringnoOmit with one computer, or on a free account. With several computers the API answers 400 and lists the candidates.
waitbooleannoDefault true: stream the turn over SSE. false: return JSON at once and poll /events.
modelstringnoAn id from /models or auto. Written to the session before delivery and kept until you pass another. Omit to keep the session's current model.
attachmentsarraynoEach entry is the response of a /upload call. width and height are optional integers for images.

Response with wait: false

202 Accepted:

json
{ "sessionId": "cs_01HZY", "machineId": "m_7f2a", "queued": false }
FieldNotes
sessionIdPass to GET /events.
machineIdThe computer the message landed on. Useful when you omitted it.
queuedtrue when Sai was busy and the message was parked as a steer for the running task.

Refusals on this path are plain JSON, not stream events. A message the agent could not take (for example, the computer is unavailable) returns 409 { "error": "..." }. A message that was handled without starting a task, such as a plain acknowledgement, returns 200 { "error": "..." } with the reply text in error and no sessionId.

Response with wait: true

200 with Content-Type: text/event-stream. Each frame is data: {...} followed by a blank line; the stream ends with data: [DONE].

data: {"type":"start","messageId":"msg_1"}

data: {"type":"data-session","data":{"sessionId":"cs_01HZY"}}

data: {"type":"text-start","id":"t_1"}

data: {"type":"text-delta","id":"t_1","delta":"Opening Notepad."}

data: {"type":"data-approval-request","data":{"approvalId":"ap_7Qw","title":"Command Approval Required","description":"","approvalType":"exec","isLinkOnly":false,"approvalUrl":"https://sai.simular.ai/approval/3fA9kQ2xYz/ap_7Qw?from=api","command":"notepad.exe"}}

data: {"type":"text-end","id":"t_1"}

data: {"type":"finish","finishReason":"stop"}

data: [DONE]

The stream stays open while an approval is pending. Answer it with /approve from another connection. The full frame list is on the Events page.

Errors

StatusBody
400Validation failed, or machineId was omitted and it cannot be resolved (below).
400{ "error": "unknown_model", "message": "Unknown model \"x\". See GET /v1/agents/models." }
403{ "error": "Machine not found or does not belong to your account." }
403{ "error": "model_not_allowed_for_plan", "message": "...", "allowedModels": ["auto"] } on the free plan. See Models.
409{ "error": "..." }: the message was refused (wait: false only).
429{ "error": "Message rate limit exceeded. Maximum 60 messages per hour." }

When machineId is omitted and several computers qualify, the 400 lists them:

json
{
  "error": "Several computers are linked to this account. Pass machineId. See GET /v1/agents/machines.",
  "machines": [
    { "machineId": "m_7f2a", "name": "Work Desktop" },
    { "machineId": "m_9c1e", "name": "Sai cloud computer" }
  ]
}

name is absent for a computer without one. A paid account with no computer at all gets a 400 asking you to create one in the Sai app or the portal.

Gotchas

  • Attachments need a fileId. Upload first; do not build attachment objects by hand.
  • The default is wait: true. A coding agent or a script should send wait: false explicitly.
  • On a free account the first request of the day starts the computer. Expect about two minutes before the first event.

Note

wait: false and optional machineId are available from October 1. Today's staging streams every message and requires machineId.