Notion API - a complete, practical guide to integrations, pages, databases, blocks, search, and webhooks

The Notion API lets you build integrations that read and write Notion content: create pages, query databases, append blocks, search across content shared with your integration, and react to changes using webhooks. It’s used for automations, syncing Notion with CRMs, generating reports, content workflows, internal tools, and “Notion-as-a-backend” apps.

1) What the Notion API is

Overview

The Notion API is a public HTTP API that lets your software integrate with Notion workspaces. You create an integration in Notion, receive an access token, and then call REST endpoints to read or write content such as pages, databases, and blocks. Notion’s reference introduction describes integrations as the way developers access Notion’s pages, databases, and users and build experiences that connect services to Notion. (See the API intro.)

A key idea: integrations only see what’s shared with them. Your integration will not automatically access a whole workspace. Users (or admins) must explicitly share pages/databases with the integration, and then the API can read and write within those shared areas. This permission boundary is one reason Notion integrations tend to be safer than “global” API keys.

API style

JSON over HTTPS, Bearer auth, versioning via the Notion-Version header, and cursor-based pagination for large lists.

Best mental model

Notion pages are composed of blocks, and databases are containers for pages with structured properties.

Official essentials (highly important)

Authentication: send a Bearer token in the Authorization header. (Authentication docs.)
Versioning: every request must include Notion-Version. The docs state the latest version is 2025-09-03. (Versioning docs.)

2) What you can build with the Notion API

Use cases

The Notion API is flexible, but it shines in a few common patterns:

Sync & data pipelines

Keep Notion databases in sync with other tools (CRM, support tickets, shipping systems). Webhooks signal changes, and your service pulls the latest data and mirrors it elsewhere.

Automations

Create pages from forms, generate reports, move items between statuses, assign owners, and keep dashboards updated on a schedule.

Internal tools

Build lightweight apps that read/write Notion as a backend: project trackers, content calendars, editorial tools, knowledge bases.

Content publishing

Pull Notion pages and render them as a website/blog. You typically store metadata in properties and blocks provide the body content.

Notifications & workflows

Trigger Slack/email notifications when pages change, when an item moves to “Ready,” or when a deadline is near.

AI & enrichment

Enrich database items with summaries/tags in your own service, then write results back into Notion properties.

Reality check: Notion isn’t a traditional SQL database

Notion databases are extremely powerful for human workflows, but they don’t behave like a relational database server. Most integrations do best when they cache data, sync incrementally, and treat Notion as the “source of truth for humans” while your app provides performance and complex computation.

3) Quickstart: create an integration → query a database → create a page

Quickstart

This quickstart shows the “happy path” for most Notion API projects: (1) create an integration and get a token, (2) share a database with the integration, (3) query the database, and (4) create a new page inside it.

  1. Create an integration in Notion (internal or public). Notion’s Help Center walks through creating integrations and permissions.
  2. Copy the token (for internal integrations) or implement OAuth for public integrations.
  3. Share the target page/database with the integration in Notion UI (so the integration can access it).
  4. Call the API with required headers: Authorization: Bearer ..., Notion-Version: 2025-09-03, and Content-Type: application/json where needed.
Don’t skip Notion-Version

Notion’s versioning documentation states versioning is required via the Notion-Version header, and it gives 2025-09-03 as the latest version at time of writing. If you omit it, requests can fail or behave unexpectedly. (Versioning docs.)

Example: query a database (POST /v1/databases/<id>/query)

curl -X POST "https://api.notion.com/v1/databases/YOUR_DATABASE_ID/query" \
  -H "Authorization: Bearer YOUR_SECRET" \
  -H "Notion-Version: 2025-09-03" \
  -H "Content-Type: application/json" \
  --data '{
    "page_size": 10,
    "sorts": [{"timestamp":"last_edited_time","direction":"descending"}]
  }'

