Skip to main content
OAuth2 enables application developers to build applications that utilize authentication and data from the Discord API. Discord supports the authorization code grant, the implicit grant, client credentials, and some modified special-for-Discord flows for Bots and Webhooks.

Shared resources

The first step in implementing OAuth2 is registering a developer application and retrieving your client ID and client secret. After creating your application, make sure you have your client_id and client_secret handy. Refer to RFC 6749 for the full OAuth2 specification. OAuth2 URLs
The token and token revocation URLs only accept a content type of application/x-www-form-urlencoded. JSON content is not permitted and will return an error.

OAuth2 scopes

These are all the OAuth2 scopes that Discord supports. Some scopes require approval from Discord to use.
ScopeDescription
activities.readFetch data from a user’s “Now Playing/Recently Played” list — not currently available for apps
activities.writeUpdate a user’s activity — not currently available for apps
applications.builds.readRead build data for a user’s applications
applications.builds.uploadUpload/update builds for a user’s applications — requires Discord approval
applications.commandsAdd commands to a guild — included by default with the bot scope
applications.commands.updateUpdate your app’s commands using a Bearer token — client credentials grant only
applications.commands.permissions.updateUpdate permissions for your app’s commands in a guild
applications.entitlementsRead entitlements for a user’s applications
applications.store.updateRead and update store data (SKUs, store listings, achievements, etc.) for a user’s applications
botFor OAuth2 bots, puts the bot in the user’s selected guild by default
connectionsAllows /users/@me/connections to return linked third-party accounts
dm_channels.readSee information about the user’s DMs and group DMs — requires Discord approval
emailEnables /users/@me to return an email
gdm.joinJoin users to a group DM
guildsAllows /users/@me/guilds to return basic information about all of a user’s guilds
guilds.joinAllows /guilds/{guild.id}/members/{user.id} to be used for joining users to a guild
guilds.members.readAllows /users/@me/guilds/{guild.id}/member to return a user’s member information in a guild
identifyAllows /users/@me without email
messages.readRead messages from all client channels via local RPC server API access
relationships.readAccess a user’s Discord Friends list, pending requests, and blocked users — Social SDK only
role_connections.writeUpdate a user’s connection and metadata for the app
rpcControl a user’s local Discord client via local RPC server access — requires Discord approval
rpc.activities.writeUpdate a user’s activity via local RPC server access — requires Discord approval
rpc.notifications.readReceive notifications pushed out to the user via local RPC server access — requires Discord approval
rpc.voice.readRead a user’s voice settings and listen for voice events via local RPC — requires Discord approval
rpc.voice.writeUpdate a user’s voice settings via local RPC — requires Discord approval
voiceConnect to voice on user’s behalf and see all voice members — requires Discord approval
webhook.incomingGenerates a webhook returned in the OAuth token response for authorization code grants
In order to add a user to a guild, your bot must already belong to that guild. role_connections.write cannot be used with the Implicit grant type.

State and security

Before implementing OAuth2 grants, it’s important to understand the state parameter and how it prevents Cross-site request forgery (CSRF) and Clickjacking attacks. state is sent in the authorization request and returned in the response. It should be a value that binds the user’s request to their authenticated state — for example, a hash of the user’s session cookie or a nonce linked to the user’s session. When the user is redirected back, validate that the state returned matches the stored value. If they don’t match, deny the request.
While Discord does not require the state parameter, it is highly recommended for the security of your own applications and data.

Authorization code grant

The authorization code grant is the standard OAuth2 flow and involves retrieving an authorization code and exchanging it for a user’s access token. All calls to the OAuth2 endpoints require either HTTP Basic authentication or client_id and client_secret in the form data body.
1

Redirect the user to the authorization URL

Construct the authorization URL and direct the user to it:
https://discord.com/oauth2/authorize?response_type=code&client_id=157730590492196864&scope=identify%20guilds.join&state=15773059ghq9183habn&redirect_uri=https%3A%2F%2Fnicememe.website&prompt=consent&integration_type=0
Key parameters:
  • client_id — your application’s client ID
  • scope — space-separated (URL-encoded as %20) list of OAuth2 scopes
  • redirect_uri — your registered redirect URI, URL-encoded
  • state — unique string for CSRF protection
  • prompt — set to consent to always show the authorization screen, or none to skip if already authorized
  • integration_type0 for guild install, 1 for user install (only relevant when scope contains applications.commands)
2

Receive the authorization code

After the user authorizes your application, they are redirected to your redirect_uri with a code query parameter:
https://nicememe.website/?code=NhhvTDYsFcdgNLnnLijcl7Ku7bEEeee&state=15773059ghq9183habn
Validate the state parameter before proceeding.
3

Exchange the code for an access token

Make a POST request to the token URL to exchange the code for an access token:
import requests

API_ENDPOINT = 'https://discord.com/api/v10'
CLIENT_ID = '332269999912132097'
CLIENT_SECRET = '937it3ow87i4ery69876wqire'
REDIRECT_URI = 'https://nicememe.website'

