# Start a streaming message

`POST /chat.startStream`

## Description

Open a streaming message and post its first content. Streaming lets a bot
deliver a message incrementally — recipients see the text fill in live (with
a "typing…" indicator) instead of waiting for the full response. This is
useful for AI agents that produce text token-by-token.

A stream has three steps, each its own request:

1. **[`/chat.startStream`](/docs/api/chat-start-stream)** — open the stream and pick the destination. Returns a `streamId`.
2. **[`/chat.appendStream`](/docs/api/chat-append-stream)** — append chunks of text (call as many times as needed).
3. **[`/chat.stopStream`](/docs/api/chat-stop-stream)** — finalize the stream into a single persisted message.

Pass the `streamId` returned here to every subsequent `appendStream` and
`stopStream`. The sender, destination, and thread are fixed for the lifetime
of the stream.

**Custom sender (optional):** same semantics as
[`/chat.post`](/docs/api/chat-post) — `sender.name` / `sender.imageUrl`
apply a per-message display override to the finalized message, and
`sender.id` authors the stream as a configured bot persona (unknown ids
are accepted and ignored). The typing indicator shown while streaming uses
the override name when given, otherwise the persona's or app's configured
name. See the [Sender Profiles guide](/docs/guides/sender-profiles).

**Access:** Organization and Personal. Organization tokens follow the
same public-group carveout as [`/chat.post`](/docs/api/chat-post): the
bot may stream into a public group in its roam without joining.
Personal tokens can stream only where the owner is a member
(`403` `not_in_chat` for an unjoined public group) and reject the
`sender` field.

**Required scope:** `chat:send_message` or `chat:write`

## Destination

Provide exactly one of `chatId`, `groupId`, or `userIds`. If `text` is empty,
the destination is recorded but message creation is deferred until the first
non-empty `appendStream` or the `stopStream` call.

## Thinking streams

Set `kind` to `thinking` to finalize the message as a thought-bubble; clients
show a "thinking…" indicator instead of "typing…". The default `kind` is `text`.

## Limits

- Up to **10 concurrent streams per API client**.
- Only **one active stream per chat** at a time.
- Accumulated text may not exceed the regular message size limit.

---

**OpenAPI Spec:** [chat-v1.json](https://developer.ro.am/chat-v1.json)

## Authentication

```
Authorization: Bearer YOUR_API_KEY
```

## Request Body

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `chatId` | string | No | Stream into an existing chat by ID (mutually exclusive with groupId/userIds). |
| `groupId` | string | No | Stream into a group chat (mutually exclusive with chatId/userIds). |
| `userIds` | string[] | No | Stream into a DM or Multi-DM with these users (mutually exclusive with chatId/groupId). |
| `kind` | "text" | "thinking" | No | Stream kind. `thinking` finalizes as a thought-bubble message. |
| `threadTimestamp` | integer | No | Optional thread to reply within. |
| `text` | string | No | Optional initial text. May be empty to defer destination resolution until the first append/stop. |
| `sender` | Sender | No |  |

**Sender**:

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `id` | string | No | Code of a configured bot persona to author the message as (trimmed, case-insensitive). Omitted, empty, or `_` posts as the app's root identity. Unconfigured ids are accepted and ignored — supplying an |
| `name` | string | No | Display name override for this message only (max 128 characters). Does not rename the app or persona.  |
| `imageUrl` | string | No | Avatar URL override for this message only. Must be an absolute HTTP(S) URL.  |

## Responses

### 200 - Stream started.

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `streamId` | string | No | Unique ID for this stream. Pass it to appendStream and stopStream. |
| `chatId` | string | No | ID of the destination chat. |
| `threadTimestamp` | integer | No | Thread timestamp if the stream is a thread reply. |


#### Example Response

```json
{
  "streamId": "018f5c8e-7d2a-7c4e-8f9a-1a2b3c4d5e6f",
  "chatId": "757dfe66-37b4-4772-baa5-8c86ec68c176"
}
```

### 400 - Bad request. Common causes:
- No destination, or multiple destinations (exactly one of chatId/groupId/userIds is required)
- Unknown `kind` (must be `text` or `thinking`)
- Invalid thread timestamp, or threading a destination that does not support it
- Another stream is already active in the target chat


### 401 - Presented invalid authentication credentials.

### 403 - The token lacks the required scope (`chat:send_message` or `chat:write`).

### 405 - An unsupported method was requested.

### 413 - Initial text exceeds the maximum allowed message size.

### 429 - Too many concurrent streams (max 10 active per API client).

### 500 - An internal error occurred.

---

*Machine-readable API documentation.*
*Full documentation: https://developer.ro.am/docs/api/chat-start-stream*