The database query endpoint returns pages in that database (and in newer structures, it can also return databases in wiki-like structures). The official reference explains that the response may contain fewer than page_size results and includes next_cursor for pagination. (Database query docs.)

Example: create a page in a database (POST /v1/pages)

curl -X POST "https://api.notion.com/v1/pages" \
  -H "Authorization: Bearer YOUR_SECRET" \
  -H "Notion-Version: 2025-09-03" \
  -H "Content-Type: application/json" \
  --data '{
    "parent": { "database_id": "YOUR_DATABASE_ID" },
    "properties": {
      "Name": { "title": [{ "text": { "content": "Hello from the Notion API" } }] },
      "Status": { "select": { "name": "To do" } }
    }
  }'

The most common failure in this step is property mismatch: your payload must match the database property schema (property names, types, and allowed options). In this guide, you’ll learn how to safely build payloads by first retrieving the database schema.

4) Authentication: Bearer tokens and required headers

Auth

Notion’s authentication documentation is straightforward: requests use the HTTP Authorization header to authenticate, and the API accepts Bearer tokens in that header. These tokens come from your integration (internal) or are issued via OAuth for public integrations. (Authentication docs.)

Minimum headers (typical)

Authorization: Bearer <token>
Notion-Version: 2025-09-03
Content-Type: application/json (for POST/PATCH bodies)

Why headers matter

Versioning is required. Rate limiting and errors rely on consistent status code handling and retry logic.

Official versioning rule

Notion’s versioning page says: “Set the version by including a Notion-Version header. Setting this header is required.” It also states that versions are date-based and cites 2025-09-03 as the latest version. (Versioning docs.)

Tokens should be treated like passwords. Do not commit them to Git, do not place them in client-side JavaScript, and never ship them to end users. Use environment variables and a secure secret manager for production.

5) Authorization & OAuth: internal vs public integrations

OAuth

Notion distinguishes between internal integrations (used within a single workspace) and public integrations (distributed to many workspaces). The authorization process differs: internal integrations typically use a static token created in the integration settings, while public integrations use OAuth so each user/workspace grants access and your app receives tokens per installation. Notion’s authorization documentation explains this distinction and notes special handling for link preview integrations. (Authorization docs.)

Internal integration

Best for: personal automation, internal company tools, single workspace apps.
Setup: create integration → set permissions → copy token → share target pages/databases.

Public integration (OAuth)

Best for: SaaS products, tools you distribute publicly, multi-tenant apps.
Setup: implement OAuth flow → store tokens per workspace → respect permission scopes.

Plan for token storage & revocation

With OAuth, you must store tokens per workspace securely (encrypted at rest), implement refresh/reauth flows if needed, and handle “uninstall” cases so you stop calling the API when access is revoked.

6) Versioning: Notion-Version and upgrading safely

Versioning

Notion’s API is versioned. The versioning documentation states that versions are named for the date they’re released, and it explicitly gives 2025-09-03 as the latest version (at the time of the doc). You set the version with the required Notion-Version header. (Versioning docs.)

Incremental upgrades

Notion’s changelog notes that you can use different version headers per request and upgrade incrementally. (Changelog guidance.) This is useful if one endpoint is breaking for you under the newest version while you migrate the rest.

If you’re updating an older integration, Notion publishes upgrade guides. For example, the “Upgrading to Version 2025-09-03” guide describes changes and suggests a migration process, including updating calls to newer database APIs and using the 2025-09-03 version header during migration. (Upgrade guide.)

Versioning best practices

  • Pin a version. Always set Notion-Version explicitly in your HTTP client.
  • Read upgrade guides. When Notion announces a new version, scan what changed before you flip the header globally.
  • Test in a staging workspace. Use a separate Notion workspace to validate changes before production.
  • Log the version. Include the version in your logs so incidents are easier to debug.

7) Integration permissions model: scopes + “share with integration”

Permissions

Notion access has two layers:

  • Integration capabilities (scopes/permissions): what types of objects/actions the integration is allowed to request.
  • Shared content boundary: which pages/databases a user has explicitly shared with the integration in the Notion UI.

