Authorization
This guide describes the authorization flows for internal and public Notion integrations.
Authorization is the process of granting an integration access to a user’s Notion data. That process varies depending on whether or not the integration is public or internal. For specifics on the differences between public and internal integrations, refer to the getting started overview.
Read on to learn how to set up the auth flows for internal and public integrations.
Set up the auth flow for an internal integration
Before you begin: share a page or database with your internal integration
A user needs to add an internal integration to a page or database before the integration can be useful. During development, be sure to share a page or database with your integration to test it.
An internal integration is used by only one Notion workspace, so the integration sends the same bearer token in every API request. You can find the integration token in the integration’s settings. Visit notion.so/my-integrations, and click View integration
.

Find the integration token in the integration's settings.
Keep your token secret
This token is a secret. To keep your integration secure, never store the token in your source code or commit it in version control. Instead, read the the token from an environment variable. Use a secret manager or deployment system to set the token in the environment.
In your integration code, include the token in the Authorization
header with every API request, as in the following example:
GET /v1/pages/b55c9c91-384d-452b-81db-d1ef79372b75 HTTP/1.1
Authorization: Bearer {INTEGRATION_TOKEN}
After an internal integration is created in the workspace, it needs to be added to a page or database. Then, all users with access to the page can interact with the integration. Users don’t need to install the integration individually.
Set up the auth flow for a public integration
By default, any Notion user, in any workspace, can use a public integration.
In a Notion for Enterprise workspace, a workspace admin needs to approve a public integration before it can be added to the workspace.
Public integrations follow the OAuth 2.0 protocol to access multiple workspaces. The authorization flow includes the following steps:
- Navigate the user to the integration’s authorization URL
- Notion redirects the user to the integration’s
redirect_uri
and includes acode
parameter - The integration sends the
code
in a POST request to the Notion API - Notion responds with an
access_token
and some additional information - The integration stores the
access_token
for future requests
The rest of this guide details each step in the flow, including relevant implementation details.
Step 1: Navigate the user to the integration’s authorization URL
After you input the OAuth Domain & URIs information in a public integration’s settings page and click "Submit ->"
, an Authorization URL
field is added to the settings page under "Secrets"
.

The Authorization URL field populates after a public integration is submitted
To start the authorization flow for a public integration, you need to direct the prospective user to this URL. You can send the user to it via a hyperlink or through a redirect from another request.
The following example adds an authorization URL to a hyperlink:
<a href="https://api.notion.com/v1/oauth/authorize?owner=user&client_id=463558a3-725e-4f37-b6d3-0889894f68de&redirect_uri=https%3A%2F%2Fexample.com%2Fauth%2Fnotion%2Fcallback&response_type=code">Add to Notion</a>
The URL begins with https://api.notion.com/v1/oauth/authorize
and has the following parameters:
Parameter | Description | Required |
---|---|---|
client_id | An identifier for your integration, found in the integration settings. | ✅ |
redirect_uri | The URL where the user should return after granting access. | ✅ |
response_type | Always use code . | ✅ |
owner | Always use user . | ✅ |
state | If the user was in the middle of an interaction or operation, then this parameter can be used to restore state after the user returns. It can also be used to prevent CSRF attacks. |
The authorization URL displays a prompt that varies depending on whether or not the integration comes with a Notion template option.
Prompt for a standard integration (no template option)
In the standard integration permissions flow, a prompt describes the integration capabilities, presented to the user as what the integration would like to be able to do in the workspace. A user can either select pages to grant the integration access to, or cancel the request.

Prompt for authorizing a standard integration (no template option)
If the user opts to Select pages
, then a page picker interface opens. A user can search for and select pages and databases to share with the integration from the page picker.
The page picker only displays pages or databases to which a user has full access, because a user needs full access to a resource in order to be able to share it with an integration.

Page picker interface
If the user clicks Allow access
, then they are redirected to the redirect_uri
with a temporary authorization code (code
). If the user denies access, then they are redirected to the redirect_uri
with an error (error
).
Prompt for an integration with a Notion template option
If the integration offers a Notion template option, then the first step in the permissions flow describes the integration capabilities, presented to the user as what the integration would like to be able to do in the workspace, and prompts the user to click Next
.

Prompt for an integration with a Notion template option
In the next step, a user can either choose to duplicate the template that you provided, or to select existing pages to share with the integration.

