> [!WARNING]
> **Developer Preview:** Available as part of the [Google Workspace Developer Preview Program](https://developers.google.com/workspace/preview.md.txt), which grants early access to certain features.

This guide explains how to stream messages incrementally in Google Chat using
the `StreamMessage` resource in the Chat API. Streaming lets your
Google Chat app display live progress indicators, intermediate
reasoning or tool-execution steps, and incremental text responses before
finalizing a message.

Streaming is designed for Chat apps powered by generative AI or
long-running backend workflows where generating a complete response takes
several seconds or minutes.

## How message streaming works

A streaming message progresses through an active streaming state before
becoming a standard
[`Message`](https://developers.google.com/workspace/chat/api/reference/rest/v1/spaces.messages.md.txt#Message)
resource in a space:

1. **Start a stream (`spaces.streamMessages.create`)** : Your Chat app creates a `StreamMessage` in a threaded space, referencing the user interaction message it's replying to. The API displays a live progress indicator in the thread and returns a `StreamMessage` resource name and an initial `nextChunkToken`.
2. **Update the stream (`appendStreamMessageContent` or `patch`)** : As your Chat app generates output, it sends sequential updates using the latest `nextChunkToken`:
   - Call `spaces.streamMessages.appendStreamMessageContent` to concatenate new text chunks, append intermediate steps, add inline citations, or update `progressText`.
   - Call `spaces.streamMessages.patch` to replace the streamed content chunks or update `progressText` in place.
3. **Finalize or discard the stream (`commit` or `delete`)** :
   - Call `spaces.streamMessages.commit` to finalize the stream and convert it into a permanent `Message` resource. You can also attach commit-only elements such as cards, accessory widgets, and footer sources when committing.
   - Call `spaces.streamMessages.delete` to discard an active stream without saving a message in the space.

While a stream is active, only the sender of the referenced message
(`referencedMessage`) being streamed on can click **Stop** to cancel
generation. Chat sends a cancellation system command event to
your Chat app so you can stop backend work and either
commit partial output or delete the stream.

## Prerequisites

To stream messages with the Chat API, complete the following
prerequisites:

- A Business or Enterprise [Google Workspace](https://support.google.com/a/answer/6043576) account with access to [Google Chat](https://workspace.google.com/products/chat/).
- Set up your environment:
  - [Create a Google Cloud project](https://developers.google.com/workspace/guides/create-project.md.txt).
  - [Enable and configure the Google Chat API](https://developers.google.com/workspace/chat/configure-chat-api.md.txt) with a name, icon, and description for your Chat app.
  - [Create service account credentials](https://developers.google.com/workspace/chat/authenticate-authorize-chat-app.md.txt#create-service-account) and save them as `credentials.json`. Message streaming requires [app authentication](https://developers.google.com/workspace/chat/authenticate-authorize-chat-app.md.txt) with the `https://www.googleapis.com/auth/chat.bot` authorization scope; user authentication isn't supported.
- Add your Chat app to a **threaded** Chat space (or a 1:1 direct message that already contains at least one message), and obtain the resource name of an accessible message to reply to (`spaces/SPACE_ID/messages/MESSAGE_ID`).
- For Python samples, install the Google API Client Library for Python and
  `google-auth`, and initialize the Chat API service client:

  ```bash
  pip install google-api-python-client google-auth
  ```

  ```python
  from google.oauth2 import service_account
  from googleapiclient.discovery import build

  SCOPES = ["https://www.googleapis.com/auth/chat.bot"]
  creds = service_account.Credentials.from_service_account_file(
      "credentials.json", scopes=SCOPES
  )
  chat = build("chat", "v1", credentials=creds)
  ```

## Start a stream

To start streaming a reply, call `spaces.streamMessages.create` and pass a
`StreamMessage` in the request body:

- `referencedMessage` (Required): Resource name of the message that triggered the response, formatted as `spaces/{space}/messages/{message}`. The referenced message must belong to the same space and be accessible to your Chat app. Chat posts the streamed reply in the same thread as `referencedMessage`.
- `progressText` (Required): Non-empty plain-text status label (up to 250 characters) shown next to the progress indicator, such as `"Thinking..."`.
- `markupSyntax` (Optional): [Text formatting syntax](https://developers.google.com/workspace/chat/api/reference/rest/v1/spaces.messages.md.txt#MarkupSyntax) for the message, such as `MARKUP_SYNTAX_MARKDOWN` or `MARKUP_SYNTAX_CHAT` (defaults to the Chat app's configured syntax if `MARKUP_SYNTAX_UNSPECIFIED`). Set `MARKUP_SYNTAX_MARKDOWN` if you plan to include inline citations.
- `streamContentChunks` (Optional): Initial `text`, `step`, or `citation` chunks to display immediately.

Save both `name` (`spaces/{space}/streamMessages/{stream_message}`) and
`nextChunkToken` from the response for subsequent calls.

### HTTP

```console
POST https://chat.googleapis.com/v1/spaces/SPACE_ID/streamMessages HTTP/1.1
Authorization: Bearer ACCESS_TOKEN
Content-Type: application/json

{
  "referencedMessage": "spaces/SPACE_ID/messages/MESSAGE_ID",
  "progressText": "Thinking...",
  "markupSyntax": "MARKUP_SYNTAX_MARKDOWN"
}
```

### Python

```python
stream = (
    chat.spaces()
    .streamMessages()
    .create(
        parent="spaces/SPACE_ID",
        body={
            "referencedMessage": "spaces/SPACE_ID/messages/MESSAGE_ID",
            "progressText": "Thinking...",
            "markupSyntax": "MARKUP_SYNTAX_MARKDOWN",
        },
    )
    .execute()
)

stream_name = stream["name"]
chunk_token = stream["nextChunkToken"]
```

The method returns a `StreamMessage` resource:

```json
{
  "name": "spaces/SPACE_ID/streamMessages/STREAM_MESSAGE_ID",
  "progressText": "Thinking...",
  "markupSyntax": "MARKUP_SYNTAX_MARKDOWN",
  "nextChunkToken": "INITIAL_CHUNK_TOKEN"
}
```

## Append content chunks

As your Chat app produces intermediate steps or text
tokens, call `spaces.streamMessages.appendStreamMessageContent`:

- Pass the latest `nextChunkToken` in `chunkToken`. Each successful call returns a new `nextChunkToken` that you must pass on your next update or commit request.
- `text` chunks concatenate directly onto previously streamed text. Include spaces or newlines where needed between chunks.
- `step` chunks append to the list of intermediate steps shown on the message.
- `progressText` is optional. If you omit `progressText` or set it to `""`,
  Chat keeps displaying the previously set `progressText`.

### HTTP

```console
POST https://chat.googleapis.com/v1/spaces/SPACE_ID/streamMessages/STREAM_MESSAGE_ID:appendStreamMessageContent HTTP/1.1
Authorization: Bearer ACCESS_TOKEN
Content-Type: application/json

{
  "chunkToken": "INITIAL_CHUNK_TOKEN",
  "progressText": "Searching documentation...",
  "streamContentChunks": [
    {
      "step": {
        "title": "Search knowledge base",
        "description": "Found 3 matching articles."
      }
    },
    {
      "text": "See <chat-citation data-id=\"c1\">guide</chat-citation>:\n"
    },
    {
      "citation": {
        "id": "c1",
        "citedSources": [
          {
            "title": "System Architecture Guide",
            "uri": "https://example.com/docs/arch"
          }
        ]
      }
    }
  ]
}
```

### Python

```python
stream = (
    chat.spaces()
    .streamMessages()
    .appendStreamMessageContent(
        name=stream_name,
        body={
            "chunkToken": chunk_token,
            "progressText": "Searching documentation...",
            "streamContentChunks": [
                {
                    "step": {
                        "title": "Search knowledge base",
                        "description": "Found 3 matching articles.",
                    }
                },
                {
                    "text": (
                        'See <chat-citation data-id="c1">'
                        "guide</chat-citation>:\n"
                    )
                },
                {
                    "citation": {
                        "id": "c1",
                        "citedSources": [
                            {
                                "title": "System Architecture Guide",
                                "uri": "https://example.com/docs/arch",
                            }
                        ],
                    }
                },
            ],
        },
    )
    .execute()
)

chunk_token = stream["nextChunkToken"]
```

## Replace streamed content or progress text

If you need to rewrite previously streamed content---for example, after running a
post-processing or safety filter---or update only `progressText` without
appending chunks, call `spaces.streamMessages.patch`:

- Set `updateMask` to a comma-separated list of fields to update: `progressText`, `streamContentChunks`, or `progressText,streamContentChunks`.
- Include the latest `nextChunkToken` inside the `StreamMessage` request body, and record the new `nextChunkToken` returned in the response.
- When `streamContentChunks` is in `updateMask`, the provided chunks replace previously streamed content instead of appending to it.
- When `progressText` is in `updateMask`, `progressText` must be non-empty
  plain text up to 250 characters.

### HTTP

```console
PATCH https://chat.googleapis.com/v1/spaces/SPACE_ID/streamMessages/STREAM_MESSAGE_ID?updateMask=progressText,streamContentChunks HTTP/1.1
Authorization: Bearer ACCESS_TOKEN
Content-Type: application/json

{
  "nextChunkToken": "LATEST_CHUNK_TOKEN",
  "progressText": "Formatting response...",
  "streamContentChunks": [
    {
      "text": "Updated response text."
    }
  ]
}
```

### Python

```python
stream = (
    chat.spaces()
    .streamMessages()
    .patch(
        name=stream_name,
        updateMask="progressText,streamContentChunks",
        body={
            "nextChunkToken": chunk_token,
            "progressText": "Formatting response...",
            "streamContentChunks": [
                {"text": "Updated response text."}
            ],
        },
    )
    .execute()
)

chunk_token = stream["nextChunkToken"]
```

## Finalize a stream

When generation finishes, call `spaces.streamMessages.commit` with the latest
`chunkToken` to finalize the stream into a permanent
[`Message`](https://developers.google.com/workspace/chat/api/reference/rest/v1/spaces.messages.md.txt#Message)
resource:

- You can optionally include final `streamContentChunks` in the commit request, including commit-only chunk types (`card`, `accessoryWidget`, and `citedSource`).
- If you already streamed non-empty content in earlier calls, you can omit
  `streamContentChunks` when committing. The final committed message can't be
  empty.

### HTTP

```console
POST https://chat.googleapis.com/v1/spaces/SPACE_ID/streamMessages/STREAM_MESSAGE_ID:commit HTTP/1.1
Authorization: Bearer ACCESS_TOKEN
Content-Type: application/json

{
  "chunkToken": "LATEST_CHUNK_TOKEN",
  "streamContentChunks": [
    {
      "text": "All checks passed."
    },
    {
      "citedSource": {
        "title": "Deployment Runbook",
        "uri": "https://example.com/docs/runbook"
      }
    },
    {
      "card": {
        "cardId": "details-card",
        "card": {
          "header": {
            "title": "Build Status"
          },
          "sections": [
            {
              "widgets": [
                {
                  "textParagraph": {
                    "text": "All 42 checks succeeded."
                  }
                }
              ]
            }
          ]
        }
      }
    }
  ]
}
```

### Python

```python
status_card = {
    "cardId": "details-card",
    "card": {
        "header": {"title": "Build Status"},
        "sections": [
            {
                "widgets": [
                    {"textParagraph": {"text": "All 42 checks succeeded."}}
                ]
            }
        ],
    },
}

commit_response = (
    chat.spaces()
    .streamMessages()
    .commit(
        name=stream_name,
        body={
            "chunkToken": chunk_token,
            "streamContentChunks": [
                {"text": "All checks passed."},
                {
                    "citedSource": {
                        "title": "Deployment Runbook",
                        "uri": "https://example.com/docs/runbook",
                    }
                },
                {"card": status_card},
            ],
        },
    )
    .execute()
)

final_message = commit_response["message"]
print(f"Committed message: {final_message['name']}")
```

## Discard an active stream

If an unrecoverable error occurs or a user cancels generation and you don't
want to leave a partial reply in the thread, call
`spaces.streamMessages.delete`:

### HTTP

```console
DELETE https://chat.googleapis.com/v1/spaces/SPACE_ID/streamMessages/STREAM_MESSAGE_ID HTTP/1.1
Authorization: Bearer ACCESS_TOKEN
```

### Python

```python
chat.spaces().streamMessages().delete(name=stream_name).execute()
```

Once a stream is committed into a `Message`, call
[`spaces.messages.delete`](https://developers.google.com/workspace/chat/delete-messages.md.txt) instead if you need
to delete the finalized message.

## Supported content chunk types

Each entry in `streamContentChunks` specifies one field in the
`StreamContentChunk` union. Some chunk types can be sent throughout the stream,
while others are accepted only in `spaces.streamMessages.commit`:

| Chunk field | Supported methods | Final `Message` field | Notes |
|---|---|---|---|
| `text` | `create`, `appendStreamMessageContent`, `patch`, `commit` | `text` | Incremental text. Concatenated on `appendStreamMessageContent`; replaced on `patch`. Final committed text can't exceed 32,000 bytes. |
| `step` | `create`, `appendStreamMessageContent`, `patch`, `commit` | `elements.steps.steps[]` | Intermediate processing step with plain-text `title` (required) and optional `description`. |
| `citation` | `create`, `appendStreamMessageContent`, `patch`, `commit` | `elements.citations[]` | Inline [`Citation`](https://developers.google.com/workspace/chat/format-messages.md.txt#messages-citations) with an alphanumeric `id` (under 63 characters, underscores allowed) and `citedSources`. Requires `markupSyntax: "MARKUP_SYNTAX_MARKDOWN"` and matching `<chat-citation data-id="ID">...</chat-citation>` tags in `text`. Sending a `citation` with an existing `id` overwrites the earlier citation. |
| `card` | `commit` only | `cardsV2[]` | Interactive or informational [`CardWithId`](https://developers.google.com/workspace/chat/api/reference/rest/v1/spaces.messages.md.txt#CardWithId) card. |
| `accessoryWidget` | `commit` only | `accessoryWidgets[]` | [`AccessoryWidget`](https://developers.google.com/workspace/chat/api/reference/rest/v1/spaces.messages.md.txt#AccessoryWidget) buttons displayed below the message, such as feedback buttons. |
| `citedSource` | `commit` only | `elements.citedSources[]` | [`CitedSource`](https://developers.google.com/workspace/chat/format-messages.md.txt#messages-citations) footer link displayed below the message without inline citation tags. |

## Handle user-initiated stream cancellation

While a stream is active, only the sender of the referenced message
(`referencedMessage`) being streamed on can click **Stop** on the streaming
message. When the user stops a stream, Chat sends a system
command interaction event containing `cancelStream.streamMessage` (the
`spaces/{space}/streamMessages/{stream_message}` resource name of the stopped
stream) to your Chat app endpoint.

The structure of the incoming JSON payload depends on whether you build your
Chat app as a Google Workspace add-on or using
Chat API interaction events:

### Google Workspace add-ons

```json
{
  "commonEventObject": {
    "hostApp": "CHAT"
  },
  "chat": {
    "user": {
      "name": "users/USER_ID"
    },
    "space": {
      "name": "spaces/SPACE_ID"
    },
    "systemCommandPayload": {
      "systemCommandMetadata": {
        "cancelStream": {
          "streamMessage": "spaces/SPACE_ID/streamMessages/STREAM_ID"
        }
      }
    }
  }
}
```

### Google Chat API

```json
{
  "type": "SYSTEM_COMMAND",
  "space": {
    "name": "spaces/SPACE_ID"
  },
  "user": {
    "name": "users/USER_ID"
  },
  "systemCommandMetadata": {
    "cancelStream": {
      "streamMessage": "spaces/SPACE_ID/streamMessages/STREAM_ID"
    }
  }
}
```

When your Chat app receives a `cancelStream` event:

1. Signal your background worker to stop generating further tokens or running downstream tools for `streamMessage`.
2. Resolve the active stream using one of the following approaches:
   - **Keep partial output** : Call `spaces.streamMessages.commit` with the latest `chunkToken` (optionally appending a note such as `"\n\n_Generation stopped._"`).
   - **Discard the reply** : Call `spaces.streamMessages.delete` so nothing is saved to the thread.
3. Return an empty HTTP `200 OK` JSON response (`{}`) from your interaction event handler.

## Limitations

Message streaming has the following limitations:

- **Platform availability**: Message streaming is visible only in Chat on the web. Support for Android and iOS is coming soon.
- **Stream timeout** : An active `StreamMessage` times out 10 minutes after creation. Your Chat app must call `spaces.streamMessages.commit` or `spaces.streamMessages.delete` within 10 minutes of starting the stream.

## Related topics

- [Google Chat messages overview](https://developers.google.com/workspace/chat/messages-overview.md.txt)
- [Send a message](https://developers.google.com/workspace/chat/create-messages.md.txt)
- [Format messages and add
  citations](https://developers.google.com/workspace/chat/format-messages#messages-citations)
- [Receive and respond to user
  interactions](https://developers.google.com/workspace/chat/receive-respond-interactions)
- [Authenticate as a Google Chat app](https://developers.google.com/workspace/chat/authenticate-authorize-chat-app.md.txt)