How to Connect an AI Agent to Notion (Python + API)
Updated 2026-10-27
This guide connects a Python agent to Notion with the official REST API and a personal access token (PAT). The agent can search pages you have shared with the integration, read page content as Markdown, and append a short note. For Claude or Cursor without custom code, use Notion MCP instead. Last tested 2026-10-27.
Docs used for the steps below:
Prerequisites
- A Notion account and permission to create integrations in the workspace
- Python 3.10+ and an LLM API key
- The tool loop from Build your first AI agent in Python
Steps
1. Create a personal access token
- Open Personal access tokens in the Notion Developer portal.
- Select New token, name it, enable the Notion API capability, and create the token.
- Copy the token (it starts with
ntn_) and store it securely.
export NOTION_API_KEY="ntn_..."
On some Business and Enterprise workspaces, only owners can create PATs. See Who can create PATs.
2. Share pages with the integration
Pages are invisible to the API until you grant access:
- Open the target page in Notion.
- Click ⋯ → Connect to → choose your integration.
Or, from the integration’s Access tab, add pages and databases explicitly. Without this step you will get object_not_found even with a valid token.
3. Install dependencies
pip install requests openai
4. Write Notion tools for the agent
The API version header must match the endpoints you call. Markdown read/write uses 2026-03-11 per the API quickstart.
# notion_tools.py
import os
import requests
NOTION_VERSION = "2026-03-11"
BASE = "https://api.notion.com/v1"
def _headers():
return {
"Authorization": f"Bearer {os.environ['NOTION_API_KEY']}",
"Notion-Version": NOTION_VERSION,
"Content-Type": "application/json",
}
def search_notion(query: str, max_results: int = 5) -> str:
"""Search pages shared with the integration. query can be empty to list recent pages."""
payload = {
"query": query,
"page_size": max_results,
"filter": {"property": "object", "value": "page"},
"sort": {"direction": "descending", "timestamp": "last_edited_time"},
}
r = requests.post(f"{BASE}/search", headers=_headers(), json=payload, timeout=30)
r.raise_for_status()
lines = []
for page in r.json().get("results", []):
title = ""
for t in page.get("properties", {}).get("title", {}).get("title", []):
title += t.get("plain_text", "")
lines.append(f'{page["id"]} | {title or "(untitled)"} | {page.get("url", "")}')
return "\n".join(lines) or "no results"
def read_page_markdown(page_id: str) -> str:
"""Read a page as Markdown (requires Notion-Version 2026-03-11)."""
r = requests.get(f"{BASE}/pages/{page_id}/markdown", headers=_headers(), timeout=30)
r.raise_for_status()
return r.json().get("markdown", "")
def append_page_note(page_id: str, note: str) -> str:
"""Append a paragraph using insert_content (see Update page markdown API)."""
payload = {
"insert_content": {
"position": {"type": "end"},
"content": f"\n\n{note}",
}
}
r = requests.patch(
f"{BASE}/pages/{page_id}/markdown",
headers=_headers(),
json=payload,
timeout=30,
)
r.raise_for_status()
return "ok"
Wire these functions into your agent’s tool list with JSON schemas (same pattern as Connect an AI agent to Gmail).
5. Run a smoke test
if __name__ == "__main__":
print(search_notion(""))
page_id = search_notion("").split(" | ")[0]
print(read_page_markdown(page_id)[:500])
6. Add guardrails
- Keep the integration read-only in the Developer portal if the agent should not edit pages.
- Log tool calls and cap
max_steps(Fix: AI agent stuck in a loop). - For multi-user bots, do not reuse one Notion token per end user; each human needs their own integration or OAuth flow.
Common errors
unauthorized / 401: Wrong or expired PAT. Create a new token and update NOTION_API_KEY.
object_not_found / 404: The page is not shared with the integration. Use Connect to on the page or add it under the integration Access tab.
restricted_resource / 403: The token lacks capability for the action (for example write on a read-only integration).
Empty search results: The integration has no shared content, or the query filter is too narrow. Try search_notion("") to list recent pages.
Markdown endpoint errors: Confirm Notion-Version: 2026-03-11 on read/patch Markdown calls. Older versions may not expose /pages/{id}/markdown.
FAQ
Should I use the Notion API or Notion MCP?
Use Notion MCP (guide) when the agent runs inside Cursor, Claude Code, or another MCP client and you want OAuth plus hosted tools. Use the REST API in this guide when you own a Python (or Node) agent process and need full control over tools and prompts.
Do I need OAuth for a personal agent script?
No. A PAT in NOTION_API_KEY is enough for scripts that act as you. OAuth and MCP tokens matter for multi-user products and hosted MCP clients; see Connect to Notion MCP.
Can the agent edit any page in my workspace?
Only pages and databases explicitly shared with the integration. Notion does not grant workspace-wide access through a PAT.