Field notes
Slack API fix guides
Slack answers almost everything with HTTP 200, and puts the failure in the body as ok: false. Code that checks the status code sees success and moves on, so a bot that is not in the channel, or a token missing one scope, looks exactly like a bot that worked. Each note here explains one such problem and gives you a script that finds it through the API.
A script here holds a token that can post into your workspace and read your conversations. So these read, they tell you exactly what is wrong, and they print the repair: the scope to add, the channel to join, the reinstall URL. You run it.
Slack is unusually good at saying what is missing — a missing_scope error names both what was needed and what was provided — and most of these notes are about reading the answer it already gave you.
Slack
Slack answers HTTP 200 and puts the failure in the body
Every Slack Web API error arrives as 200 OK with ok: false in the JSON. Clients that check the status code read every failure as a success.
Not_in_channel: the bot was never invited to the channel
Installing a Slack app joins it to nothing. The token is valid, the channel ID is right, and every call returns 200 with ok: false and not_in_channel.
Missing_scope tells you the scope needed and the ones you have
Slack names both needed and provided on a missing_scope error, and X-OAuth-Scopes lists the whole grant. Adding the scope is not enough: reinstall.
Next_cursor is ignored so only the first page is ever seen
A Slack list call returns 100 items and a cursor. Code that reads the array and stops loses the rest silently, with ok: true and no error anywhere.
The same message posted three times, and the ts says why
Slack has no idempotency key, so every duplicate is a real second call. The gap between the copies names the cause: a double subscription or a retry.
The bot answers its own messages in an endless loop
message.channels delivers your own posts back to you. Without a bot_id guard the handler replies to its own reply, and history shows the run.
Files made public with a link that works without a Slack login
files.list reports public_url_shared per file. Where it is true the permalink_public opens for anyone on the internet, with no login and no expiry.
Conversations.history clamped to 15 objects and 1 per minute
Ask for 200 messages and get 15. Slack clamps conversations.history for apps that are not on the Marketplace, and there is no setting to change.
Installs keyed on team_id alone collide on Enterprise Grid
On Grid an install is identified by enterprise_id, team_id and is_enterprise_install. A store keyed on team_id alone hands one tenant another's token.
Files.upload is retired: one probe returns method_deprecated
files.upload was sunset for every app on 12 November 2025. One argument-free read of the method answers method_deprecated and settles it in a second.
Every Slack profile has a null email and nothing errored
users:read returns profiles with no email at all. The address needs users:read.email, a separate scope, and its absence is a missing key rather than an error.
Slack disabled event delivery and will not turn it back on
Fail more than 95% of deliveries in an hour and Slack switches your event subscriptions off. Recovery is manual, and the Web API never reports the flag.
The scope was granted to the user token, not the bot
Slack keeps two scope lists per app. Add the scope to one and call with the other and missing_scope survives every reinstall you throw at it.
Token_revoked: the app is gone and retrying will not help
token_revoked is permanent. Sweep auth.test across the installation store, sort the dead rows by what should happen to them, and stop scheduling work.
Account_inactive: the installer left and took the token
A user token dies with its human. Join your install rows against the member directory to find the automations that are one offboarding away from stopping.
Token_expired every 12 hours because rotation is on
Rotated Slack tokens live 43200 seconds. Read the xoxe. prefix and the stored expires_in to find installs with no refresh loop, before tonight breaks them.
Slack refresh tokens are single use: a replay kills the pair
Two replicas refreshing a rotating Slack token at once burn a single-use refresh token. The evidence is in your refresh ledger, not in the API.
Invalid_auth: the xapp- token is in the Web API slot
Slack issues six token classes with different prefixes. A prefix check across your environment finds the swap before a single request is sent.
Not_allowed_token_type: right secret, wrong token class
The token authenticates everywhere else and one method still refuses it. Probe each method and read an argument error as proof the class was accepted.
The token holds admin scopes the app has never called
Nothing is failing, which is the problem. Compare X-OAuth-Scopes against the methods your code actually calls and prune what has no call site.
These are the ones I keep hitting. If yours is broken in a way none of them describes, tell me on LinkedIn — it is usually how the next note gets written.