# Search chat messages

`POST /chat.search`

## Description

Full-text search over the caller's accessible messages. Returns
full-fidelity messages — text, items, voice, polls, blocks, and
mentions — hydrated through the same pipeline as
[`/chat.history`](/docs/api/chat-history).

All fields are optional. With no parameters, the most recent messages
across all chat types (DMs, multi-DMs, group chats) are returned in
reverse chronological order.

**Sort:** When omitted and `query` is empty, results are sorted
chronologically (newest first), since relevance scoring is meaningless
without search terms. Pass `sort: recent` to force chronological order
even with a text query.

**Date filters:** `before` and `after` accept `YYYY-MM-DD`. Dates are
interpreted in the caller's timezone (see
[Timezone handling](/docs/guides/migration-v0-to-v1#timezone-handling)).

**Access:** Organization and Personal.

- **Personal tokens** search chats the owner can read, including public
  groups in their roam they have not joined.
- **Organization tokens** search chats the bot is a **member** of,
  plus unjoined **public** groups in the bot's roam (Slack
  `search:read.public`). Private groups the bot is not in are excluded.
  [`/chat.history`](/docs/api/chat-history) stays membership-only.

**Required scope:** `chat:history`

Every returned sender includes `userId` plus `userType`. The ID resolves
through [`user.info`](/docs/api/user-info) with the same credentials.

---

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

## Authentication

```
Authorization: Bearer YOUR_API_KEY
```

