---
name: slack-oauth-app
description: Creates a Slack app and obtains OAuth2 client ID and client secret — with the right redirect URLs, bot vs user token scopes, public distribution, and a safe credential handoff. Use when asked to get Slack OAuth credentials, create or configure a Slack app, set up Slack scopes, activate Slack public distribution, rotate a Slack client secret or signing secret, or fix a Slack install error like `bad_redirect_uri` or `invalid_scope`. For any other vendor's developer portal, use that vendor's skill instead.
---

# Slack OAuth2 App Registration

Get a working Slack OAuth2 client — app, redirect URLs, scopes, client ID and secret, public distribution — for a
platform that connects Slack workspaces on behalf of many customers.

Slack has no separate developer account: any Slack account can create an app at `api.slack.com/apps`. The work is in
three decisions that are easy to get wrong and expensive to discover later: **bot token vs user token** (they are
different scope lists and different fields in the token response), **redirect URL matching** (Slack compares the
`redirect_uri` you send against what is registered, and fails the exchange if the two calls disagree), and
**token rotation** (turn it on and every token expires in 12 hours whether or not your client refreshes).

## Inputs to collect before you start

Ask in one batch. Never invent.

| Input | Notes |
| --- | --- |
| **App name** and short description | Shown on the install consent screen |
| **Slack account + a workspace you can install into** | A free workspace you control, for testing (§2) |
| **Redirect URLs** | Every callback host your platform serves (§4) |
| **Bot token, user token, or both** | The decisive question — see §5 |
| **Scope set** | The exact scopes your connector calls for (§5) |
| **Distribution** | Single workspace, public distribution, or a Marketplace listing (§7) |
| **Icon, privacy policy URL, support URL** | Needed for public distribution and a listing |

## Quick Start

1. Confirm a **new app** is needed — existing installs are bound to the current client ID (§1).
2. Sign in to Slack and pick or create a **test workspace** you can install into (§2).
3. Create the app at `api.slack.com/apps` — from an app manifest if you want it reproducible (§3).
4. Add **every** redirect URL under OAuth & Permissions (§4).
5. Set **User Token Scopes**, **Bot Token Scopes**, or both — matching what your authorize URL actually sends (§5).
6. Copy client ID, client secret and signing secret from Basic Information (§6).
7. Activate **public distribution** if customers outside your workspace will install it (§7).
8. Verify with a real authorize → `oauth.v2.access` round trip, checking the token field you actually use (§8).
9. Hand the credentials over — never commit them (§9).

## Platform state (verified 2026-09-20 — re-verify before trusting)

- **The docs moved.** `api.slack.com/*` documentation URLs now 302 to **`docs.slack.dev`**. The app dashboard itself
  is still at `api.slack.com/apps`. Older write-ups linking `api.slack.com/authentication/...` are not wrong, just
  redirected.
- **Two ways to create an app**: the dashboard (from scratch or from an app manifest) and the **Slack CLI**
  (`slack create`), which is manifest-first and what Slack's own quickstart now leads with. Either produces the same
  kind of app; the manifest is the reproducible one.
- **The App Directory is the Slack Marketplace.** A Marketplace listing requires the app to be installed on **5 or
  more active workspaces** (active = used in the past 28 days) plus a security review of your endpoints for TLS and
  request signing. Public distribution is separate and does not require review.
- **Token rotation is opt-in.** With rotation on, access tokens expire in **12 hours (43,200s)** and are refreshed via
  `oauth.v2.access` with `grant_type=refresh_token`; rotated tokens carry an `xoxe`-prefixed format.

If the dashboard does not look like this, stop and report what you actually see rather than clicking on.

## 1. Decide: reuse the existing app, or create a new one

A new app means a new client ID, and **every existing workspace install is bound to the old one** — customers would
have to reinstall. Reuse the existing app for: adding a scope (existing installs must re-authorize to gain it), adding
a redirect URL, rotating a secret, or diagnosing an install failure.

Create a **new** app only when the user explicitly wants one: a separate app for a different product surface, a
replacement for a compromised app, or a Gov Slack (`slack-gov.com`) deployment. Say which path you are taking first.

## 2. Account and workspace

There is no developer account to register. You need:

- **A Slack account** — sign in at `https://slack.com/signin`, then go to `https://api.slack.com/apps`.
- **A workspace you can install into.** Create a free one for testing rather than installing a half-configured app
  into a production workspace. The app is owned by the workspace you pick at creation time, and that choice is not
  easily changed.
- **Permission to install.** Many workspaces restrict app installation to admins, or require admin approval for each
  app. If installs silently land in an "awaiting approval" state, that is the workspace's app-approval setting, not
  your app being broken — a customer-side setting you cannot fix from the portal.

