Go to Slack

Permissions API

Developer preview

Welcome to the workspace token-based Slack app developer preview, still under active development.

Whether your app asks for permissions progressively or requires them all at installation, workspace token-based Slack apps may use the Permissions API to programmatically manage team access.

Lunch Train asking for upgraded posting permissions

Permissions API Overview

The Permissions API grants you the serenity to deftly manage your app's capabilities, privileges, and resources:

  • Capabilities - the things your app can do for a team, like offering slash commands or posting interactive notifications. Sometimes, but not always, represented by a scope.
  • Privileges - the things your app is allowed to do for a team, like being granted the ability to learn more about users or examine channel history. Typically represented by a scope.
  • Resources - the where and what your app can act and act on, like a list of channels, specific direct message conversations, and a team.

The Permissions API is not a single API but a collection of Web API methods and Events API event types that inform your app about permission and access change over time while also providing your app with the hooks needed to progressively ask for additional permissions. OAuth 2.0 is still required to manage initial application installation for distributed apps.

Workspace token-based Slack apps are required for the Permissions API. It cannot be used with traditional user and bot token-based Slack apps or legacy custom integrations.

Flow and states

Where does OAuth end and these permission APIs begin? In the infinitesimal space between the reticulated splines of each. They work together.

Here is a description of your hopefully never-ending, non-linear Permissions API lifecycle :

A user installs your app for the first (or nth) time

This is part of the Slack app installation flow, either using OAuth or the streamlined single team installation flow.

Lunch Train installation

All workspace token-based apps request default permissions from the installing user — the opportunity to have a direct message conversation with the app. Additional permissions can be requested at this time, or they can be saved for later.

A user adds your app to a resource

During the installation process or during the normal course of user business, a user might add your app directly to a resource.

Lunch Train being added to a single channel

One way they might do this is by inviting your app to a public or private channel, or by adding your slash command to a channel, or by discovering your app in the sidebar.

If subscribed, you'll receive resources_added events as this happens.

A user removes your app from a resource

This happens to all apps. Your app is asked to leave a channel or is removed from it in some other way.

When this happens, your scopes and access will no longer apply to the resource and you'll get a resources_removed event, if subscribed.

Your app requests additional permissions

If progressive permissions is an attractive concept to you, then bask in the opportunity to conversationally suggest expanding your app's permission purview.

Lunch Train asks for more permission

While your app can't make this request out of the blue, there are many opportunities to initiate this flow and it's an ideal way to gate paid features, trial new functionality, and change your app's capabilities over time.

Implementation details can be found in the progressive permissions section below.

Your app is awarded or rejected those permissions

It's not enough to ask for progressive permissions, you need to be granted or rejected them to.

Lunch Train gets more permission

This step is actually just like the other resources_added step above — the net result is your app gains resources and is notified about it.

Your app waits for stuff to happen

Your app is probably doing other things, just things with the permissions, resources, and capabilities afforded to it. But as far as the Permissions API goes, your app just waits for a new change to occur.

Maybe your app makes occasional requests to apps.permissions.info while waiting — just in case.

Resources

Each OAuth scope governs your app's capabilities against a set of resources.

A single resource might be a specific direct message conversation, a private channel, or maybe even a wildcard.

Today, workspace token-based apps support only these resources:

  • channel - public channels
  • im - private direct messages
  • mpim - private multi-party direct messages
  • group - private channels
  • team - information about the team itself and its inhabitants

Expect this list to include users and files and more in the future.

Wildcard resources

These little tricksters let a user add your application to all of something. Today, only the channels resource can be communicated as a wildcard, meaning a user can indicate your app should be installed in all channels. All channels that is, except those explicitly excluded.

Here's what it looks like when a user selects a channel wildcard:

Lunch Train asking for upgraded posting permissions

If the user selects that All Public Channels option, you'll see an OAuth response somewhat like this from oauth.token:

{
    "ok": true,
    "access_token": "xoxa-access-token-string",
    "token_type": "app",
    "app_id": "A0BLA3EP2",
    "app_user_id": "U0BH95DFB",
    "installer_user_id": "U061F7AUR",
    "team_name": "Subarachnoid Workspace",
    "team_id": "T061EG9R6",
    "permissions": [
        {
            "scopes": [
                "channels:read",
                "chat:write:user"
            ],
            "resource_type": "channel",
            "resource_id": 0
        }
    ]
}

In that response, resource_id set to 0 tells you that you've been approved wildcard access to all public channel resources for the scopes channels:read and chat:write:user. If that sounds a little crazy to you, don't worry, we'll improve it later to something more like:

This wildcard status is more clearly communicated in the response to apps.permissions.info:

{
    "ok": true,
    "info": {
        // ...
        "channel": {
            "scopes": [
                "channels:read",
                "chat:write:user"
            ],
            "resources": {
                "ids": [],
                "wildcard": true,
                "excluded_ids": []
            }
        }
    }
}

Using this method, you'll find an explicit wildcard boolean under channel.

Exclusionary resources

Workspaces can also explicitly indicate the channels a workspace token-based Slack app may not access, no matter their scopes. You'll see these channel IDs listed under excluded_ids in apps.permissions.info.

Given an exclusion, you won't be able to read or write data about that resource.

OAuth permission scopes

As with all Slack apps, workspace token-based apps are governed by the OAuth 2.0 scopes system.

To determine which OAuth scopes are supported with workspace token-based apps, look for the workspace token labels. Only the scopes with this label work with workspace-based apps and the Permissions API.

Or consult this list right here. During the developer preview period you may notice some of these scopes are not quite useful yet with workspace tokens.

Monitoring change with the Events API

If you're working with workspace token-based apps or with the Permissions API, then chances are you'll want to use the Events API too. And if you do that, you'll want to subscribe to these app event types, which require no specific OAuth scope.

resources_added

The thing about this event is that you need to be ready for it at any time. In a world of workspace token-based apps, a user might discover your app and open a conversation with it. Or discover your slash command and add it to a dozen channels willy-nilly.

For direct messages, you won't get a chance to say "yeah, but" — the conversation will open and you'll get the base-level grant to converse as if they're an installer.

{
        "token": "verification_token",
        "team_id": "T061EG9R6",
        "api_app_id": "A0BLA3EP2",
        "event": {
                "type": "resources_added",
                "resources": [
                        {
                                "resource": {
                                        "type": "channel",
                                        "grant": {
                                                "type": "specific",
                                                "resource_id": "C061EG9SL"
                                        }
                                },
                                "scopes": [
                                        "channels:read"
                                ]
                        }
                ]
        },
        "type": "event_callback",
        "authed_teams": [],
        "event_id": "Ev0CV7H3KJ",
        "event_time": 1501110008
}

Here's another example of a resources_added event, this time our app is being awarded the users:read scope against the team resource. We can read the list of users and information about those users.

{
    "token": "verification_token",
    "team_id": "T061EG9R6",
    "api_app_id": "A0BLA3EP2",
    "event": {
        "type": "resources_added",
        "resources": [
            {
                "resource": {
                    "type": "team",
                    "grant": {
                        "type": "specific",
                        "resource_id": "T061EG9R6"
                    }
                },
                "scopes": [
                    "users:read"
                ]
            }
        ]
    },
    "type": "event_callback",
    "authed_teams": [],
    "event_id": "Ev0CQ92RH7",
    "event_time": 1501174698
}

resources_removed

Users and admins can remove your app from channels and other conversations. Maybe someday they can seek and destroy a piecemeal permission. If you're keeping track, this event subscription will tell you which grants have gone the way of the dodo.

{
    "token": "verification_token",
    "team_id": "T061EG9R6",
    "api_app_id": "A0BLA3EP2",
    "event": {
        "type": "resources_removed",
        "resources": [
            {
                "resource": {
                    "type": "channel",
                    "grant": {
                        "type": "specific",
                        "resource_id": "C061EG9SL"
                    }
                },
                "scopes": [
                    "channels:read"
                ]
            }
        ]
    },
    "type": "event_callback",
    "authed_teams": [],
    "event_id": "Ev0CV7JG72",
    "event_time": 1501110117
}