In practice, you often get “permission denied” errors because one of these layers is missing: either the integration doesn’t have the correct capabilities (e.g., “read content” vs “insert content”), or the page/database wasn’t shared with the integration.

Tip: build a “connection check” endpoint

In your app, add a “Test connection” button that calls something simple (like Search or Retrieve User) to confirm: token is valid, version header is set, and access is granted.

8) Core objects: pages, databases, blocks, properties, users

Objects

Understanding Notion’s object model will save you days of confusion:

Page

A page is both content and metadata. If a page is inside a database, it has properties like a row in a table. A page’s body content is stored as a tree of blocks.

Database

A database defines a schema (properties) and contains pages. Querying a database returns pages matching filters and sorts.

Block

Blocks are the building pieces: paragraphs, headings, lists, to-dos, callouts, images, toggles, code blocks, and more. A page is technically a block, which is why some endpoints treat pages as blocks (for example, comments can be retrieved via block_id). (Comments guide.)

Properties

Database columns: title, rich_text, select, multi_select, status, date, people, relation, rollup, number, checkbox, URL, email, phone, files, etc. Property payloads must match the schema exactly.

Many integrations follow a “schema-first” approach: retrieve the database schema (Get Database), then generate payloads that match it. This helps you avoid hardcoding property types and reduces breaking changes when someone edits the database in Notion.

9) Endpoint map: what exists in the Notion API

Endpoints

Notion’s API reference is organized by object type and endpoint. Most integrations use a subset of these endpoints:

Area Common endpoints What you do with them
Databases Get Database, Query Database, Update Database Read schema, pull rows/pages, filter/sort, update schema metadata
Pages Create Page, Retrieve Page, Update Page Create rows, update properties, read page metadata
Blocks Retrieve Block, Retrieve Block Children, Append Block Children, Update Block Read page content, add paragraphs/lists/images, maintain docs
Search POST /v1/search Find pages/databases shared with the integration (title search + filters)
Users List Users, Retrieve User, Retrieve Bot User Identify people objects, show authors/owners, resolve mentions
Comments Retrieve Comments Read open/resolved threads on pages/blocks (pages are blocks) (Comments guide.)
Webhooks Webhook registration + event delivery docs Get real-time notifications when pages/databases change (Webhooks docs.)
Start with Search when you don’t know IDs

Many first-time builders get stuck because they have no database/page IDs. The Search endpoint (POST /v1/search) is a convenient way to find objects that have been shared with your integration, and it supports pagination. (Search reference.)

10) Databases: schema, querying, filters, and sorts

Databases

Databases are where most Notion API projects live. A typical integration does these steps: (1) retrieve the database to read its schema, then (2) query the database with filters and sorts, then (3) create or update pages (rows) in that database.

Query a database

Notion’s database query endpoint is a POST call where you can supply filters and sorts. The reference notes the response may include next_cursor and that you should use cursor-based pagination to iterate. (Database query docs.)

Filters

Filter by property values (e.g., Status = “In progress”), dates (created/edited), checkbox true/false, text contains, numbers greater/less, and compound AND/OR logic.

Sorts

Sort by property or timestamps. Use stable sorts for pagination to avoid duplicates when data changes during traversal.

Example: query with filter + pagination fields

{
  "page_size": 50,
  "filter": {
    "and": [
      { "property": "Status", "select": { "equals": "In progress" } },
      { "property": "Priority", "select": { "does_not_equal": "Low" } }
    ]
  },
  "sorts": [
    { "timestamp": "last_edited_time", "direction": "descending" }
  ]
}
Schema changes can break payloads

If someone renames a property in Notion, your code that references the old name will fail. For production systems, store property IDs (when available) or build a mapping layer that can re-discover schema changes.

11) Property types & payload patterns (the part that trips everyone up)

Properties

