---
title: "Slack app manifest"
description: "Every field of the Slack app manifest Chickpea installs, with the scopes, events, and URLs it declares and what each one is used for."
---

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

# Slack app manifest

The **Slack app manifest** is the single JSON document that declares the Slack app a Chickpea deployment installs: its identity, its permission scopes, the events Slack sends it, and the URLs Slack calls. Where a [channel grant](/slack/handles-and-channels) decides which Agent may answer inside Slack, the manifest decides what the Slack app may ask Slack for at all. This page lists every field of that manifest, the 20 bot scopes and 3 user scopes with the code path behind each, the 11 bot events, and the settings that are deliberately off.

Source of truth: `slack-app-manifest.json` at the repository root. It is generated by `canonicalSlackAppManifestJson()` in `src/slack/app-manifest.ts`, and `tests/slack-app-manifest.test.ts` asserts the committed file is byte-identical to that builder's output. Editing the JSON without editing the builder fails the test.

## Quick reference

| Field group | Value |
|---|---|
| App name and bot display name | `Chickpea`, both |
| Description | `A self-hosted, model-agnostic AI agent for Slack.` |
| Background color | `#bd5732` |
| Bot token scopes | 20 |
| User token scopes | 3 (`openid`, `profile`, `email`) |
| Redirect URLs | 2, under `/auth/slack/` |
| Events request URL | `/channels/slack/events` |
| Bot events | 11 |
| Interactivity request URL | `/channels/slack/interactions` |
| Socket Mode, org deploy, token rotation, Slack MCP | all `false` |

## Where the manifest applies

The manifest describes the customer-owned app created in the **Use your own Slack app** lane. Admin builds a Slack deep link from it: the committed placeholder origin `https://chickpea.example` is replaced with the deployment's own origin before the link is handed to Slack, so no URL is ever copied by hand. Over a plain HTTP origin, which Slack rejects anyway, only the Events request URL is substituted.

The scope list is wider than one lane. `REQUIRED_SLACK_BOT_SCOPES` in `src/slack/scopes.ts` is read directly from the committed manifest and is the authority for every bot installation, so the same 20 scopes are requested and verified whichever lane a deployment uses. The **Add to Slack** lane installs Chickpea's shared app instead of this manifest, and the deployment reaches Slack through a fixed allowlist of 33 gateway operations rather than with a token of its own.

## Display information

| Field | Value |
|---|---|
| `name` | `Chickpea`, at most 35 characters |
| `description` | `A self-hosted, model-agnostic AI agent for Slack.` |
| `background_color` | `#bd5732` |

The builder accepts a different app name and bot display name for a deployment that needs one. The description and background color are fixed.

## App features

| Feature | Value | What it enables |
|---|---|---|
| `app_home.home_tab_enabled` | `true` | The App Home directory that lists an operator's available Agents |
| `app_home.messages_tab_enabled` | `true` | Direct messages to an Agent from the Messages tab |
| `app_home.messages_tab_read_only_enabled` | `false` | The Messages tab accepts input |
| `bot_user.display_name` | `Chickpea`, at most 80 characters | The installed bot's name |
| `bot_user.always_online` | `true` | The bot never shows as away |
| `agent_view.agent_description` | Same string as the app description | Slack's agent view header |
| `agent_view.suggested_prompts` | 3 entries | Starter prompts: `Summarize supplied text`, `Investigate a question`, `Plan a task` |

## Bot token scopes

Twenty scopes, in manifest order. Each line names the Slack API method or event that requires it.

| Scope | Why Chickpea needs it |
|---|---|
| `app_mentions:read` | Receives the `app_mention` event, the mention that addresses an Agent handle in a channel. |
| `assistant:write` | Drives the Slack agent view through `assistant.threads.setStatus`, `setTitle`, and `setSuggestedPrompts`. |
| `channels:history` | Reads public channel context with `conversations.history` and `conversations.replies`. |
| `channels:join` | Calls `conversations.join` when an Agent is published to a public channel the app has not joined. |
| `channels:read` | Resolves public channel facts with `conversations.info`, `conversations.list`, and `conversations.members`. |
| `chat:write` | Posts and edits replies: `chat.postMessage`, `chat.postEphemeral`, `chat.update`, `chat.delete`, and the three `chat.*Stream` calls. |
| `chat:write.customize` | Posts each reply under that Agent's own `username` and `icon_url` instead of one shared bot identity. |
| `files:read` | Reads an attachment's bytes by file id before normalization and analysis. |
| `files:write` | Uploads files an Agent produces, with `files.uploadV2`. |
| `groups:history` | The same context reads inside private channels, where `message.groups` arrives. |
| `groups:read` | The same channel facts for private channels. |
| `im:history` | Reads direct message context for a DM turn. |
| `im:write` | Opens a DM with `conversations.open` to deliver a schedule result or a management receipt. |
| `mpim:read` | Identifies group DMs, so a turn is classified `mpim` rather than as a channel. |
| `reactions:read` | Receives `reaction_added` and reads the reacted message with `reactions.get`. |
| `reactions:write` | Adds and clears reactions with `reactions.add` and `reactions.remove`. |
| `users:read` | Reads `users.info` and `users.list` for display names and account class: deleted, bot, guest, or foreign. |
| `usergroups:read` | Lists user groups with `usergroups.list` to find the group behind an Agent handle. |
| `usergroups:write` | Creates, renames, disables, and re-enables the user group that is an Agent handle, with `usergroups.create`, `update`, `disable`, and `enable`. |
| `users:read.email` | Adds `profile.email` to `users.info`. The address is stored as a member contact detail, never as an identity key. |