A user can select to duplicate a template or to share existing pages with the integration
If the user chooses to duplicate the template, then the following happens automatically:
- The integration is added to the user’s workspace.
- The template is duplicated as a new page in the workspace.
- The new page is shared with the integration.
If the user chooses to select pages to share with the integration, then they continue to the page picker interface that’s part of the prompt for a standard integration.
After a user installs a public integration, only that user is able to interact or share pages and databases with the integration. Unlike internal integrations, if multiple members in a workspace want to use a public integration, each prospective user needs to individually follow the auth flow for the integration.
User authorization failures
User authorization failures are common. If a user chooses to Cancel
the request, then a failure is triggered. Build your integration to handle these cases gracefully.
In some cases, Notion redirects the user to the redirect_uri
that you set up when you created the public integration, along with an error
query parameter. Notion uses the common error codes in the OAuth specification. Use the error code to create a helpful prompt for the user when they’re redirected here.
Step 2: Notion redirects the user to the integration’s redirect_uri
and includes a code
parameter
redirect_uri
and includes a code
parameterWhen you created the public integration, you specified a redirect_uri
. If the user follows the prompt to Allow access
for the integration, then Notion generates a temporary code
and sends a request to the redirect_uri
with the following information in the query string:
Parameter | Description | Required |
---|---|---|
code | A temporary authorization code. | ✅ |
state | The value provided by the integration when the user was prompted for access. |
Step 3: The integration sends the code
in a POST request to the Notion API
code
in a POST request to the Notion APIThe integration needs to exchange the temporary code
for an access_token
.
To set up this step, in your integration implementation retrieve the code
from the redirect_uri
. Then, send the code
as part of a POST request to Notion’s token URL: https://api.notion.com/v1/oauth/token
.
The request is authorized using HTTP Basic Authentication. The credential is a colon-delimited combination of the integration’s CLIENT_ID
and CLIENT_SECRET
: CLIENT_ID:CLIENT_SECRET
. You can find these values in the integration’s settings. Visit notion.so/my-integrations, and click View integration
.
Note that in HTTP Basic Authentication, credentials are base64
encoded before being added to the Authorization
header.
The body of the request contains the following JSON-encoded fields:
Field | Type | Description | Required |
---|---|---|---|
"grant_type" | string | Always use "authorization_code" . | ✅ |
"code" | string | The temporary authorization code received in the incoming request to the "redirect_uri" . | ✅ |
"redirect_uri" | string | The "redirect_uri" that was provided in the Authorization step. | If you added multiple redirect URIs in the integration settings, then you need to specify a "redirect_uri" in the request body. |
The following is an example request to exchange the authorization code for an access token:
POST /v1/oauth/token HTTP/1.1
Authorization: Basic "$CLIENT_ID:$CLIENT_SECRET"
Content-Type: application/json
{"grant_type":"authorization_code","code":"e202e8c9-0990-40af-855f-ff8f872b1ec6", "redirect_uri":"https://example.com/auth/notion/callback"}
Step 4: Notion responds with an access_token
and some additional information
access_token
and some additional informationNotion responds to the request with an access_token
and additional information. The response contains the following JSON-encoded fields:
Field | Type | Description | Not null |
---|---|---|---|
"access_token" | string | An access token used to authorize requests to the Notion API. | ✅ |
"bot_id" | string | An identifier for this authorization. | ✅ |
"duplicated_template_id" | string | The ID of the new page created in the user’s workspace. The new page is a duplicate of the template that the developer provided with the integration. If the developer didn’t provide a template for the integration, then the value is null . | |
"owner" | object | An object containing information about who can view and share this integration. { "workspace": true } is returned for installations of workspace-level tokens. For user level tokens, a user object is returned. | ✅ |
"workspace_icon" | string | A URL to an image that can be used to display this authorization in the UI. | |
"workspace_id" | string | The ID of the workspace where this authorization took place. | ✅ |
"workspace_name" | string | A human-readable name that can be used to display this authorization in the UI. |
Token request failures
If something goes wrong when the integration attempts to exchange the code
for an access_token
, then the response contains a JSON-encoded body with an "error"
field. Notion uses the common error codes from the OAuth specification.
Step 5: The integration stores the access_token
for future requests
access_token
for future requestsYou need to set up a way for your integration to store all of the access_token
's that it receives, so that the integration can then choose a bearer token from storage based on the user it’s acting on behalf of for each request to the Notion API.
Tips for storing and using token access
- Setting up a database is a typical solution for storing
access_token
's. If you’re using a database, then build relations between anaccess_token
and the corresponding Notion resources that your integration accesses with that token. For example, if you store a Notion database or page ID, relate those records with the correctaccess_token
that you use to authorize requests to read or write to that database or page. - Store all of the information that your integration receives with the
access_token
. You never know when your UI or product requirements might change and you’ll need this data. It's really hard (or impossible) to send users to repeat the authorization flow to generate the information again. - The
bot_id
returned along with your token should act as your primary key when storing information.
Updated 4 months ago