Skip to main content

What is authorization?

Authorization is the process of granting a connection access to a user’s Notion data. Internal connections use a static API token, while public connections use the OAuth 2.0 protocol.

Internal connection auth flow set-up

To use an internal connection, start by creating your connection in the Creator dashboard. The internal connection will be associated with the workspace of your choice. You are required to be a workspace owner to create a connection.
Once the connection is created, you can update its settings as needed under the Configuration tab and retrieve the installation access token in this tab. The installation access token will be used to authenticate REST API requests. The connection sends the same token in every API request.

Connection permissions

Before a connection can interact with your Notion workspace page(s), the page must be manually shared with the connection. To share a page with a connection, visit the page in your Notion workspace, click the ••• menu at the top right of a page, scroll down to Add connections, and use the search bar to find and select the connection from the dropdown list. Once the connection is shared, you can start making API requests. If the page is not shared, any API requests made will respond with an error.
Never share your installation access tokenYour installation access token is a secret. To keep your connection secure, never store the token in your source code or commit it in version control. Instead, read the token from an environment variable. Use a secret manager or deployment system to set the token in the environment.Learn more: Best Practices for Handling API Keys

Making API requests with an internal connection

Any time your connection is interacting with your workspace, you will need to include the installation access token in the Authorization header with every API request. However, if you are using Notion’s SDK for JavaScript to interact with the REST API, the token is set once when a client is initialized.
GET /v1/pages/b55c9c91-384d-452b-81db-d1ef79372b75 HTTP/1.1
Authorization: Bearer {INTEGRATION_TOKEN}
const { Client } = require("@notionhq/client")

// Initializing a client
const notion = new Client({
	auth: process.env.NOTION_TOKEN,
})

const getUsers = async () => {
	const listUsersResponse = await notion.users.list({})
}
If you are not using the Notion SDK for JavaScript, you will also need to set the Notion-Version and Content-type headers in all of your requests, like so:
JSON
headers: {
  Authorization: `Bearer ${process.env.NOTION_TOKEN}`,
  "Notion-Version": "2026-03-11",
  "Content-Type": "application/json",
},
If you receive an error response from the API, check if the connection has been properly added to the page. If this does not solve the problem, refer to our Status codes page for more information.

Public connection auth flow set-up

A public connection can be installed in any Notion workspace within its installation scope — either any workspace, or a specific set chosen at creation time. Since a public connection is not tied to a single workspace with a single installation access token, public connections instead follow the OAuth 2.0 protocol to authorize a connection to interact with a workspace.

How to make a public connection

Navigate to the Build section of the Creator dashboard and select Public connections to create a new public connection. You will need to fill out the form with your connection details, including your redirect URI(s) under the OAuth configuration section and the connection’s installation scope — either Any workspace or Selected workspaces only. This can’t be changed after creation. The redirect URI is the URI your users will be redirected to after authorizing the public connection. To learn more, read OAuth’s description of redirect URIs.
Marketplace listing details (such as descriptions, categories, and images) are managed separately through the Listings section of the Creator dashboard. Refer to the List on the Marketplace guide to learn more.

Public connection authorization overview

Once your connection has been made public, you can update your connection code to use the public auth flow. As an overview, the authorization flow includes the following steps. Each step will be described in more detail below.
1
Navigate the user to the connection’s authorization URL. This URL is provided in the Creator dashboard.
2
After the user selects which workspace pages to share, Notion redirects the user to the connection’s redirect URI and includes a code query parameter. The redirect URI is the one you specified in your Creator dashboard.
3
You will make a POST request to create an access token , which will exchange the temporary code for an access token.
4
The Notion API responds with an access token and some additional information.
5
You will store the access token for future API requests. View the API reference docs to learn about available endpoints.

Step 1 - Navigate the user to the connection’s authorization URL

After you’ve created your public connection in the Creator dashboard, you will be able to access the connection’s secrets in the Configuration tab. Similarly to the internal connections, these values should be protected and should never be included in source code or version control.
As an example, your .env file using these secrets could look like this:
#.env