Notion property payloads are strict. You must send the right shape for each property type. The safest approach is:

  1. Retrieve the database schema first (Get Database) and inspect property types.
  2. Build payloads by type (title, rich_text, select, multi_select, date, number, people, relation, files, checkbox, etc.).
  3. Validate inputs in your app so you never send illegal values (like a Select option that doesn’t exist).
Property type Payload shape (high level) Common mistakes
title {"title":[{"text":{"content":"..."}}]} Forgetting title is an array; sending plain string
rich_text {"rich_text":[{"text":{"content":"..."}}]} Wrong nesting; exceeding UI expectations for formatting
select {"select":{"name":"Option"}} Option name doesn’t exist; casing mismatch
multi_select {"multi_select":[{"name":"A"},{"name":"B"}]} Sending a string list instead of objects
date {"date":{"start":"YYYY-MM-DD"}} Invalid ISO; timezone confusion; missing start
people {"people":[{"id":"user-id"}]} Using email instead of user id; user not visible to integration
relation {"relation":[{"id":"page-id"}]} Relating to a page that isn’t shared; wrong ID type
checkbox {"checkbox":true} Sending "true" as a string
number {"number":123} NaN/empty; incorrect locale decimals
Pro tip: write a “property adapter” layer

Instead of building payloads everywhere in your code, create one adapter function per property type. This keeps your integration maintainable and makes schema changes far easier to handle.

12) Pages: retrieve, create, update (properties vs content)

Pages

Pages are central to Notion. When a page lives inside a database, you typically interact with it through its properties. When you want to edit the body of the page (paragraphs, headings, lists), you work with blocks.

Update a page (properties)

Use Update Page to change property values like Status, Due date, Owner, or Title. This is how you “edit a row” in a database.

Edit content (blocks)

To change the page body, append or update blocks via block endpoints. Many publishing integrations render blocks into HTML/Markdown.

Example: update a page property (PATCH /v1/pages/<id>)

{
  "properties": {
    "Status": { "select": { "name": "Done" } },
    "Reviewed": { "checkbox": true }
  }
}
Pages are blocks

Notion notes in its comments guide that pages are technically blocks, which is why some endpoints accept block_id even when you’re thinking “page”. (Comments guide.)

13) Blocks: reading content and appending children

Blocks

Blocks represent the actual content on a page: text, headings, bullet lists, to-dos, images, toggles, callouts, code blocks, tables, and more. If you’re building a publisher, exporter, or document automation, blocks are where you’ll spend most of your time.

Notion’s “Working with page content” guide shows that retrieving block children is paginated, with a maximum of 100 results per response, and you use start_cursor and page_size to fetch additional results. (Working with page content guide.)

Retrieve children

Use Retrieve Block Children, then paginate if the page is long (max 100 per response).

Append children

Use Append Block Children to add paragraphs, headings, lists, etc., to an existing page.

Example: append blocks (high-level)

{
  "children": [
    { "object": "block", "type": "heading_2", "heading_2": { "rich_text": [{ "type":"text","text":{"content":"Summary"}}] } },
    { "object": "block", "type": "paragraph", "paragraph": { "rich_text": [{ "type":"text","text":{"content":"Generated by my integration."}}] } },
    { "object": "block", "type": "bulleted_list_item", "bulleted_list_item": { "rich_text": [{ "type":"text","text":{"content":"First bullet"}}] } }
  ]
}
Rendering strategy

For export/publishing, build a renderer that maps block types to HTML/Markdown. Handle nested blocks recursively (toggles, lists, columns), and make pagination transparent to callers.

15) Comments: retrieve comment threads on pages/blocks

Comments

The comments guide explains that you can list comments for a page or block using the block_id query parameter, because pages are technically blocks. It also notes the endpoint returns a flat list and that some block types may support multiple discussion threads. (Comments guide.)

When comments matter

Comments are useful for review workflows: editorial reviews, approvals, and task discussions. Many teams treat Notion comments like “lightweight pull request feedback.”