Hand control back to the user for anything only they can do: sign-in, email or SMS verification, accepting terms,
approving the install as a workspace admin. Do not retry a blocked step in a loop.

**If this session has no browser automation** (the usual case for a CLI or cloud run), do not pretend to click. Hand
the user an exact, ordered click path with the literal values to paste (§4 redirect URLs, §5 scope list), or hand them
a ready-made app manifest (§3) to paste in one shot, then continue once they report back with the client ID.

## 3. Create the app

At `https://api.slack.com/apps` → **Create New App**:

- **From an app manifest** — paste YAML or JSON describing name, scopes and redirect URLs. This is the option to
  prefer: it is reviewable, reproducible, and puts the scope list where a diff can catch it.
- **From scratch** — then set each field by hand in the dashboard.

A minimal manifest for a user-token connector:

```yaml
display_information:
  name: My App
oauth_config:
  redirect_urls:
    - https://api.example.com/oauth/code
  scopes:
    user:                 # see §5 — user vs bot is the decisive choice
      - users:read
      - channels:read
settings:
  token_rotation_enabled: false
```

Where things live in the dashboard afterwards:

| What | Where |
| --- | --- |
| Client ID, client secret, signing secret | **Basic Information → App Credentials** |
| Redirect URLs | **OAuth & Permissions → Redirect URLs** |
| Bot and user scopes | **OAuth & Permissions → Scopes** |
| Public distribution | **Manage Distribution** |
| Token rotation toggle | **OAuth & Permissions** (treat as one-way — see §6) |

## 4. Redirect URLs

Register **every** callback host your platform serves, under **OAuth & Permissions → Redirect URLs**. For Unified.to
these are one per data center — confirm the current list with the platform owner rather than assuming:

```
https://api.unified.to/oauth/code          # us (default)
https://api-eu.unified.to/oauth/code       # eu
https://api-au.unified.to/oauth/code       # au
https://api-dev.unified.to/oauth/code      # dev
```

Slack's matching rules, which cause most install failures:

- **HTTPS required**, no URL anchors. Slack does not accept `http://` here, including for localhost.
- The `redirect_uri` you send must **match or be a subdirectory of** a registered URL.
- When more than one redirect URL is registered, you must send the **same `redirect_uri` in both** the authorize
  request and the `oauth.v2.access` exchange, or the exchange fails with `bad_redirect_uri`. A flow that works with
  one registered URL and breaks after you add a second is this rule, not a Slack outage.

## 5. Scopes — bot token or user token (the decisive choice)

Slack issues two different tokens from one install, driven by two different parameters:

| Parameter | Scope list in the app | Token in the response | Prefix | Acts as |
| --- | --- | --- | --- | --- |
| `scope` | **Bot Token Scopes** | `access_token` (top level) | `xoxb-` | The app itself |
| `user_scope` | **User Token Scopes** | `authed_user.access_token` | `xoxp-` | The installing user |

Configure the list that matches what your authorize URL sends. Configuring bot scopes for a connector that reads
`authed_user.access_token` produces an install that "succeeds" and then has no usable token — a failure that surfaces
on the first API call, not at install.

**Unified.to's Slack connector sends `user_scope` only** — it reads the user token and never uses a bot token. So its
app needs **User Token Scopes**, and its scope set as of 2026-09-20 is:

```
channels:history  channels:read  chat:write  files:read  files:write
groups:history    groups:read    im:history  im:read     im:write
im:write.topic    mpim:history   mpim:read   mpim:write  mpim:write.topic
users:read        users:read.email           users.profile:read
```

Sign-in-only (identity) installs use `email`, `openid` and `profile` against Slack's OpenID Connect authorize URL
instead. That list drifts as object coverage grows — confirm the current set with the connector's owner before you
submit.

Mechanics worth knowing:

- **Scopes are comma-delimited** in the authorize URL, not space-delimited as in most OAuth2 implementations.
- Authorize URL: `https://slack.com/oauth/v2/authorize` (Gov Slack: `https://slack-gov.com/oauth/v2/authorize`).
- Adding a scope later does **not** upgrade existing installs — each workspace must re-authorize before the new scope
  works. Plan scope additions as a customer-visible re-consent, not a silent config change.
- Request only what the connector calls. Over-broad scope sets are a standard Marketplace review rejection, and
  reviewers check data access against what the app demonstrably needs.

## 6. Capture the credentials

From **Basic Information → App Credentials**:

- **Client ID** and **client secret** — both re-readable and regenerable, unlike most portals
- **Signing secret** — verifies inbound request signatures; treat it as a credential even though it is not OAuth
- **App ID** and the workspace that owns the app