OAUTH_CLIENT_ID=<your-client-id>
OAUTH_CLIENT_SECRET=<your-client-secret>
NOTION_AUTH_URL=<your-auth-url>
To start the authorization flow for a public connection, you need to direct the prospective user to the authorization URL. To do this, it is common to include a hyperlink in the connection app that will be interacting with the Notion REST API. For example, if you have an app that will allow users to create new Notion pages for their workspace(s), you will first need them to first visit the authorization URL by clicking on the link. The following example shows an authorization URL made available through 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:
ParameterDescriptionRequired
client_idAn identifier for your connection, found in the connection settings.
redirect_uriThe URL where the user should return after granting access.
response_typeAlways use code.
ownerAlways use user.
stateIf 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.
Once the authorization URL is visited, the user will be shown a prompt that varies depending on whether or not the connection comes with a Notion template option.

Prompt for a standard connection with no template option (Default)

In the standard connection permissions flow, a prompt describes the connection capabilities, presented to the user as what the connection would like to be able to do in the workspace. A user can either select pages to grant the connection access to, or cancel the request.
If the user presses Cancel, they will be redirected to the redirect URI with and error query param added.
www.example.com/my-redirect-uri?error=access_denied&state=
You can use this errorquery parameter to conditionally update your app’s state as needed. 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 connection 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 a connection.
Users can select which pages to give the connection access to, including both private and public pages available to them. Parent pages can be selected to quickly provide access to child pages, as giving access to a parent page will provide access to all available child pages. Users can return to this view at a later time to update access settings if circumstances change. If the user clicks Allow access, they are then redirected to the redirect_uri with a temporary authorization code. If the user denies access, they are redirected to the redirect_uri with an error query parameter. If the user clicks Allow access and the rest of the auth flow is not completed, the connection will not have access to the pages that were selected.

Prompt for a connection with a Notion template option

Public connections offer the option of providing a public Notion page to use as a template during the auth flow. To add a template to your workspace, complete the following steps:
  • Choose a public page in your workspace that you want users to be able to duplicate.
  • Navigate to your Creator dashboard and open the Configuration tab, then scroll to the Basic information section.
  • Scroll to the bottom of your distribution settings and add the URL of the Notion page you selected to the Notion URL for optional template input.
Once this URL is added, your auth flow prompt appearance will be updated. Going back to your prompt view, if the connection offers a Notion template option, the first step in the permissions flow will describe the connection capabilities. This is presented to the user as what the connection would be able to do in the workspace, and it prompts the user to click Next.
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 connection.
If the user chooses to duplicate the template, then the following happens automatically:
  • The connection 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 connection.
If the user chooses to select pages to share with the connection, then they continue to the page picker interface that’s part of the prompt for a standard connection.
After a user authorizes a public connection, only that user is able to interact or share pages and databases with the connection. Unlike internal connections, if multiple members in a workspace want to use a public connection, each prospective user needs to individually follow the auth flow for the connection.
User authorization failures User authorization failures can happen. If a user chooses to Cancel the request, then a failure is triggered. Build your connection to handle these cases gracefully, as needed. In some cases, Notion redirects the user to the redirect_uri that you set up when you created the public connection, 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 connection’s redirect URI and includes a code parameter

When you first created the public connection, you specified a redirect URI. If the user follows the prompt to Allow access for the connection, then Notion generates a temporary code and sends a request to the redirect URI with the following information in the query string:
ParameterDescriptionRequired
codeA temporary authorization code.
stateThe value provided by the connection when the user was prompted for access.
To complete the next set, you will need to retrieve the code query parameter provided in the redirect. How you retrieve this value will vary depending on your app’s tech stack. In a React component, for example, the query parameters are made available through the useRouter() hook:
export default function AuthRedirectPage() {
  const router = useRouter();
  const { code } = router.query;
  ...
}

Step 3 - Send the code in a POST request to the Notion API

The connection needs to exchange the temporary code for an access_token. To set up this step, retrieve the code from the redirect URI. Next, you will need to send the code as part of a POST request to Notion’s token endpoint: https://api.notion.com/v1/oauth/token. This endpoint is described in more detail in the API reference docs for creating a token. The request is authorized using HTTP Basic Authentication. The credential is a colon-delimited combination of the connection’s CLIENT_ID and CLIENT_SECRET, like so:
CLIENT_ID:CLIENT_SECRET
You can find both of these values in the Creator dashboard. 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:
FieldTypeDescriptionRequired
"grant_type"stringAlways use "authorization_code".
"code"stringThe temporary authorization code received in the incoming request to the "redirect_uri".
"redirect_uri"stringThe "redirect_uri" that was provided in the Authorization step.✅/❌*

