> ## Documentation Index
> Fetch the complete documentation index at: https://docs.twinbay.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Slack

> Channels, messages, threads and reactions, with Slack's ok:false envelope

A Slack workspace over the Web API a bot integration actually uses: conversations public and private, messages with server-minted `ts` values and edit stamps, one-level threads with the rollups they leave on their parent, reactions, members, DMs and scheduled messages that post when the sandbox is advanced. The conformance suite pins Slack's own `slack_sdk` and drives the container through the sequence an integration comes up in.

**Slug:** `slack`

## Application failures are HTTP 200

This is the defining behaviour of the provider, and the one thing a caller cannot work around. Slack does not use status codes for application errors: a missing token, a bad token, a missing scope, an unknown channel and a method it does not have all come back `HTTP 200` with `{"ok": false, "error": "…"}`. `slack_sdk` raises `SlackApiError` off that body, so a twin answering `401` would make the caller's `except` land somewhere else than it does in production. `429` is the single exception.

## One container is one workspace

The team, the bot user and the bot id are constants of the image — `T024TW1NBAY`, `U024TW1NB0T`, `B024TW1NB0T` — which is what makes `auth.test` assertable. They are three different things and callers key on each of them for different reasons.

## What it models

| Area               | Modelled                                                                                                                                                                                                                                                                                                                                                                                                                       |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| The workspace      | A furnished team of six: Slackbot, three people (`ada`, `bruno`, `chidi`) and **two** bot users — the app's own `twinbay` and a seeded `deploybot`, both of which `users.list` returns with `is_bot: true` — plus `#general` nobody may archive, a `#releases` with a live thread and a reaction, and an `#announcements` the bot was deliberately never invited to                                                            |
| Authentication     | A bot token (`xoxb-`) and nothing else — user, app-level and refresh tokens are refused rather than accepted — with `not_authed`, `invalid_auth`, `token_revoked`, `account_inactive` and `missing_scope` carrying both `needed` and `provided`, every one of them at `HTTP 200`                                                                                                                                               |
| Rate limits        | Per-method, per-workspace tiers with Slack's own burst tolerance, a real `429` with `Retry-After` in whole seconds, and no `X-RateLimit-*` headers, because Slack sends none. `chat.postMessage` has its own budget per channel                                                                                                                                                                                                |
| Conversations      | `create` with all four name refusals kept distinct, `list` with its `types` filter, `info` with `include_num_members`, `join`, `invite` reporting each user that failed, `members`, `setTopic`, `setPurpose`, `archive`, and `open` for DMs                                                                                                                                                                                    |
| Messages           | `chat.postMessage` sent as JSON the way the SDK sends it, `chat.update` keeping the `ts` and stamping `edited`, `chat.delete` answering `message_not_found` the second time, and `conversations.history` newest-first without the thread replies                                                                                                                                                                               |
| Threads            | One level only: a `thread_ts` naming a reply attaches to its root, and one naming nothing posts to the channel. `reply_count`, `reply_users`, `reply_users_count` and `latest_reply` are derived on every read, and `reply_broadcast` produces the `thread_broadcast` subtype                                                                                                                                                  |
| Reactions          | `add`, `remove` and `get`, one record per person per emoji, projected onto a message grouped by first use, with `already_reacted`, `no_reaction` and `invalid_name`                                                                                                                                                                                                                                                            |
| Members            | `users.list` with Slackbot first, `users.info`, `users.lookupByEmail` with its own plural error string, bots against humans, and deactivated members whose messages stay where they are                                                                                                                                                                                                                                        |
| Scheduled messages | `chat.scheduleMessage` with `time_in_past` and Slack's 120-day ceiling, the pending list, cancellation, and the `advance` step that turns one into a real message with a fresh `ts`                                                                                                                                                                                                                                            |
| Paging             | Cursors in both of the shapes Slack sends: `next_cursor: ""` on `users.list`, `conversations.list`, `conversations.members` and `chat.scheduledMessages.list`, and **no `response_metadata` at all** on the last page of `conversations.history` *and* `conversations.replies`, which say they are done with `has_more: false` instead. A total order in every case, and `invalid_cursor` for a cursor from another collection |
| System messages    | Joining, setting a topic or purpose, and archiving all leave real messages in history, because a caller iterating history sees them                                                                                                                                                                                                                                                                                            |

Advancing the sandbox does one thing, because one thing in Slack ages: a scheduled message whose `post_at` has passed becomes a real message. Messages do not expire and channels do not close.

## Membership has two failure modes

A **public** channel the app is not in is `not_in_channel` on a read — and postable anyway when `chat:write.public` is granted, which is what a live workspace does. A **private** channel the app is not in is `channel_not_found` on every method and absent from the list: invisible rather than forbidden.

## Scenarios

`busy-workspace`, `rate-limited`, `narrow-scopes`, `uninstalled-app`.

## Not modelled

Socket Mode, the Events API and webhooks, incoming webhooks, slash commands and interactivity, the OAuth v2 install flow, Block Kit rendering and modals, file uploads, Enterprise Grid and `admin.*`, Canvases, huddles, workflows, multi-person DMs, user tokens beyond refusing them, and `search.messages`, which needs a user token.

Slack's Web API carries no version anywhere, so the twin's behaviour is pinned to the SDK version and to the date it was read off a live workspace. Two things it deliberately does not reproduce: `msg_too_long`, which never fired — Slack splits long text instead of refusing it — and `unknown_method` for a method-shaped name it has not modelled, where it answers a string Slack never sends rather than falsely claim Slack retired the method. The twin's own README explains both.