def exchange_code(code):
  data = {
    'grant_type': 'authorization_code',
    'code': code,
    'redirect_uri': REDIRECT_URI
  }
  headers = {
    'Content-Type': 'application/x-www-form-urlencoded'
  }
  r = requests.post('%s/oauth2/token' % API_ENDPOINT, data=data, headers=headers, auth=(CLIENT_ID, CLIENT_SECRET))
  r.raise_for_status()
  return r.json()
Required parameters:
  • grant_type — must be authorization_code
  • code — the code from the query string
  • redirect_uri — the redirect URI associated with this authorization
4

Use the access token

You will receive an access token response:
{
  "access_token": "6qrZcUqja7812RVdnEKjpzOL4CvHBFG",
  "token_type": "Bearer",
  "expires_in": 604800,
  "refresh_token": "D43f5y0ahjqew82jZ4NViEr2YafMKhue",
  "scope": "identify"
}
expires_in is the number of seconds until the access token expires.

Refreshing tokens

To refresh an expired access token, make another POST request to the token URL:
import requests

API_ENDPOINT = 'https://discord.com/api/v10'
CLIENT_ID = '332269999912132097'
CLIENT_SECRET = '937it3ow87i4ery69876wqire'

def refresh_token(refresh_token):
  data = {
    'grant_type': 'refresh_token',
    'refresh_token': refresh_token
  }
  headers = {
    'Content-Type': 'application/x-www-form-urlencoded'
  }
  r = requests.post('%s/oauth2/token' % API_ENDPOINT, data=data, headers=headers, auth=(CLIENT_ID, CLIENT_SECRET))
  r.raise_for_status()
  return r.json()

Revoking tokens

To disable an access or refresh token, make a POST request to the token revocation URL:
import requests

API_ENDPOINT = 'https://discord.com/api/v10'
CLIENT_ID = '332269999912132097'
CLIENT_SECRET = '937it3ow87i4ery69876wqire'

def revoke_access_token(access_token):
  data = {
    'token': access_token,
    'token_type_hint': 'access_token'
  }
  headers = {
    'Content-Type': 'application/x-www-form-urlencoded'
  }
  requests.post('%s/oauth2/token/revoke' % API_ENDPOINT, data=data, headers=headers, auth=(CLIENT_ID, CLIENT_SECRET))
Required parameter:
  • token — the access token or refresh token to revoke
Optional parameter:
  • token_type_hint — either access_token or refresh_token
When you revoke a token, any active access or refresh tokens associated with that authorization will be revoked, regardless of the token and token_type_hint values you pass in.

Implicit grant

The implicit OAuth2 grant is a simplified flow optimized for in-browser clients. The client is directly issued an access token instead of an authorization code. Authorization URL example
https://discord.com/oauth2/authorize?response_type=token&client_id=290926444748734499&state=15773059ghq9183habn&scope=identify
On redirect, your redirect URI will contain additional URI fragments (not query string parameters): access_token, token_type, expires_in, scope, and state. Redirect URL example
https://findingfakeurlsisprettyhard.tv/#access_token=RTfP0OK99U3kbRtHOoKLmJbOn45PjL&token_type=Bearer&expires_in=604800&scope=identify&state=15773059ghq9183habn
The implicit grant does not return a refresh token. The user must explicitly re-authorize once their token expires. The access token is also returned in the URI fragment, making it potentially exposed to unauthorized parties.

Client credentials grant

The client credentials flow lets bot developers get their own bearer tokens for testing purposes. Make a POST request to the token URL using Basic authentication (client ID as username, client secret as password).
import requests

API_ENDPOINT = 'https://discord.com/api/v10'
CLIENT_ID = '332269999912132097'
CLIENT_SECRET = '937it3ow87i4ery69876wqire'

def get_token():
  data = {
    'grant_type': 'client_credentials',
    'scope': 'identify connections'
  }
  headers = {
    'Content-Type': 'application/x-www-form-urlencoded'
  }
  r = requests.post('%s/oauth2/token' % API_ENDPOINT, data=data, headers=headers, auth=(CLIENT_ID, CLIENT_SECRET))
  r.raise_for_status()
  return r.json()
Response (no refresh token is returned):
{
  "access_token": "6qrZcUqja7812RVdnEKjpzOL4CvHBFG",
  "token_type": "Bearer",
  "expires_in": 604800,
  "scope": "identify connections"
}
Team applications are limited to the identify and applications.commands.update scopes, because teams are not bound to a specific user.

Bot users

Discord’s API provides bot users — a separate type of user dedicated to automation. Bot users are automatically added to all apps and are authenticated using the bot token found in your app’s settings. Unlike the normal OAuth2 flow, bot users have full access to most API routes without using bearer tokens, and can connect to the Real Time Gateway.

Bot vs user accounts