* If the redirect URI was supplied as a query param in the Authorization URL, this field is required. If there are more than one redirect URIs included in your connection settings, this field is required. Otherwise, it is not allowed. Learn more in the Create a token page.
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"}
The Node-equivalent of this example would look something like this:
...
const clientId = process.env.OAUTH_CLIENT_ID;
const clientSecret = process.env.OAUTH_CLIENT_SECRET;
const redirectUri = process.env.OAUTH_REDIRECT_URI;

// encode in base 64
const encoded = btoa(`${clientId}:${clientSecret}`);

const response = await fetch("https://api.notion.com/v1/oauth/token", {
	method: "POST",
	headers: {
	Accept: "application/json",
	"Content-Type": "application/json",
	Authorization: `Basic ${encoded}`,
},
	body: JSON.stringify({
		grant_type: "authorization_code",
		code: "your-temporary-code",
		redirect_uri: redirectUri,
	}),
});
...

Step 4 - Notion responds with an access_token , refresh_token, and additional information

Notion responds to the request with an access_token, refresh_token, and additional information. The access_token will be used to authenticate subsequent Notion REST API requests. The refresh_token will be used to refresh the access token, which generates a new access_token. The response contains the following JSON-encoded fields:
FieldTypeDescriptionNot null
"access_token"stringAn access token used to authorize requests to the Notion API.
"refresh_token"stringA refresh token used to generate a new access token
"bot_id"stringAn identifier for this authorization.
"duplicated_template_id"stringThe 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 connection. If the developer didn’t provide a template for the connection, then the value is null.
"owner"objectAn object containing information about who can view and share this connection. A user object is returned, representing the user who authorized the connection.
"workspace_icon"stringA URL to an image that can be used to display this authorization in the UI.
"workspace_id"stringThe ID of the workspace where this authorization took place.
"workspace_name"stringA human-readable name that can be used to display this authorization in the UI.
Token request failures If something goes wrong when the connection 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 connection stores the access_token and refresh_token for future requests

You need to set up a way for your connection to store both the access_token and refresh_token that it receives. The access_token is used to make authorized requests to the Notion API, and the refresh_token is used to generate a new access_token. Tips for storing and using token access
  • Setting up a database is a typical solution for storing access tokens. If you’re using a database, then build relations between an access_token, refresh_token, and the corresponding Notion resources that your connection accesses with that token. For example, if you store a Notion database or page ID, relate those records with the correct access_token that you use to authorize requests to read or write to that database or page, and the refresh_token for ongoing token lifecycle support..
  • Store all of the information that your connection receives with the access_token and refresh_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 tokens should act as your primary key when storing information.

Step 6 - Refreshing an access token

Refreshing an access token will generate a new access token and a new refresh token. You will need to send the refresh_token provided from Step 4 as part of a POST request to Notion’s token endpoint: https://api.notion.com/v1/oauth/token. This endpoint is described in more detail in the API reference docs for refreshing a token. The request is authorized using HTTP Basic Authentication. The credential is a colon-delimited combination of the connection’s CLIENT_ID and CLIENT_SECRET, like so:
CLIENT_ID:CLIENT_SECRET
You can find both of these values in the Creator dashboard. 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:
FieldTypeDescriptionRequired
"grant_type"stringAlways use "refresh_token".
"refresh_token"stringThe "refresh_token" returned in the Authorization step.
The following is an example request to exchange the refresh_token for a new access token and new refresh token
POST /v1/oauth/token HTTP/1.1
Authorization: Basic "$CLIENT_ID:$CLIENT_SECRET"
Content-Type: application/json

{"grant_type":"refresh_token","refresh_token":"nrt_4991090011501Ejc6Xn4sHguI7jZIN449mKe9PRhpMfNK"}
The Node-equivalent of this example would look something like this:
...
const clientId = process.env.OAUTH_CLIENT_ID;
const clientSecret = process.env.OAUTH_CLIENT_SECRET;

// encode in base 64
const encoded = btoa(`${clientId}:${clientSecret}`);

const response = await fetch("https://api.notion.com/v1/oauth/token", {
	method: "POST",
	headers: {
	Accept: "application/json",
	"Content-Type": "application/json",
	Authorization: `Basic ${encoded}`,
},
	body: JSON.stringify({
		grant_type: "refresh_token",
		refresh_token: "your-refresh-token",
	}),
});
...