## User token scopes

Three scopes, requested only during Slack OIDC sign-in and never used to call the Slack Web API. Slack OIDC is the only human sign-in. No passwords.

| Scope | Why Chickpea needs it |
|---|---|
| `openid` | Issues the ID token that proves a Slack workspace and user identity to Admin. |
| `profile` | Supplies the display name on the `openid.connect.userInfo` response. |
| `email` | Supplies the email claim on the same response. |

## Redirect URLs

Two, both on the deployment's own origin.

| Path | Purpose |
|---|---|
| `/auth/slack/install/callback` | Completes the bot installation OAuth exchange. |
| `/auth/slack/oidc/callback` | Completes a Slack OIDC sign-in. |

## Event subscriptions

The request URL is `/channels/slack/events` on the deployment origin. Eleven bot events are subscribed.

| Event | What it does |
|---|---|
| `app_context_changed` | Accepted and ignored. No handler runs. |
| `app_home_opened` | Publishes the App Home Agent directory to the user who opened it. |
| `app_mention` | The primary trigger. Routes the turn to the Agent whose handle was mentioned. |
| `member_joined_channel` | Greets the channel when the app itself joins a channel that has an active grant. On by default, disabled with `SLACK_TAG_WELCOME_ON_JOIN=false`. |
| `message.channels` | Public channel messages. In every channel, a top-level message that mentions no one is dropped before any model is involved. |
| `message.groups` | The same for private channels. |
| `message.im` | Direct messages to the app. |
| `reaction_added` | May start a turn from a reaction, using the reacted message as the thread root. |
| `app_uninstalled` | Marks the stored workspace installation `revoked`. |
| `tokens_revoked` | Marks the stored workspace installation `revoked`. |
| `user_change` | Suspends a member's stored authority when Slack reports that account deleted or converted to a bot. |

## Interactivity

`interactivity.is_enabled` is `true` and its request URL is `/channels/slack/interactions`. The only interaction the app handles is the App Home button that starts a private thread with a chosen Agent, action id `chickpea.agent.start`.

## Settings that are off

| Setting | Value | Consequence |
|---|---|---|
| `socket_mode_enabled` | `false` | Slack delivers events over HTTPS to the request URL. No app-level token exists. |
| `org_deploy_enabled` | `false` | The app installs into one workspace, not an Enterprise Grid org. |
| `token_rotation_enabled` | `false` | The bot token does not expire and is not refreshed. |
| `is_mcp_enabled` | `false` | Slack's own MCP surface is not exposed by this app. |

One deployment currently serves one Slack workspace.

## What is compared when a manifest is validated

`validateSlackAppManifest` compares a reduced contract rather than the whole document, so cosmetic drift in an exported manifest is tolerated and a capability change is not. The compared fields are the app name, the bot display name, the redirect URLs, the sorted user scopes, the sorted bot scopes, the events request URL, the sorted bot events, whether the App Home tab is enabled, whether interactivity is enabled, the interactivity request URL, and `pkce_enabled`, which Slack materializes as `false` when it is omitted. A mismatch raises `Slack app manifest does not match the expected callbacks, scopes, or events.`

Recovery has a narrower check. `validateSlackAppManifestUrlRepair` overwrites the two redirect URLs, the events request URL, and the interactivity request URL with the expected values before comparing, so a deployment that moved to a new origin may repair its own URLs and nothing else.

## Scope enforcement

The granted scopes on a live token are checked against the manifest at three points.

- **On install.** An OAuth exchange missing a manifest scope fails with `missing_scopes`. An exchange that returns a scope absent from the manifest fails with `unexpected_scopes`.
- **On verification.** A missing scope raises `Reinstall this Slack app to grant the required permissions:` followed by the missing scope names. When the response carries no scope header at all, verification raises `Slack did not return the installed permission scopes`.
- **On health.** An installation with a missing scope reports `needs_reauthorization` with the action label `Reinstall Chickpea in Slack`. When Slack omits the scope header, the status is `unverifiable` rather than healthy.

Source: https://docs.chickpea.co/reference/slack-app-manifest/index.mdx