## Request Body

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `query` | string | No | Free-text search query. Empty matches all messages. |
| `in` | string[] | No | Group names to search within. |
| `from` | string[] | No | Filter to messages sent by these email addresses. |
| `with` | string[] | No | Filter to chats including these email addresses. |
| `before` | string | No | Only include messages before this date (`YYYY-MM-DD`, caller's timezone). |
| `after` | string | No | Only include messages on or after this date (`YYYY-MM-DD`, caller's timezone). |
| `has` | "mention" | "item"[] | No | Restrict to messages that contain a mention or an item. |
| `chatTypes` | "channel" | "teamRoam" | "address"[] | No | Restrict to specific chat types. Defaults to all types (channels, all-hands "team Roam" groups, and DMs).  |
| `excludeChatIds` | string[] | No | Chat IDs to exclude from results. |
| `excludeUserIds` | string[] | No | Sender user IDs to exclude from results. |
| `sort` | "relevant" | "recent" | No | `relevant` (default) ranks by relevance to `query`; `recent` sorts newest first. With an empty `query`, results are sorted chronologically regardless.  |
| `expand` | string | No | Comma-separated fields to expand. Supported: `addresses` — include an `addresses` map resolving the sender (`userId`) and mentioned IDs on each message to their display info.  |
| `limit` | integer | No | Number of messages per page (max 200). |
| `cursor` | string | No | Opaque pagination cursor from a previous response's `nextCursor`. |

## Responses

### 200 - Search results.

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `messages` | ChatMessage[] | Yes |  |
| `nextCursor` | string | No | Cursor to fetch the next page. Absent when there are no more results. |
| `addresses` | object | No | Resolved address objects keyed by ID, for the senders and mentioned entities in this response. Included only when `expand=addresses` is requested.  |

**ChatMessage** (array item):

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `type` | "message" | No | Message type identifier |
| `userId` | string | Yes | Principal ID of the message sender. Resolve with `user.info`. |
| `userType` | "user" | "bot" | Yes | Principal type of `userId`; always equals `user.info.type` for the same credentials. Use it to prevent bot loops without another lookup. |
| `chatId` | string | No | ID of the chat the message belongs to |
| `timestamp` | integer | Yes | Message key as Unix microseconds |
| `threadTimestamp` | integer | No | Unix microseconds timestamp of the parent message (for thread replies) |
| `replyTimestamp` | integer | No | Timestamp of the message this one quotes — a DM or channel-thread quoted reply, set via the `replyTimestamp` request field on chat.post. Omitted otherwise. |
| `ephemeral` | boolean | No | Whether the message is ephemeral. Always omitted (false) in chat.history, chat.search, and chat.link.resolve responses — ephemeral messages (posted via chat.postEphemeral) are never persisted, so they |
| `text` | string | No | Text of the message, formatted as github-flavored markdown. Mention tokens use Slack's syntax: `<@ID>` is a principal (user or bot — resolve with `user.info`), `<!subteam^ID>` is a group or channel (r |
| `contentType` | "text" | "voice" | "block" | "poll" | Yes | Type of message content: `text` (markdown body, optionally with items), `voice` (voice note), `block` (rich block layout), or `poll`. |
| `items` | ChatItem[] | No | Items attached to this message |
| `poll` | object | No | Poll content, present when contentType is `poll`. |
| `voice` | object | No | Voice-note content, present when contentType is `voice`. |
| `blocks` | object[] | No | Rich block layout, present when contentType is `block`. |
| `color` | string | No | Accent color for a block message, present when contentType is `block`. Omitted otherwise. |
| `replyCount` | integer | No | Number of replies in this message's thread. Omitted when zero. |
| `sender` | object | No | Per-message sender display override supplied at send time via the request's `sender` field. Present only when the stored message carries one. Additive: `userId` remains the authoring identity — render |
| `mentions` | string[] | No | Flat, order-preserving, de-duplicated list of everything referenced by mention tokens in `text`: bare address UUIDs (from both `<@ID>` principal and `<!subteam^ID>` group tokens), plus the literal `al |

**ChatItem** (array item):

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `id` | string | Yes | UUID identifying this item |
| `type` | "photo" | "blob" | Yes | Type of item:  - **photo**: Images with inline preview and thumbnail   - image/jpeg, image/png, image/gif, image/webp  - **blob**: Any other file type (download only, no preview)   - application/octet |
| `mime` | string | No | MIME type of the file (e.g., "application/octet-stream"). May be omitted for photo items where the type is inferred from the image format.  |
| `created` | string | Yes | Timestamp when the item was created |
| `name` | string | Yes | Name of the item (typically the filename). |
| `url` | string | Yes | URL for the uploaded item. |
| `thumbnail` | string | No | URL for a thumbnail of the uploaded item (photo type only). This may be equal to the item's main URL if it is suitable to use as a thumbnail.  |
| `size` | integer | No | Size of the item in bytes |
| `width` | integer | No | Width in pixels (images only) |
| `height` | integer | No | Height in pixels (images only) |

**poll**:

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `question` | string | No | The poll question. |
| `options` | object[] | No | The poll answer options. |
| `allowMultipleAnswers` | boolean | No | Whether voters can select multiple options. |
| `closesAt` | string | No | When the poll closes (RFC-3339). Omitted if no close time is set. |

**voice**:

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `audioUrl` | string | Yes | URL of the voice-note audio (m4a). |
| `duration` | integer | Yes | Duration of the voice note in milliseconds. |
| `transcript` | string | No | Text transcript of the voice note. Omitted if not yet available. |

**sender**:

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `name` | string | No | Display name override for this message. |
| `imageUrl` | string | No | Avatar URL override for this message. |


#### Example Response

```json
{
  "messages": [
    {
      "type": "message",
      "contentType": "text",
      "userId": "ad1e9cc0-0ffd-47e5-895c-2630a73327b4",
      "userType": "user",
      "chatId": "295155ae-7df5-4ed5-9ebc-89a170559c81",
      "timestamp": 1765602474760032,
      "text": "Let's review the Q2 roadmap on Friday."
    }
  ],
  "nextCursor": "YzE6MTc2NTYwMjQ3NDc2MDAzMjoyOTUxNTVhZS03ZGY1"
}
```

### 400 - Bad request. Common causes:
- Invalid `chatTypes` value
- Invalid `excludeChatIds` or `excludeUserIds` (must be UUIDs)
- Invalid `before`/`after` date format
- Invalid `from`/`with` email addresses
- Invalid `sort` value


### 401 - Presented invalid authentication credentials.

### 403 - Forbidden. The token lacks the required scope.


### 405 - An unsupported method was requested.

### 500 - An internal error occurred, including a stored sender that cannot resolve as a visible principal. The endpoint does not return a partial page.

---

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