Communications · active

Notion API

A workspace content API for pages, blocks, data sources, comments, users, search, and webhooks.

What it covers

A workspace content API for pages, blocks, data sources, comments, users, search, and webhooks. The profile stays deliberately concise: use the linked official reference for endpoint schemas and current commercial terms.

Integration notes

Authenticate exactly as the provider documents, keep credentials out of client bundles, and retain upstream request identifiers when available. Pagination, retries, caching, and webhook verification should be implemented per product rather than inferred from another API.

Key endpoints

Endpoint Description
POST /v1/pages Create a page
GET /v1/pages/{page_id} Retrieve a page
PATCH /v1/pages/{page_id} Update page properties
POST /v1/databases/{db_id}/query Query a database
POST /v1/databases Create a database
GET /v1/blocks/{block_id}/children List block children
PATCH /v1/blocks/{block_id} Update a block
GET /v1/search Search pages/databases

Decision notes

Best suited to knowledge-base integrations and content workflow automation. Before production, verify notion-version is required. respect retry-after for 429 and 529 responses. Mutable limits, prices, and versions were checked on 2026-09-13 and should be rechecked at purchase or deployment time.

Python example: query a database (version header required)

import httpx

API_KEY = "your_notion_integration_token"
BASE = "https://api.notion.com/v1"
HEADERS = {
    "Authorization": f"Bearer {API_KEY}",
    "Notion-Version": "2022-06-28",  # required — pin a version
    "Content-Type": "application/json",
}

def query_database(database_id: str, filter_: dict | None = None) -> list[dict]:
    """Query a database. Paginate with start_cursor."""
    rows, cursor = [], None
    while True:
        body = {"page_size": 50}
        if filter_:
            body["filter"] = filter_
        if cursor:
            body["start_cursor"] = cursor
        resp = httpx.post(f"{BASE}/databases/{database_id}/query", headers=HEADERS, json=body, timeout=30)
        resp.raise_for_status()
        data = resp.json()
        rows.extend(data["results"])
        if not data.get("has_more"):
            break
        cursor = data["next_cursor"]
    return rows

def page_title(page: dict) -> str:
    for prop in page["properties"].values():
        if prop["type"] == "title" and prop["title"]:
            return prop["title"][0]["plain_text"]
    return "(untitled)"

for p in query_database("your_database_id")[:5]:
    print(page_title(p))

Notes:

  • Notion-Version header is mandatory; pin it to avoid surprise breaking changes
  • The integration must be shared with the database/page or queries return empty
  • Properties are typed (title, rich_text, select, date…) — parse per type
  • Rate limit ~3 req/sec; back off on 429

Integration surface

Versioning
Required Notion-Version request header
Resource groups
Pages, Blocks, Databases, Data sources, Comments, Users, Search
Pagination
start_cursor and page_size with next_cursor and has_more
Errors
Structured JSON errors; 429 and 529 include retry guidance
Webhooks
Webhook events for pages, databases, data sources, comments and more
Official SDKs
JavaScript