Skip to main content
The Notion SDK supports two authentication methods: Integration Tokens for internal integrations and OAuth for public integrations that require user authorization.

Integration Tokens

Integration tokens are the simplest way to authenticate when building internal tools or private integrations.

Getting an Integration Token

1

Create an integration

Visit Notion’s integration settings and create a new integration.
2

Copy your token

After creating the integration, copy the Internal Integration Token (starts with secret_).
3

Store securely

Store your token in an environment variable:
.env
NOTION_TOKEN=secret_your_integration_token_here

Using an Integration Token

Pass your token to the Client constructor using the auth option:
const { Client } = require("@notionhq/client")

const notion = new Client({
  auth: process.env.NOTION_TOKEN,
})
Security Best Practice: Never hardcode tokens in your source code. Always use environment variables or a secure secrets management system.

Per-Request Authentication

You can also authenticate on a per-request basis, which is useful when working with multiple tokens:
const notion = new Client() // No auth in constructor

// Pass auth with each request
const response = await notion.pages.retrieve({
  page_id: "page-id",
  auth: process.env.NOTION_TOKEN,
})
Request-level auth parameters override the client-level auth option.

OAuth Authentication

OAuth is required when building public integrations that need to access Notion workspaces on behalf of users.

OAuth Flow Overview

1

User authorization

Redirect users to Notion’s authorization URL with your client ID.
2

Authorization code

Notion redirects back to your app with an authorization code.
3

Exchange for token

Exchange the authorization code for an access token using your client ID and secret.
4

Make API calls

Use the access token to make authenticated API requests.

Setting Up OAuth

First, configure your integration for OAuth in Notion’s integration settings:
  1. Enable OAuth in your integration settings
  2. Add your Redirect URIs
  3. Note your Client ID and Client Secret

Exchanging Authorization Code for Token

After the user authorizes your integration, use the oauth.token() method to exchange the authorization code for an access token:
const { Client } = require("@notionhq/client")

const notion = new Client()

const response = await notion.oauth.token({
  grant_type: "authorization_code",
  code: "authorization-code-from-redirect",
  redirect_uri: "https://your-app.com/callback",
  client_id: process.env.OAUTH_CLIENT_ID,
  client_secret: process.env.OAUTH_CLIENT_SECRET,
})

// Extract the access token
const accessToken = response.access_token
The oauth.token() method requires both client_id and client_secret parameters. These are passed separately and used for Basic Authentication.

Using OAuth Access Tokens

Once you have an access token, use it to authenticate API requests:
const notion = new Client({
  auth: accessToken, // Use the access_token from OAuth
})

// Now you can make API calls on behalf of the user
const users = await notion.users.list({})

OAuth Token Management

The SDK provides methods for managing OAuth tokens:

Token Introspection

Inspect a token to check its validity and metadata:
const introspection = await notion.oauth.introspect({
  token: accessToken,
  client_id: process.env.OAUTH_CLIENT_ID,
  client_secret: process.env.OAUTH_CLIENT_SECRET,
})

if (introspection.active) {
  console.log("Token is valid")
  console.log("Workspace ID:", introspection.workspace_id)
} else {
  console.log("Token is invalid or expired")
}

Token Revocation

Revoke an access token when it’s no longer needed:
await notion.oauth.revoke({
  token: accessToken,
  client_id: process.env.OAUTH_CLIENT_ID,
  client_secret: process.env.OAUTH_CLIENT_SECRET,
})

console.log("Token revoked successfully")
After revoking a token, it cannot be used for API requests. Users will need to re-authorize your integration.

Authentication Headers

The SDK handles authentication headers automatically:

Integration Token Authentication

When using an integration token or OAuth access token, the SDK adds:
Authorization: Bearer secret_your_token_here

OAuth Client Authentication

For OAuth token endpoints (token, introspect, revoke), the SDK uses Basic Authentication:
Authorization: Basic base64(client_id:client_secret)
You don’t need to manually construct these headers—the SDK handles them based on the authentication method you’re using.

Error Handling

Handle authentication errors using the APIErrorCode enum:
const { Client, APIErrorCode, isNotionClientError } = require("@notionhq/client")

try {
  const notion = new Client({ auth: process.env.NOTION_TOKEN })
  const response = await notion.pages.retrieve({ page_id: "page-id" })
} catch (error) {
  if (isNotionClientError(error)) {
    switch (error.code) {
      case APIErrorCode.Unauthorized:
        console.error("Invalid or missing authentication token")
        break
      case APIErrorCode.RestrictedResource:
        console.error("Integration doesn't have access to this resource")
        break
      default:
        console.error("API error:", error.message)
    }
  } else {
    console.error("Unexpected error:", error)
  }
}

Common Authentication Error Codes

Error CodeDescription
unauthorizedMissing or invalid authentication token
restricted_resourceIntegration lacks permission to access the resource
rate_limitedToo many requests; retry with exponential back-off
invalid_requestMalformed request (e.g., missing required OAuth params)
Use TypeScript for better error handling with full type inference for error codes and properties.

Best Practices

Secure Storage

Store tokens in environment variables or secure secret management systems, never in source code.

Token Rotation

Regularly rotate integration tokens and implement token refresh for OAuth flows.

Minimal Permissions

Request only the permissions your integration needs to function.

Error Handling

Always handle authentication errors gracefully and provide clear user feedback.

Complete OAuth Example

Here’s a complete example of implementing OAuth in an Express.js application:
const express = require("express")
const { Client } = require("@notionhq/client")

const app = express()
const notion = new Client()

// Step 1: Redirect user to Notion's authorization page
app.get("/auth", (req, res) => {
  const clientId = process.env.OAUTH_CLIENT_ID
  const redirectUri = "http://localhost:3000/callback"
  const authUrl = `https://api.notion.com/v1/oauth/authorize?client_id=${clientId}&response_type=code&owner=user&redirect_uri=${encodeURIComponent(redirectUri)}`
  
  res.redirect(authUrl)
})

// Step 2: Handle callback and exchange code for token
app.get("/callback", async (req, res) => {
  const code = req.query.code
  
  try {
    const response = await notion.oauth.token({
      grant_type: "authorization_code",
      code: code,
      redirect_uri: "http://localhost:3000/callback",
      client_id: process.env.OAUTH_CLIENT_ID,
      client_secret: process.env.OAUTH_CLIENT_SECRET,
    })
    
    // Store the access token securely (e.g., in a database)
    const accessToken = response.access_token
    
    // Step 3: Use the token to make API calls
    const authenticatedNotion = new Client({ auth: accessToken })
    const users = await authenticatedNotion.users.list({})
    
    res.json({ success: true, users: users.results })
  } catch (error) {
    console.error("OAuth error:", error)
    res.status(500).json({ error: "OAuth failed" })
  }
})

app.listen(3000, () => {
  console.log("Server running on http://localhost:3000")
})

Next Steps

Client Configuration

Explore advanced client configuration options

Error Handling

Learn comprehensive error handling techniques

Build docs developers (and LLMs) love