scopes_granted

When subscribed to this event, you'll receive it when a user approves specific scopes to your app. Often paired with resources_added events, though it depends on what the scopes are being added to.

scopes_denied

When subscribed to this event, you'll receive it when a user rejects the overtures of your apps.permission.request.

Asking for additional permissions progressively

Your app's initial moment of installation is just the beginning.

By default, your app is only installed into a single conversation with a single user — the installer.

You can request a list of OAuth scopes as part of an OAuth-based installation process, getting assigned specific scopes and resource combinations by the installer. But it'd be pretty tedious for every user that wants to work with your app to have to also go through this installation flow.

Imagine another user starting a DM with your app and realizing you don't have the permission to examine their profile, or your recently created slash command wasn't yet installed.

Prerequisites

To effectively use progressive permissions, your app should make use of the Events API, app events, and either interactive messages or slash commands.

Flow

The progressive permissions flow begins with a user interacting with your app.

Step 1: User interaction

To ask for an additional permission, a user must first interact with your application one of two ways: 1. Invoke a slash command, if your app offers one 2. Interact with a message button or message menu as part of the interactive message framework.

Step 2: Your app receives a trigger_id

When that interaction occurs, your slash command request URL or interactive message request URL will receive a standard payload with a bonus field called trigger_id.

A trigger_id is an artifact of interaction between the user and your app. Use it soon after receiving to draw a theoretical dotted line between the interaction and your requesting permissions.

Step 3: Use the trigger_id to request upgraded permissions

Using the apps.permissions.request method, request any additional OAuth scopes, passing the time-sensitive trigger_id as a kind of symbolic proof of user interest.

Step 4: User approves or revokes your request

After receiving a successful apps.permissions.request, Slack will pop open an in-client dialog with the end user, making the case for your upgraded permissions.

Step 5: Your app receives additional grants

Depending on whether they approve or revoke, you'll then receive a set of accompanying Events API events: scope_granted and resources_granted if it's successful, or scope_denied if they turn your request down.

Walkthrough

Here are the nuts and bolts of a typical progressive permissions flow:

Your app is installed in a direct message conversation with @izzy. You only have the default permissions and they only apply to your conversation with @izzy.

You want to install a slash command in your app's conversations.

Step 1: Tell @izzy you have a slash command and to install it just click this button by posting an interactive message to your direct message conversation.

We'll send a simple message with a message button attachment built just so:

[
        {
                "callback_id": "perm_request_1234",
                "attachment_type": "default",
                "fallback": "Adding this command requires an official Slack client.",
                "actions": [
                        {
                                "name": "add_commands",
                                "text": "Add /insectoid command",
                                "type": "button",
                                "value": "add"
                        }
                ]
        }
]

Which takes the form of a application/x-www-form-urlencoded request to chat.postMessage like this:

POST slack.com/api/chat.postMessage?token=xoxa-token-token-token
text=New%20feature%20alert%3A%20Try%20our%20new%20slash%20command%20to%20look%20up%20all%20the%20intersections%20you%27ve%20collected.
&attachments=%5B%7B%22callback_id%22%3A%22perm_request_1234%22%2C%22attachment_type%22%3A%22default%22%2C%22fallback%22%3A%22Adding%20this%20command%20requires%20an%20official%20Slack%20client.%22%2C%22actions%22%3A%5B%7B%22name%22%3A%22add_commands%22%2C%22text%22%3A%22Add%20%2Finsectoid%20command%22%2C%22type%22%3A%22button%22%2C%22value%22%3A%22add%22%7D%5D%7D%5D
&channel=D0BH95DLH

Your DM conversation with the user in "channel" D0BH95DLH will then receive this message.

Step 3: Use apps.permissions.request to prompt Slack to invoke a modal permissions dialog with the user.

apps.permissions.request takes three parameters:

  • token - your workspace token for the team, typical!
  • trigger_id - the trigger_id value you only just received from Step 2. It's time-sensitive. Use it context with your moment of conversation.
  • scopes - a comma-separated list of the scopes you want to add to the app, in this case commands but could've been users:read,channels:history or similar.