16) Users: people objects, bots, and ownership

Users

Notion exposes user objects for people who appear in properties (like “Assignee”). Integrations often need users to:

  • Assign tasks (people property)
  • Show who created or edited pages (metadata)
  • Map Notion users to internal users in your system (SSO or email matching when possible)

In multi-tenant apps, avoid assuming an email address is always available or stable. Use Notion user IDs as primary identifiers and store mappings in your database.

17) Pagination pattern: has_more, next_cursor, start_cursor

Pagination

Notion uses cursor-based pagination across many “list” endpoints (database query results, search results, block children, comments lists, etc.). The “Working with page content” guide notes that paginated responses are used throughout the API and the maximum results per response is 100, and it references using start_cursor and page_size to retrieve more than 100 results. (Working with page content guide.)

The standard loop looks like this:

let cursor = undefined;
do {
  const body = cursor ? { start_cursor: cursor, page_size: 100 } : { page_size: 100 };
  const res = await callNotion(endpoint, body);
  handle(res.results);
  cursor = res.next_cursor; // null/undefined when done
} while (cursor);
Pagination + changing data = duplicates or gaps

If content changes while you paginate, ordering can shift. Use stable sorts, and consider “sync by time window” (e.g., last_edited_time greater than X) with checkpoints to reduce missed changes.

18) Request limits: rate limiting and how to handle 429

Limits

Notion’s request limits documentation says requests that exceed limits return a rate_limited error (HTTP 429), and it states the rate limit for incoming requests per integration is an average of three requests per second. It also says you should respect the Retry-After header (seconds). (Request limits docs.)

What “3 rps average” means

You can sometimes burst above 3 rps, but your integration should throttle and back off. Implement a rate limiter (token bucket/leaky bucket) and queue requests.

What to do on 429

Sleep for Retry-After seconds, then retry. Add jitter, cap retries, and log rate-limit events.

Example retry strategy (human-readable)

  • On 429: read Retry-After → wait that many seconds → retry.
  • Also implement exponential backoff for transient 5xx or network timeouts.
  • Make writes idempotent where possible (avoid duplicate page creation on retry).
Official behavior to rely on

Notion explicitly documents the 3 rps average limit and the Retry-After header for 429 responses. That’s the contract you should build against. (Request limits docs.)

19) Status codes & errors: building a resilient integration

Errors

A production Notion integration is mostly error handling and data hygiene. Your app should gracefully handle:

401 / 403

Token invalid, token revoked, missing scopes, or page/database not shared with the integration. Your UI should guide users to reconnect or share content.

404

Object not found (often “not shared” looks like “not found”). Avoid leaking existence of private objects; treat as a permissions issue.

409 / 422

Conflict/validation errors: schema mismatch, invalid property payload, invalid filter, invalid select option, etc.

429

Rate limited; wait for Retry-After. (Request limits docs.)

Most common bug: invalid property payload

If you see consistent 4xx errors on writes, inspect your payload against the database schema. Retrieve the database and compare property types and allowed values.

20) Webhooks: real-time updates from Notion

Webhooks

Webhooks let your integration receive real-time updates from Notion. Notion’s webhook reference says that when a page or database changes, Notion sends a secure HTTP POST request to your webhook endpoint so your application can respond as it happens. (Webhooks reference.)

Notion also documents that webhook events are signals rather than full diffs: the “Event types & delivery” page explains events do not contain the full content that changed, and your integration should follow up with API calls to retrieve the latest content. (Events & delivery docs.)

Webhook pattern (recommended)

Webhook event → enqueue job → fetch updated page/database → update your cache → trigger downstream actions. Keep webhook handler fast and reliable.

Why events don’t include full content

Keeps events lightweight and reduces data leakage. Your integration pulls the specific content it needs afterward. (Events & delivery docs.)

Do not do heavy work inside the webhook request

