Skip to content
DocsSlack

Connect a provider

Connect Slack

Verified against the Slack console, August 2026

@kody/slack talks Slack with a user token. Create your own Slack app with User Token Scopes, register Kody's redirect URI, then finish on /connect/oauth. The helpers reject bot-only grants (xoxb- / auth.test with bot_id and no user_id).

What you get

Once connected, you can ask Kody things like:

  • "List the public channels I can see in Slack."
  • "Post this deploy summary to #eng-alerts."
  • "Find recent messages mentioning the billing outage."

Exact capabilities depend on the User Token Scopes granted at connect time.

Create a user-token Slack app

  1. Confirm there is no existing user-token Slack connection: integrationList. A connection named slack that only has a bot grant does not count — leave it and use a distinct name such as slack-user.
  2. Create a Slack app at https://api.slack.com/apps with User Token Scopes. User-token Slack uses oauth/v2_user + oauth.v2.user.access, not the bot-token oauth/v2 + oauth.v2.access.
  3. Register the redirect URI https://kody.codes/connect/oauth.
  4. Ask the user for the client id. They paste the client secret on /connect/oauth — never in chat.
  5. Open /connect/oauth (or /connect/oauth?provider=slack-user when slack is already a bot grant). Fill:
    • Name: slack when free, otherwise a distinct name
    • Authorization URL: https://slack.com/oauth/v2/authorize
    • Token URL: https://slack.com/api/oauth.v2.user.access
    • Client ID: the Slack app client id
    • Client secret: paste on the form
    • Scopes: comma-separated User Token Scopes
    • PKCE: off (confidential client)
    • Allowed hosts: slack.com,files.slack.com
  6. After they authorize, ./smoke-test on @kody/slack (or the forked package) must report a user identity. A bot token is a failed setup, not a package bug.

See the OAuth guide for query parameters, confidential exchange, and reconnect behavior.

Verify

Run this smoke test in execute after connecting (adjust the integration name if you chose something other than slack):

import { createAuthenticatedFetch } from 'kody:runtime'

export default async function main() {
	const slackFetch = await createAuthenticatedFetch('slack')
	const response = await slackFetch('https://slack.com/api/auth.test', {
		method: 'POST',
	})
	if (!response.ok) {
		throw new Error(
			`Slack smoke test HTTP failed: ${response.status} ${await response.text()}`,
		)
	}
	const data = (await response.json()) as {
		ok?: boolean
		error?: string
		user?: string
		user_id?: string
		bot_id?: string
		team?: string
	}
	if (!data.ok) {
		throw new Error(`Slack auth.test failed: ${data.error ?? 'unknown'}`)
	}
	if (!data.user_id || data.bot_id) {
		throw new Error(
			'Slack auth.test returned a bot grant. @kody/slack needs a user token.',
		)
	}
	return { user: data.user, userId: data.user_id, team: data.team }
}

After the connection exists, call Slack through createAuthenticatedFetch('slack') (or the connection name you chose). Do not store Slack OAuth tokens as named secrets.

Troubleshooting

  • invalid_auth / token_revoked: reconnect with your /connect/oauth URL.
  • Missing channels or post failures: the granted User Token Scopes do not cover the API method. Reconnect with a Slack app that includes them.
  • redirect_uri mismatches: the registered redirect must be exactly https://kody.codes/connect/oauth.
  • auth.test shows bot_id and no user_id: this is a bot token. Do not retry @kody/slack helpers. Connect a user-token Slack app instead (use a distinct name such as slack-user if slack is already the bot connection).

Do not

  • Do not send the user to /account/secrets/new for Slack OAuth tokens.
  • Do not secretSet Slack access or refresh tokens.
  • Do not paste tokens or the Slack client secret in chat.
  • Do not treat a bot-token Slack connection as a working @kody/slack setup.
  • Do not invent xoxp- / xoxb- token collection outside /connect/oauth.

Working with an agent? This page is also plain markdown at /docs/slack.md, or load it over MCP with search({ entity: 'provider_slack:guide' }).