Our request may look something like:

POST https://slack.com/api/apps.permissions.request?token=xoxa-token-token-token
trigger_id=11820576016.6048553856.093b9205636ddbfc180e1e74d33e9af6
&scopes=commands

The HTTP response is a simple HTTP 200 with {"ok":true} if things check out or {"ok":false} if you don't provide a valid scope or trigger_id or otherwise can't properly make this request.

Step 4: The user is presented with a dialog in their open Slack client. It's super-friendly. It asks whether this app can upgrade its permissions to include commands, the ability to use Slash commands. Let's assume the user says yes here.

Step 5: Your Events API request URL receives a small flurry of events (OK, 2):

The first is scope_granted and it tells you that the commands scope was added to the installation. It even includes the trigger_id you just used in case you want to track it roundtrip:

{
    "token": "verification_token",
    "team_id": "T061EG9R6",
    "api_app_id": "A0BLA3EP2",
    "event": {
        "type": "scope_granted",
        "scopes": [
            "commands"
        ],
        "trigger_id": "11820576016.6048553856.093b9205636ddbfc180e1e74d33e9af6"
    },
    "type": "event_callback",
    "authed_teams": [],
    "event_id": "Ev0BQ5FTL0",
    "event_time": 1497911545
}

The second event gets into more detail about the resources involved and where that commands scope is now applicable to. In the case of commands, that'll be any channel contexts your app is installed in. Slash commands are used in channels and other conversational contexts.

{
    "token": "verification_token",
    "team_id": "T061EG9R6",
    "api_app_id": "A0BLA3EP2",
    "event": {
        "type": "resources_added",
        "resources": [
            {
                "resource": {
                    "type": "im",
                    "grant": {
                        "type": "specific",
                        "resource_id": "D0BH95DLH"
                    }
                },
                "scopes": [
                    "chat:write:user",
                    "im:read",
                    "im:history",
                    "commands"
                ]
            }
        ]
    },
    "type": "event_callback",
    "authed_teams": [],
    "event_id": "Ev0BLTJ7JM",
    "event_time": 1497911545
}

Now @izzy can use /insectoid in your app-to-user DM conversation and any other channels your app is installed in.

Methods for the Web API

Look up your app's current state of permissions with a team or leverage a trigger_id to make a progressive permissions request with these two workspace token API methods.

Examine current permissions with apps.permissions.info

Look up your token's current permission state at will with apps.permissions.info. You'll find a complete inventory of afforded OAuth scopes and resources.

Example output

{
    "ok": true,
    "info": {
        "team": {
            "scopes": [],
            "resources": {
                "ids": []
            }
        },
        "channel": {
            "scopes": [
                "channels:read"
            ],
            "resources": {
                "ids": [
                    "C061FA5PB"
                ],
                "wildcard": false,
                "excluded_ids": []
            }
        },
        "group": {
            "scopes": [],
            "resources": {
                "ids": []
            }
        },
        "mpim": {
            "scopes": [],
            "resources": {
                "ids": []
            }
        },
        "im": {
            "scopes": [],
            "resources": {
                "ids": []
            }
        },
        "app_home": {
            "scopes": [
                "chat:write:user",
                "im:history",
                "im:read"
            ],
            "resources": {
                "ids": [
                    "D0C0NU1Q8",
                    "D0BH95DLH"
                ]
            }
        }
    }
}

What's an app_home? It's like a virtual house for your Slack app. Today, it's a handy way of knowing which users you can have open direct message conversations with — the ones users can find under Apps in the sidebar.

The resources correspond to those direct message conversations and scopes are your app's powers granted to those same conversations.

Request permissions with apps.permissions.request

This is your powerful write request initiating everything awesome about progressive permissions.

When successful, a delightful dialog will be presented to the intended user, requesting the scopes in context to the appropriate resources.

The real trick with the method, if you haven't read deeper yet, is that you need a trigger_id and you can only get one of those from a slash command invocation or an interactive message action.

Which means that your app needs to incite a user to do something -- click a button, select from a menu, or type a slash command to inspire your app to ask the user for more permission.

Events for the Events API