Return 2xx quickly, verify the webhook request per Notion docs, then process asynchronously via your job queue to avoid timeouts and accidental retries.

Notion’s Help Center also covers “integration webhooks” in more user-facing language, emphasizing they enable integrations to respond to changes in shared pages/databases in real time. (Help Center integrations page.)

21) Security best practices (non-negotiables)

Security

Treat Notion like any SaaS with privileged access:

  • Never expose tokens in the browser. Notion tokens must stay server-side.
  • Use least privilege permissions. Only request the scopes you need.
  • Encrypt tokens at rest. Use a KMS or managed secret store.
  • Log safely. Never log tokens or full Notion content if it includes sensitive data.
  • Validate input. Defend against injection in any content you write into Notion (especially if rendering elsewhere).
  • Verify webhooks. Validate signatures/headers per Notion webhook docs before trusting events.
Important: Notion tokens are effectively workspace passwords

If a token leaks, an attacker can read/write whatever was shared with that integration under its permission scopes. Rotate tokens immediately and audit what was shared.

22) Production architecture: how to build a Notion integration that scales

Production

The Notion API is powerful, but you’ll hit rate limits (3 rps average per integration) quickly if you do naive polling or page-by-page traversal. (Request limits docs.) The scalable pattern is a sync engine with caching and incremental updates:

Core components

1) Notion connector service (HTTP client + rate limiter)
2) Cache/database in your app (normalized objects)
3) Job queue (webhook + batch sync)
4) Renderer/transformer (blocks → HTML/Markdown)
5) Admin UI (connect, test, select database)

Recommended sync strategy

Webhooks for “push” signals + periodic reconciliation for safety. On webhook: fetch only the changed objects, not everything.

Performance tips that actually matter

  • Batch reads with concurrency limits. Use a small pool (e.g., 2–4 concurrent calls) + a global rate limiter.
  • Prefer database queries over search for structured lists. Search is great for discovery, but query is better for deterministic retrieval. (Search docs, Database query docs.)
  • Cache block trees. Blocks retrieval is paginated and can be expensive on long pages. (Working with page content guide.)
  • Use checkpoints. Track last successful sync times and update windows to prevent missed changes.
  • Make writes idempotent. Store an external_id property or use stable keys to avoid duplicates on retries.
Rule of thumb

Don’t treat Notion as a high-frequency database. Treat it as a collaborative workspace: sync intelligently, cache locally, and only call the API when you need fresh data or you’re writing changes back.

23) FAQs (Notion API)

FAQ
Do I have to set the Notion-Version header?

Yes. Notion’s versioning documentation says setting the Notion-Version header is required, and it states the latest version is 2025-09-03. See: https://developers.notion.com/reference/versioning

What is the Notion API rate limit?

Notion’s request limits documentation states the rate limit for incoming requests per integration is an average of three requests per second, and rate-limited requests return HTTP 429 with a Retry-After header. See: https://developers.notion.com/reference/request-limits

Why can’t my integration find a page or database?

Most often, the page/database wasn’t shared with the integration in Notion, or the integration lacks required permissions/scopes. Search results and object retrieval are limited to content shared with the integration. See the API intro: https://developers.notion.com/reference/intro

How do I get more than 100 results from a long page or database query?

Notion uses cursor pagination. The page content guide explains the maximum results in one paginated response is 100 and you use start_cursor and page_size. See: https://developers.notion.com/guides/data-apis/working-with-page-content

Do webhooks include the full content that changed?

Notion’s webhook delivery docs say events do not include the full content that changed; they signal that something changed and your integration should fetch updates via the API. See: https://developers.notion.com/reference/webhooks-events-delivery

Where do I learn the exact request bodies for endpoints?

Use the official reference pages for each endpoint (e.g., Search, Query a database, Update a database). Start from the reference intro: https://developers.notion.com/reference/intro

24) Sources (official)

Sources
Last updated

Feb 7, 2026. Notion’s API evolves; always confirm the latest version and limits in official docs.