# Send a message

POST /v1/agents/message

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"
    }
  ]
}
```

| Field         | Type    | Required | Notes                                                                                                                                                                                    |
| ------------- | ------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `message`     | string  | yes      | The task, in plain language.                                                                                                                                                             |
| `machineId`   | string  | no       | Omit with one computer, or on a free account. With several computers the API answers `400` and lists the candidates.                                                                     |
| `wait`        | boolean | no       | Default `true`: stream the turn over SSE. `false`: return JSON at once and poll [`/events`](/documentation/api-reference/events).                                                        |
| `model`       | string  | no       | An id from [`/models`](/documentation/api-reference/models) or `auto`. Written to the session before delivery and kept until you pass another. Omit to keep the session's current model. |
| `attachments` | array   | no       | Each entry is the response of a [`/upload`](/documentation/api-reference/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 }
```

| Field       | Notes                                                                                |
| ----------- | ------------------------------------------------------------------------------------ |
| `sessionId` | Pass to `GET /events`.                                                               |
| `machineId` | The computer the message landed on. Useful when you omitted it.                      |
| `queued`    | `true` 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`](/documentation/api-reference/approve) from another connection. The full frame list is on the [Events](/documentation/api-reference/events) page.

## Errors

| Status | Body                                                                                                                                                                    |
| ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Validation 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](/documentation/api-reference/models#free-plan). |
| `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`.