Developers must abide by the terms of service, which includes refraining from automating standard user accounts (generally called “self-bots”) outside of the OAuth2/bot API.
Bot users differ from standard Discord users in several ways:
  1. Bots are added to guilds through the OAuth2 API and cannot accept normal invites.
  2. Bots cannot have friends or be added to or join Group DMs.
  3. Verified bots do not have a maximum number of guilds.
  4. Bots have an entirely separate set of rate limits.

Bot authorization flow

Bot authorization is a serverless and callback-less OAuth2 flow that makes it easy for users to add bots to guilds. Bot auth parameters
ParameterDescription
client_idYour app’s client ID
scopeNeeds to include bot for the bot flow
permissionsThe permissions you’re requesting
guild_idPre-fills the dropdown picker with a guild for the user
disable_guild_selecttrue or false — disallows the user from changing the guild dropdown
URL example
https://discord.com/oauth2/authorize?client_id=157730590492196864&scope=bot&permissions=1
The permissions parameter is an integer corresponding to the permission calculations. response_type and redirect_uri are not required because there is no need to retrieve the user’s access token.

Advanced bot authorization

You can request additional scopes outside of bot and applications.commands to prompt a complete authorization code grant flow and gain the ability to request the user’s access token. When receiving the access code on redirect, there will be additional query string parameters: guild_id and permissions. When you retrieve the user’s access token, you’ll also receive information about the guild the bot was added to:
{
  "token_type": "Bearer",
  "guild": {
    "mfa_level": 0,
    "name": "SomeTest",
    "id": "2909267986347357250",
    "icon": null,
    "roles": [
      {
        "hoist": false,
        "name": "@everyone",
        "mentionable": false,
        "color": 0,
        "position": 0,
        "id": "290926798626357250",
        "managed": false,
        "permissions": 49794241,
        "permissions_new": "49794241"
      }
    ]
  },
  "access_token": "zMndOe7jFLXGawdlxMOdNvXjjOce5X",
  "scope": "bot",
  "expires_in": 604800,
  "refresh_token": "mgp8qnvBwJcmadwgCYKyYD5CAzGAX4"
}

Two-factor authentication requirement

For bots with elevated permissions (marked with * in the permissions table), Discord enforces two-factor authentication on the owner’s account when added to guilds that have server-wide 2FA enabled.

Webhooks

Discord’s webhook flow is a specialized version of the authorization code implementation. Set the scope querystring parameter to webhook.incoming:
https://discord.com/oauth2/authorize?response_type=code&client_id=157730590492196864&scope=webhook.incoming&state=15773059ghq9183habn&redirect_uri=https%3A%2F%2Fnicememe.website
When the user authorizes, they select a channel for the webhook. On acceptance, they’re redirected to your redirect_uri with a code parameter. Exchange the code for an access token to receive a modified token response:
{
  "token_type": "Bearer",
  "access_token": "GNaVzEtATqdh173tNHEXY9ZYAuhiYxvy",
  "scope": "webhook.incoming",
  "expires_in": 604800,
  "refresh_token": "PvPL7ELyMDc1836457XCDh1Y8jPbRm",
  "webhook": {
    "application_id": "310954232226357250",
    "name": "testwebhook",
    "url": "https://discord.com/api/webhooks/347114750880120863/kKDdjXa1g9tKNs0-_yOwLyALC9gydEWP6gr9sHabuK1vuofjhQDDnlOclJeRIvYK-pj_",
    "channel_id": "345626669224982402",
    "token": "kKDdjXa1g9tKNs0-_yOwLyALC9gydEWP6gr9sHabuK1vuofjhQDDnlOclJeRIvYK-pj_",
    "type": 1,
    "avatar": null,
    "guild_id": "290926792226357250",
    "id": "347114750880120863"
  }
}
Store the webhook.token and webhook.id. A new webhook is created each time a user goes through the flow, so you’ll need to save each one. Be mindful of rate limits when sending to multiple webhooks.

API endpoints

GET /oauth2/applications/@me

Returns the bot’s application object.

GET /oauth2/@me

Returns info about the current authorization. Requires authentication with a bearer token. Response structure
FieldTypeDescription
applicationpartial application objectThe current application
scopesarray of stringsThe scopes the user has authorized the application for
expiresISO8601 timestampWhen the access token expires
user?user objectThe user who has authorized, if the user has authorized with the identify scope
Example authorization information
{
  "application": {
    "id": "159799960412356608",
    "name": "AIRHORN SOLUTIONS",
    "icon": "f03590d3eb764081d154a66340ea7d6d",
    "description": "",
    "hook": true,
    "bot_public": true,
    "bot_require_code_grant": false,
    "verify_key": "c8cde6a3c8c6e49d86af3191287b3ce255872be1fff6dc285bdb420c06a2c3c8"
  },
  "scopes": [
    "guilds.join",
    "identify"
  ],
  "expires": "2021-01-23T02:33:17.017000+00:00",
  "user": {
    "id": "268473310986240001",
    "username": "discord",
    "avatar": "f749bb0cbeeb26ef21eca719337d20f1",
    "discriminator": "0",
    "global_name": "Discord",
    "public_flags": 131072
  }
}