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-Versionheader 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