# How to Get a Notion API Key and Find the IDs Your Requests Need

> Which Notion API key to create in 2026, step by step: personal access token, internal connection or OAuth. Plus where page, database and data source IDs hide, the errors you’ll hit first, rate limits and keeping the key safe.

- Source: https://ivgraph.com/journal/notion-api-key/
- Author: [Andrii Danylchenko](https://ivgraph.com/team/#Andrii-Danylchenko), Founder / CTO, IVGraph
- Published: 2026-10-30
- Type: Guide
- About: Notion ([Wikidata Q60747998](https://www.wikidata.org/wiki/Q60747998)); API key ([Wikidata Q4781495](https://www.wikidata.org/wiki/Q4781495))
- Mentions: Personal access token ([Wikidata Q96381162](https://www.wikidata.org/wiki/Q96381162)); OAuth ([Wikidata Q743238](https://www.wikidata.org/wiki/Q743238)); Universally unique identifier ([Wikidata Q195284](https://www.wikidata.org/wiki/Q195284)); Rate limiting ([Wikidata Q121366924](https://www.wikidata.org/wiki/Q121366924)); Model Context Protocol ([Wikidata Q133436854](https://www.wikidata.org/wiki/Q133436854)); [IVGraph](https://ivgraph.com/)

## TL;DR

**For your own scripts, create a personal access token:** turn on Developer Mode in Notion’s settings, open **Personal access tokens** in the sidebar’s developer section and click **New token**. For a team automation, a workspace owner creates an **internal connection**, copies its API token and shares pages with it. Send the token as `Authorization: Bearer …` together with `Notion-Version: 2026-03-11`. A page or database ID is the 32 characters at the end of its link, before any `?v=`; the free [Notion ID finder](https://ivgraph.com/tools/notion-id-finder/) pulls it out for you.

A Notion API key is the token your code sends with every request to Notion’s API. In 2026 there are three kinds: a personal access token that acts as you, an internal connection token for team automations, and OAuth for apps that other people install. Below: how to create each one, give it access to your pages, find the page, database and data source IDs your requests need, and fix the errors almost everyone hits on the first try.

> **Checked October 27, 2026.** Notion now calls integrations “connections”, keeps its developer portal inside the app behind Developer Mode, and added personal access tokens on May 12, 2026. Tutorials that send you to “My integrations” describe the earlier setup. Old developer portal links redirect to the new tools, and tokens that start with `secret_` still work.

## Which Notion API key do you need?

Notion’s developer docs list three ways to authenticate. They differ in whose permissions the token carries, what it can see and who is allowed to create it.

|  | Personal access token | Internal connection | Public (OAuth) |
| --- | --- | --- | --- |
| Acts as | You | Its own bot user | Each user who connects it |
| Can reach | Everything you can open in one workspace | Only pages and databases shared with it | The pages each user picks when signing in |
| Who creates it | Full members, if the plan’s policy allows; owners always | Workspace owners | Its developer; each user approves access |
| Use it for | Your scripts, CLI work, testing, tools you trust | Team automations, syncs, internal dashboards | Apps other people install |

A quick way to choose:

- **A script, notebook or tool only you use:** a personal access token. Nothing to share, working in two minutes.
- **An automation the team relies on:** an internal connection. It keeps working when the person who set it up leaves, and it sees only the pages you share with it.
- **An app other people install:** a public connection with OAuth. Never ask your users to paste a token.
- **Claude, ChatGPT, Cursor or another AI app:** usually no key at all. Notion’s hosted MCP server signs you in with OAuth; our [Notion MCP guide](https://ivgraph.com/journal/notion-mcp/) covers the setup.

## Option 1: create a personal access token

A personal access token (PAT) acts as you, with your workspace membership and page permissions. Notion’s own API quickstart starts with one, because there is nothing to set up besides the token.

1. **Turn on Developer Mode.** In Notion, open **Settings**, search for **Developer Mode** and switch it on. It’s a local setting: the browser and the desktop app each need it turned on, and it isn’t available on mobile.
2. **Open the token list.** A developer section appears in the sidebar. Open **Personal access tokens** and click **New token**. The direct link is [notion.so/developers/tokens](https://www.notion.so/developers/tokens).
3. **Name it and set its scope.** Pick the **Notion API** capability (add **Workers** only if you deploy Notion Workers) and an expiration: 7, 30, 90 or 180 days, or 1 year, the default. If a workspace picker appears, choose the workspace: a token belongs to one workspace.
4. **Click Create token and copy the value right away.** Notion shows it once and never again, not even to admins. Put it in a password manager or your project’s secret store.

New tokens start with `ntn_`. Notion switched from `secret_` on September 25, 2024, and older tokens keep working. Don’t check the prefix in your code, though: Notion says the format may change and asks developers to treat tokens as opaque strings.

### Can’t create a token?

Guests and restricted members can’t create PATs at all. For everyone else, the plan sets who may create one with Notion API access:

| Plan | Who can create PATs by default | Can admins change it? |
| --- | --- | --- |
| Free | Workspace owners only | No |
| Plus | All workspace members | No |
| Business | Workspace owners only | Yes: owners only, or all members |
| Enterprise | Workspace owners and selected groups | Yes: owners only, owners and selected groups, or all members |

On Business and Enterprise, ask a workspace owner to allow it under **Settings → Connections**. If an admin later narrows the policy, tokens you already have stop working and return `unauthorized`.

### What a PAT can reach

Everything you can open in that workspace: pages, databases, comments and files. Pages don’t need to be shared with it. That is what makes PATs quick, and also what makes a leaked PAT expensive. Notion’s docs describe no read-only PAT: the Notion API capability reads, creates and updates. If a job only needs to read a few pages, an internal connection with only **Read content** is the safer key. When you lose access to a page or leave the workspace, the token loses it too.

## Option 2: create an internal connection token

An internal connection is a bot user that belongs to one workspace. Use it for automations the team depends on, or for any tool that should see a few pages rather than everything you can open. Older guides call it an “internal integration”, and its token the “internal integration secret” or “integration token”.

1. **Check that you’re a workspace owner.** Only owners can create connections. If you aren’t one, ask an owner, or create a free personal workspace to experiment in, as Notion’s FAQ suggests.
2. **Create the connection.** In the developer section of the sidebar, or at [notion.so/developers](https://www.notion.so/developers), open **Internal connections**, click **Create a new connection**, enter a name and choose the workspace. A connection can’t move to another workspace later; you’d create a new one there.
3. **Copy the API token.** Open the connection’s **Configuration** tab and copy the **API token**. Owners can also reach it from **Settings → Connections**: the **•••** menu next to the connection has an option to retrieve the token.
4. **Set capabilities** on the same tab (the list is below).
5. **Give it pages.** A new connection sees nothing, and every request fails until you share something. In the **Content access** tab, click **Edit access** and select pages and databases. Or open a page in Notion, click **•••** in the top-right corner, find **Connections**, choose **Add connection** and search for yours. Sharing a page also shares its child pages.

Capabilities limit what the token can do, even on pages it can see. Notion’s docs recommend asking for the minimum:

- **Read content, Update content, Insert content**, in any combination. An export needs only Read content. A form that adds rows needs only Insert content. A script that sets a status needs only Update content.
- **Read comments, Insert comments.**
- **User information:** none, without email addresses, or with email addresses.

Because the connection is its own user, its access doesn’t depend on people. If the teammate who shared a page leaves the workspace, the connection keeps that page. Every workspace owner can see all internal connections, including ones that others created.

## Option 3: OAuth for apps other people use

If you’re building a product, don’t ask users to paste tokens. Create a public connection instead: each user signs in through Notion’s OAuth window and picks the pages your app may see. Exchange the OAuth code for a token on a server you control. The client secret must never reach a browser, and Notion’s OAuth token endpoints are deliberately closed to browser code. IVGraph connects this way: you choose pages in Notion’s window and never handle a key.

## Test the key with one request

Keep the token in an environment variable rather than in your code. On macOS or Linux:

```
export NOTION_API_KEY="ntn_…"
```

In Windows PowerShell it’s `$env:NOTION_API_KEY = "ntn_…"`. Then ask Notion who the token belongs to:

```
curl https://api.notion.com/v1/users/me \
  -H "Authorization: Bearer $NOTION_API_KEY" \
  -H "Notion-Version: 2026-03-11"
```

A working key returns a user of type `bot`; for a personal access token, its owner is you. Next, see what the key can reach. Search without a query returns every page or data source the token can access. This filter keeps only the data sources of your databases, each with its own ID and its parent database’s ID:

```
curl -X POST https://api.notion.com/v1/search \
  -H "Authorization: Bearer $NOTION_API_KEY" \
  -H "Notion-Version: 2026-03-11" \
  -H "Content-Type: application/json" \
  -d '{"filter": {"property": "object", "value": "data_source"}}'
```

Every request needs the `Notion-Version` header; `2026-03-11` is the latest version. In JavaScript, the official SDK takes the token once and retries rate-limited requests for you. Its current release sends `2025-09-03` by default, so set the version explicitly:

```
import { Client } from "@notionhq/client";

const notion = new Client({
  auth: process.env.NOTION_API_KEY,
  notionVersion: "2026-03-11",
});

const me = await notion.users.me({});
console.log(me.type, me.name);
```

## Where to find a page, database or block ID

Every page, database and block in Notion has an ID: a UUID of 32 hexadecimal characters. Links show it without dashes, and the API accepts it with or without them. With dashes it’s grouped 8-4-4-4-12, so `1429989fe8ac4effbc8f57f56486db54` becomes `1429989f-e8ac-4eff-bc8f-57f56486db54`.

### The quickest way: Developer Mode

With Developer Mode on, a developer bar at the bottom of Notion shows the ID of the page, block, database or data source you’re looking at. Right-click a block or a database and open the **Developer** section to copy an ID directly. Like the setting itself, this works in the browser and the desktop app, not on mobile.

### From a link

Copy a page’s link with **Share → Copy link**, or a block’s link from its **⋮⋮** menu with **Copy link to block**. Each kind of link keeps the ID in a fixed place:

```
Page
https://www.notion.so/acme/Launch-plan-1429989fe8ac4effbc8f57f56486db54

Database (full-page view)
https://www.notion.so/acme/248104cd477e80fdb757e945d38000bd?v=8f1c2a7e3b5d4c6e9a0b1c2d3e4f5a6b

Block
https://www.notion.so/acme/Launch-plan-1429989fe8ac4effbc8f57f56486db54#5c7d2e9a1b3f4c8d9e0f1a2b3c4d5e6f
```

| In the link | What it is | Where you use it |
| --- | --- | --- |
| The last 32 characters, after the page title | Page ID | `/v1/pages/{page_id}`; also the block ID of the page’s content |
| The 32 characters before `?v=` | Database ID | `/v1/databases/{database_id}` |
| The value after `?v=` | View ID | View endpoints only; never a database ID |
| The value after `p=`, when a row is open in a side peek | That row’s page ID | `/v1/pages/{page_id}` |
| The 32 characters after `#` | Block ID | `/v1/blocks/{block_id}` |

Since June 2026, page links in API responses use another form, `https://app.notion.com/p/…`. The ID is still the last 32 characters, and older notion.so links keep working.

Two cases trip people up:

- **Inline databases.** An inline database lives inside a page, so the address bar shows the page’s ID. Open the database as a full page first, then copy the link.
- **Linked views.** If a database’s view menu offers **Open source database**, you’re looking at a linked view. The API can’t read linked databases: open the source, use its ID and share the source with your connection.

Inside a database, a formula property with `id()` shows each row’s page ID without dashes, and `id(prop("Project").first())` gives the ID of a related page. That helps when an automation tool asks you for row IDs.

### The data source ID, the one that isn’t in the link

Since API version `2025-09-03`, a database is a container for one or more data sources. The data source holds the properties and the rows, so querying rows and creating pages in a database go through endpoints under `/v1/data_sources/`. Those take a data source ID, not the database ID from the link. Three ways to get it:

- **In Notion:** open the database’s settings menu, go to **Manage data sources** and click **Copy data source ID**. With Developer Mode on, right-clicking the database works too.
- **From the API:** retrieve the database and read its `data_sources` list.
- **From search:** the `data_source` filter shown above.

```
curl https://api.notion.com/v1/databases/248104cd477e80fdb757e945d38000bd \
  -H "Authorization: Bearer $NOTION_API_KEY" \
  -H "Notion-Version: 2026-03-11"

# the part of the response you need
{
  "object": "database",
  "id": "248104cd-477e-80fd-b757-e945d38000bd",
  "data_sources": [
    { "id": "248104cd-477e-80af-bc30-000bd28de8f9", "name": "Grocery list" }
  ]
}
```

Then query the rows with `POST /v1/data_sources/248104cd-477e-80af-bc30-000bd28de8f9/query`. Most databases have a single data source today, but don’t build on that. Notion’s upgrade guide warns that once someone adds a second source to a database, connections still on an older API version stop finding it in search, and their reads, writes and queries on it fail.

### In code

The official JavaScript SDK does this parsing for you: `extractPageId`, `extractDatabaseId` and `extractBlockId` arrived in September 2025. They return the ID with dashes and prefer the ID in the path over query parameters, so a database link doesn’t hand you the view ID by mistake.

```
import { extractDatabaseId } from "@notionhq/client";

extractDatabaseId("https://www.notion.so/acme/248104cd477e80fdb757e945d38000bd?v=8f1c2a7e3b5d4c6e9a0b1c2d3e4f5a6b");
// "248104cd-477e-80fd-b757-e945d38000bd"
```

## Fix the errors you’ll hit first

Error responses carry an HTTP status, a `code` and a readable `message`. These six cover nearly every first-day problem:

| Error and message | Usual cause | Fix |
| --- | --- | --- |
| 401 `unauthorized` `API token is invalid.` | A typo or a missing `Bearer`; an expired, revoked or refreshed token; a workspace that stopped allowing your PATs | Copy the token again and send it in the `Authorization` header as `Bearer <token>`. Create a new PAT if the old one expired |
| 404 `object_not_found` `Could not find database…` | The page isn’t shared with the connection; a view ID instead of the database ID; a linked database; a page in another workspace | Share the page or its parent with the connection; take the ID before `?v=`; use the source database |
| 403 `restricted_resource` `API token does not have…` | A missing capability, such as editing with only Read content, or a Free workspace over its block limit | Add the capability on the Configuration tab; the message names the cause |
| 400 `validation_error` `body failed validation…` | A malformed ID, a wrong property name or type, or a value over a size limit | Read the message: it names the field |
| 400 `missing_version` `Notion-Version header…` | No `Notion-Version` header | Add `Notion-Version: 2026-03-11` |
| 429 `rate_limited` `You have been rate limited…` | Too many requests | Wait for `Retry-After`; the limits are below |

Three more problems that don’t look like errors:

- **Relation properties come back empty.** Share the related database with the connection too. Otherwise the properties based on that relation are left out of the response. How relations work is explained in our [Notion relations guide](https://ivgraph.com/journal/notion-database-relations/).
- **Search returns nothing.** A token only finds what it can access. For a connection, that’s the pages shared with it and their children.
- **A request that worked yesterday fails today.** A PAT may have expired, an admin may have revoked it, or someone refreshed the connection’s token. For internal and public connections, the **Health** tab in the developer tools lists the last 24 hours of requests with their error codes and rate-limit hits.

## Notion API rate limits in 2026

Notion changed its limits on September 9, 2026. Older answers describe one flat average of three requests per second with waits of up to 15 minutes. Now the limit depends on the plan and is counted per minute. Here is what Notion’s request limits page says:

| Limit | Value |
| --- | --- |
| Per connection, Business and Enterprise | 600 requests per minute, an average of 10 per second |
| Per connection, all other plans, Free and Plus included | 180 requests per minute, an average of 3 per second |
| Per workspace | One budget shared by all of the workspace’s connections, scaled to the plan |
| Query a data source | About 3 requests per second per connection |
| One request | Up to 1,000 blocks and 500 KB |
| Text, links, lists | 2,000 characters per text item or URL; up to 100 items per array, relation or multi-select in one request |
| One page of results | Up to 100 items (`page_size`); continue with `start_cursor` |

The per-connection budget resets every 60 seconds and can be spent in a single burst. Over a limit, Notion answers with HTTP 429, the code `rate_limited` and a `Retry-After` header in seconds. For the per-connection limit that wait never exceeds 60 seconds; the shared workspace limit can ask for longer. The field `additional_data.rate_limit_reason` says which limit you hit. A 529 `service_overload` means Notion itself is busy: treat it like a 429. Notion MCP tool calls draw from the same limits.

How to stay inside them:

1. Send requests through one queue, not through parallel workers that each retry on their own.
2. On 429 or 529, wait for `Retry-After` and retry. If it happens again, back off exponentially with a little jitter, and cap the number of attempts.
3. Don’t retry most 400 errors: fix the request. A 429 with the reason `public_api_request_blocked` won’t clear by waiting either; contact Notion support.
4. Before repeating a write that returned 503, read `additional_data.retry_guidance`. The change may already be saved, and repeating it would apply it twice.

## Keep the key safe

Anyone who has the token can do whatever it allows. For a PAT that’s everything you can open; for a connection, every page shared with it.

- **Store it outside your code:** an environment variable for local work, with `.env` listed in `.gitignore`, and a secret manager in production.
- **Never put it in a web page.** The Notion API accepts requests from browser code, so a token embedded in a page that others can open is usable by anyone who views its source.
- **Don’t paste it into chats, support tickets or issue trackers**, and that includes conversations with AI assistants.
- **Use one token per tool and per environment**, so revoking one doesn’t break the rest.
- **Grant the minimum.** For a third-party tool, an internal connection that sees only the pages you share is safer than a PAT that sees everything you can. Give experimental PATs a short expiration.
- **Turn on secret scanning** in your repository and CI. The `ntn_` prefix exists so that scanners can recognize Notion tokens.

If a token leaks, act in this order. In Notion, open **Settings → Connections → Develop or manage connections**, find the connection or token, and click **Refresh** for an internal connection or revoke the PAT. Replace the old value everywhere it was used. Then check recent changes to your pages and databases, and look for connections you don’t recognize. Workspace admins can revoke any member’s PAT from **Settings → Connections** as well, although they can never see or copy the token itself.

## When you don’t need an API key

- **AI apps.** Claude, ChatGPT, Cursor, Codex and VS Code connect to Notion’s hosted MCP server through OAuth, so there is no key to create. Our [Notion MCP setup helper](https://ivgraph.com/tools/notion-mcp-setup/) gives you the exact config for each app. Two exceptions: on Business and Enterprise, a workspace owner can create an MCP token for clients that can’t open a sign-in window, and the open-source, self-hosted MCP server takes an API token, although Notion no longer actively maintains it.
- **Apps that use Notion sign-in.** A tool that opens Notion’s window and asks you to pick pages uses a public connection, and you never see a token.
- **Seeing how your workspace connects.** People often collect page and database IDs to map relations by hand. [IVGraph](https://ivgraph.com/) connects through Notion’s OAuth window and draws every page, mention and database relation as an interactive 3D graph. It’s free up to 500 nodes.

If an agent should read that graph, IVGraph has a read-only [API for agents](https://ivgraph.com/docs/v1-llm-api/) with its own service token on the Pro and Max plans: search, page content, neighbors and paths between pages. There’s no IVGraph MCP server yet.

![A page selected in IVGraph’s 3D graph: its properties, a link back to Notion and 47 connections grouped by database](https://ivgraph.com/assets/img/shots-2026-10/graph-node-card-crop-1440w.webp)

*In IVGraph each page keeps its link back to Notion, and its relations are grouped by database. No IDs to copy and no key to paste.*

## FAQ

### Is the Notion API free?

Notion doesn’t list a separate price for API access. Personal access tokens and connections work on every plan, Free included. The plan changes the rate limit, 180 requests per minute per connection or 600 on Business and Enterprise, and who may create personal access tokens. One catch: a Free workspace with more than one member has a lifetime limit of 1,000 blocks. Once it’s reached and a three-day grace period ends, internal connections can’t create pages or blocks; reading and editing still work.

### What is the Notion API rate limit?

Since September 9, 2026, each connection gets 180 requests per minute on Free, Plus and other plans and 600 per minute on Business and Enterprise, counted in a fixed 60-second window. A separate limit is shared by all connections in a workspace, and querying a data source is limited to about 3 requests per second. Over a limit, Notion returns HTTP 429 with a `Retry-After` header.

### Does a Notion API key expire?

Personal access tokens do: you choose 7, 30, 90 or 180 days, or 1 year, which is the default. Notion’s docs give internal connection tokens no expiry date, but you can refresh one at any time, which replaces it. An expired, revoked or replaced token returns `401 unauthorized`.

### What is the difference between a database ID and a data source ID?

The database ID is in the database’s link, before `?v=`. Since API version 2025-09-03 a database can hold several data sources, and querying rows or creating pages needs the data source ID. Copy it in Notion with **Manage data sources → Copy data source ID**, or read the `data_sources` list that `GET /v1/databases/{id}` returns.