Token exchange is `POST https://slack.com/api/oauth.v2.access` with `code`, `client_id`, `client_secret` and the same
`redirect_uri` as the authorize call. The response carries the bot token at `access_token`, the user token at
`authed_user.access_token`, the granted `scope` strings, and `team.id`.

Two Slack-specific traps:

- **Slack returns HTTP 200 on failure.** Errors arrive as `{"ok": false, "error": "..."}` in the body. A client that
  checks only the status code will store an empty token and fail later with no useful log line.
- **Token rotation is a one-way door in practice.** Turning it on makes every access token expire in 12 hours; if the
  client does not implement the refresh exchange, every connection breaks within a day. Confirm the client refreshes
  *before* enabling it, and never enable it to "improve security" mid-run.

Regenerating the client secret invalidates the old one immediately — existing *installs* keep working (they hold
tokens), but every future code exchange and refresh fails until the new secret is deployed. Never rotate without
explicit go-ahead and a cutover plan.

Report the secret once so the user can paste it into their secret store, say plainly that it is now in the transcript
and can be regenerated, then move on.

## 7. Distribution: public install vs Marketplace listing

These are two different things, and only one needs a review:

- **Public distribution** — **Manage Distribution → Share Your App with Other Workspaces**. Complete the dashboard's
  checklist (the recurring blocker is "remove hard-coded information": no workspace-specific IDs, tokens or URLs baked
  into the app), then **Activate Public Distribution**. You get a sharable install URL and an *Add to Slack* button.
  Any workspace can now install. No Slack review.
- **Slack Marketplace listing** — optional, and a real review: the app must already be installed on **5+ active
  workspaces**, and Slack tests your endpoints for TLS and request-signature verification, reviews the listing
  content, installs the app, and checks that data access matches what the app needs.

For a multi-tenant connector, public distribution is usually the requirement; a listing is a go-to-market decision.

**Stop and ask** before answering anything you would have to invent on a listing submission: security questionnaires,
compliance certifications, data-retention periods, subprocessors, support SLAs, customer counts, or demo videos.

## 8. Verify end-to-end

A half-working registration surfaces later as a customer who cannot connect. Prove it:

1. Install the app into your **test workspace** through the real flow — the sharable URL or your platform's connect
   button, not the dashboard's "Install to Workspace" shortcut, which skips redirect-URL matching.
2. Complete the `oauth.v2.access` exchange and check the field you actually consume: `authed_user.access_token` for a
   user-token connector, top-level `access_token` for a bot-token one.
3. Confirm the granted `scope` string contains what you asked for, then make one real API call.

| Symptom | Cause |
| --- | --- |
| `bad_redirect_uri` | `redirect_uri` missing or different between the authorize and exchange calls (§4) |
| `invalid_scope_requested` | Scope not configured on the app, or bot scope sent as `user_scope` |
| Install succeeds, no usable token | Configured bot scopes but the client reads the user token, or vice versa (§5) |
| Everything dies ~12h after install | Token rotation enabled without a refresh implementation (§6) |
| Install stuck pending | Workspace requires admin approval — customer-side (§2) |
| `missing_scope` on one API call | Scope added after install; that workspace must re-authorize (§5) |

## 9. Hand off — never commit the secret

- **Do not** write the client secret or signing secret into source control, a test, a fixture, a committed `.env`, a
  ticket, a PR body, or a chat channel. Values go to the user, for the secret store or console.
- If a code change is needed (a new callback host, a scope list), keep it secret-free and say what the human must set
  out of band.
- Close with: app name and owning workspace; client ID; where the secret was delivered; the exact scope strings and
  whether they are bot or user scopes; distribution state; and anything left for the user to do.

## Stop and ask

Hand back to a human rather than guessing when: installing into a workspace you were not told to touch; enabling token
rotation on an app with live installs; regenerating a secret that live connections depend on; removing a redirect URL;
a Marketplace submission asks for compliance, legal or volume claims; or the dashboard does not match the
**Platform state** section above.

## References

- Installing with OAuth — https://docs.slack.dev/authentication/installing-with-oauth
- Token types — https://docs.slack.dev/authentication/token-types
- Using token rotation — https://docs.slack.dev/authentication/using-token-rotation/
- Scope reference — https://docs.slack.dev/reference/scopes
- `oauth.v2.access` — https://docs.slack.dev/reference/methods/oauth.v2.access
- App manifests — https://docs.slack.dev/app-manifests
- App distribution — https://docs.slack.dev/distribution
- Marketplace guidelines and requirements — https://docs.slack.dev/slack-marketplace/slack-marketplace-app-guidelines-and-requirements/
- App dashboard — https://api.slack.com/apps
