Notion API Integration Skill
Master the Notion API for workspace automation, including databases, pages, blocks, query/filter syntax, and integration patterns. This skill covers the official REST API and Python SDK for building powerful Notion integrations.
When to Use This Skill
USE Notion API when:
-
Automating database entries and updates
-
Building custom dashboards from Notion data
-
Syncing data between Notion and external systems
-
Creating pages programmatically from templates
-
Querying databases with complex filters
-
Building integrations with other productivity tools
-
Generating reports from Notion databases
-
Implementing workflow automations
DON'T USE Notion API when:
-
Need real-time sync (API has rate limits)
-
Building chat/messaging features (use Slack API)
-
Need file storage solution (use dedicated storage)
-
Simple task management only (use Todoist API)
-
Need offline-first solution (use Obsidian)
-
Require sub-second response times
Prerequisites
Create Integration
- Go to https://www.notion.so/my-integrations
- Click "New integration"
- Name: "My Integration"
- Select workspace
- Set capabilities (Read/Write content, etc.)
- Copy the "Internal Integration Token"
Connect Integration to Pages
- Open the Notion page/database you want to access
- Click "..." menu (top right)
- Click "Connections" > "Connect to" > Your integration
- Integration can now access this page and children
Environment Setup
Set environment variable
export NOTION_API_KEY="secret_xxxxxxxxxxxxxxxxxxxxx"
Verify connection
curl -s "https://api.notion.com/v1/users/me"
-H "Authorization: Bearer $NOTION_API_KEY"
-H "Notion-Version: 2022-06-28" | jq
Python SDK Installation
Install official Python client
pip install notion-client
Or with uv
uv pip install notion-client
Additional dependencies
pip install python-dotenv requests
Verify Setup
from notion_client import Client import os
notion = Client(auth=os.environ["NOTION_API_KEY"])
Test connection
me = notion.users.me() print(f"Connected as: {me['name']}")
List accessible databases
databases = notion.search(filter={"property": "object", "value": "database"}) print(f"Found {len(databases['results'])} databases")
Core Capabilities
- Database Operations
List and Search Databases:
Search for databases
curl -s -X POST "https://api.notion.com/v1/search"
-H "Authorization: Bearer $NOTION_API_KEY"
-H "Notion-Version: 2022-06-28"
-H "Content-Type: application/json"
-d '{
"filter": {
"property": "object",
"value": "database"
}
}' | jq '.results[] | {id: .id, title: .title[0].plain_text}'
Get database schema
curl -s "https://api.notion.com/v1/databases/DATABASE_ID"
-H "Authorization: Bearer $NOTION_API_KEY"
-H "Notion-Version: 2022-06-28" | jq '.properties'
Python - Database Operations:
from notion_client import Client import os
notion = Client(auth=os.environ["NOTION_API_KEY"])
Search for databases
results = notion.search( filter={"property": "object", "value": "database"} )
for db in results["results"]: title = db["title"][0]["plain_text"] if db["title"] else "Untitled" print(f"Database: {title} (ID: {db['id']})")
Get database details
database = notion.databases.retrieve(database_id="your-database-id") print(f"Properties: {list(database['properties'].keys())}")
Create database
new_db = notion.databases.create( parent={"type": "page_id", "page_id": "parent-page-id"}, title=[{"type": "text", "text": {"content": "Tasks Database"}}], properties={ "Name": {"title": {}}, "Status": { "select": { "options": [ {"name": "To Do", "color": "gray"}, {"name": "In Progress", "color": "blue"}, {"name": "Done", "color": "green"} ] } }, "Priority": { "select": { "options": [ {"name": "High", "color": "red"}, {"name": "Medium", "color": "yellow"}, {"name": "Low", "color": "gray"} ] } }, "Due Date": {"date": {}}, "Assignee": {"people": {}}, "Tags": {"multi_select": {"options": []}}, "Completed": {"checkbox": {}}, "Notes": {"rich_text": {}} } ) print(f"Created database: {new_db['id']}")
Update database
notion.databases.update( database_id="your-database-id", title=[{"type": "text", "text": {"content": "Updated Title"}}], properties={ "New Property": {"rich_text": {}} } )
- Query Databases
Basic Query:
Query all items
curl -s -X POST "https://api.notion.com/v1/databases/DATABASE_ID/query"
-H "Authorization: Bearer $NOTION_API_KEY"
-H "Notion-Version: 2022-06-28"
-H "Content-Type: application/json"
-d '{}' | jq '.results'
Query with filter
curl -s -X POST "https://api.notion.com/v1/databases/DATABASE_ID/query"
-H "Authorization: Bearer $NOTION_API_KEY"
-H "Notion-Version: 2022-06-28"
-H "Content-Type: application/json"
-d '{
"filter": {
"property": "Status",
"select": {
"equals": "In Progress"
}
}
}' | jq '.results'
Query with sort
curl -s -X POST "https://api.notion.com/v1/databases/DATABASE_ID/query"
-H "Authorization: Bearer $NOTION_API_KEY"
-H "Notion-Version: 2022-06-28"
-H "Content-Type: application/json"
-d '{
"sorts": [
{
"property": "Due Date",
"direction": "ascending"
}
]
}' | jq '.results'
Python - Query Operations:
Simple query
results = notion.databases.query(database_id="your-database-id") for page in results["results"]: props = page["properties"] name = props["Name"]["title"][0]["plain_text"] if props["Name"]["title"] else "Untitled" print(f"- {name}")
Query with filter
results = notion.databases.query( database_id="your-database-id", filter={ "property": "Status", "select": { "equals": "In Progress" } } )
Query with multiple filters (AND)
results = notion.databases.query( database_id="your-database-id", filter={ "and": [ { "property": "Status", "select": {"equals": "In Progress"} }, { "property": "Priority", "select": {"equals": "High"} } ] } )
Query with OR filter
results = notion.databases.query( database_id="your-database-id", filter={ "or": [ {"property": "Status", "select": {"equals": "To Do"}}, {"property": "Status", "select": {"equals": "In Progress"}} ] } )
Query with sorting
results = notion.databases.query( database_id="your-database-id", sorts=[ {"property": "Priority", "direction": "ascending"}, {"property": "Due Date", "direction": "ascending"} ] )
Paginated query
def query_all_pages(database_id, filter=None): """Query all pages with pagination""" all_results = [] has_more = True start_cursor = None
while has_more:
response = notion.databases.query(
database_id=database_id,
filter=filter,
start_cursor=start_cursor,
page_size=100
)
all_results.extend(response["results"])
has_more = response["has_more"]
start_cursor = response.get("next_cursor")
return all_results
all_items = query_all_pages("your-database-id") print(f"Total items: {len(all_items)}")
- Filter Syntax Reference
Text Filters:
Text property filters
{"property": "Name", "title": {"equals": "Exact Match"}} {"property": "Name", "title": {"does_not_equal": "Not This"}} {"property": "Name", "title": {"contains": "partial"}} {"property": "Name", "title": {"does_not_contain": "exclude"}} {"property": "Name", "title": {"starts_with": "Prefix"}} {"property": "Name", "title": {"ends_with": "suffix"}} {"property": "Name", "title": {"is_empty": True}} {"property": "Name", "title": {"is_not_empty": True}}
Rich text property
{"property": "Notes", "rich_text": {"contains": "keyword"}}
Number Filters:
{"property": "Amount", "number": {"equals": 100}} {"property": "Amount", "number": {"does_not_equal": 0}} {"property": "Amount", "number": {"greater_than": 50}} {"property": "Amount", "number": {"less_than": 100}} {"property": "Amount", "number": {"greater_than_or_equal_to": 10}} {"property": "Amount", "number": {"less_than_or_equal_to": 99}} {"property": "Amount", "number": {"is_empty": True}} {"property": "Amount", "number": {"is_not_empty": True}}
Date Filters:
{"property": "Due Date", "date": {"equals": "2025-01-17"}} {"property": "Due Date", "date": {"before": "2025-01-20"}} {"property": "Due Date", "date": {"after": "2025-01-10"}} {"property": "Due Date", "date": {"on_or_before": "2025-01-17"}} {"property": "Due Date", "date": {"on_or_after": "2025-01-01"}} {"property": "Due Date", "date": {"is_empty": True}} {"property": "Due Date", "date": {"is_not_empty": True}}
Relative date filters
{"property": "Due Date", "date": {"past_week": {}}} {"property": "Due Date", "date": {"past_month": {}}} {"property": "Due Date", "date": {"past_year": {}}} {"property": "Due Date", "date": {"next_week": {}}} {"property": "Due Date", "date": {"next_month": {}}} {"property": "Due Date", "date": {"next_year": {}}} {"property": "Due Date", "date": {"this_week": {}}}
Select/Multi-Select Filters:
Select
{"property": "Status", "select": {"equals": "Done"}} {"property": "Status", "select": {"does_not_equal": "Done"}} {"property": "Status", "select": {"is_empty": True}} {"property": "Status", "select": {"is_not_empty": True}}
Multi-select
{"property": "Tags", "multi_select": {"contains": "urgent"}} {"property": "Tags", "multi_select": {"does_not_contain": "archived"}} {"property": "Tags", "multi_select": {"is_empty": True}} {"property": "Tags", "multi_select": {"is_not_empty": True}}
Checkbox Filters:
{"property": "Completed", "checkbox": {"equals": True}} {"property": "Completed", "checkbox": {"equals": False}}
Relation and Rollup Filters:
Relation
{"property": "Project", "relation": {"contains": "page-id"}} {"property": "Project", "relation": {"does_not_contain": "page-id"}} {"property": "Project", "relation": {"is_empty": True}} {"property": "Project", "relation": {"is_not_empty": True}}
Rollup (depends on rollup type)
{"property": "Total Tasks", "rollup": {"number": {"greater_than": 5}}} {"property": "Completion", "rollup": {"number": {"equals": 100}}}
Compound Filters:
AND
{ "and": [ {"property": "Status", "select": {"equals": "In Progress"}}, {"property": "Priority", "select": {"equals": "High"}}, {"property": "Due Date", "date": {"before": "2025-02-01"}} ] }
OR
{ "or": [ {"property": "Status", "select": {"equals": "To Do"}}, {"property": "Status", "select": {"equals": "In Progress"}} ] }
Nested (AND with OR)
{ "and": [ { "or": [ {"property": "Priority", "select": {"equals": "High"}}, {"property": "Priority", "select": {"equals": "Medium"}} ] }, {"property": "Status", "select": {"does_not_equal": "Done"}} ] }
- Page Operations
Create Pages:
Create page in database
curl -s -X POST "https://api.notion.com/v1/pages"
-H "Authorization: Bearer $NOTION_API_KEY"
-H "Notion-Version: 2022-06-28"
-H "Content-Type: application/json"
-d '{
"parent": {"database_id": "DATABASE_ID"},
"properties": {
"Name": {
"title": [{"text": {"content": "New Task"}}]
},
"Status": {
"select": {"name": "To Do"}
},
"Priority": {
"select": {"name": "High"}
},
"Due Date": {
"date": {"start": "2025-01-20"}
}
}
}' | jq
Python - Page Operations:
Create page in database
new_page = notion.pages.create( parent={"database_id": "your-database-id"}, properties={ "Name": { "title": [{"text": {"content": "New Task"}}] }, "Status": { "select": {"name": "To Do"} }, "Priority": { "select": {"name": "High"} }, "Due Date": { "date": {"start": "2025-01-20", "end": "2025-01-25"} }, "Tags": { "multi_select": [ {"name": "development"}, {"name": "urgent"} ] }, "Assignee": { "people": [{"id": "user-id"}] }, "Notes": { "rich_text": [{"text": {"content": "Task description here"}}] }, "Completed": { "checkbox": False }, "Amount": { "number": 100 }, "URL": { "url": "https://example.com" }, "Email": { "email": "user@example.com" } } ) print(f"Created page: {new_page['id']}")
Create page with content (blocks)
new_page = notion.pages.create( parent={"database_id": "your-database-id"}, properties={ "Name": {"title": [{"text": {"content": "Page with Content"}}]} }, children=[ { "object": "block", "type": "heading_2", "heading_2": { "rich_text": [{"type": "text", "text": {"content": "Overview"}}] } }, { "object": "block", "type": "paragraph", "paragraph": { "rich_text": [{"type": "text", "text": {"content": "This is the content."}}] } }, { "object": "block", "type": "to_do", "to_do": { "rich_text": [{"type": "text", "text": {"content": "Task item"}}], "checked": False } } ] )
Retrieve page
page = notion.pages.retrieve(page_id="page-id") print(f"Page: {page['properties']['Name']['title'][0]['plain_text']}")
Update page properties
notion.pages.update( page_id="page-id", properties={ "Status": {"select": {"name": "Done"}}, "Completed": {"checkbox": True} } )
Archive page (soft delete)
notion.pages.update( page_id="page-id", archived=True )
Restore page
notion.pages.update( page_id="page-id", archived=False )
- Block Operations
Block Types:
Paragraph
{ "type": "paragraph", "paragraph": { "rich_text": [{"type": "text", "text": {"content": "Text content"}}] } }
Headings
{ "type": "heading_1", "heading_1": { "rich_text": [{"type": "text", "text": {"content": "Heading 1"}}] } }
Also: heading_2, heading_3
Bulleted list
{ "type": "bulleted_list_item", "bulleted_list_item": { "rich_text": [{"type": "text", "text": {"content": "List item"}}] } }
Numbered list
{ "type": "numbered_list_item", "numbered_list_item": { "rich_text": [{"type": "text", "text": {"content": "Item 1"}}] } }
To-do
{ "type": "to_do", "to_do": { "rich_text": [{"type": "text", "text": {"content": "Task"}}], "checked": False } }
Toggle
{ "type": "toggle", "toggle": { "rich_text": [{"type": "text", "text": {"content": "Toggle header"}}], "children": [] # Nested blocks } }
Code block
{ "type": "code", "code": { "rich_text": [{"type": "text", "text": {"content": "print('hello')"}}], "language": "python" } }
Quote
{ "type": "quote", "quote": { "rich_text": [{"type": "text", "text": {"content": "Quote text"}}] } }
Callout
{ "type": "callout", "callout": { "rich_text": [{"type": "text", "text": {"content": "Important note"}}], "icon": {"emoji": "💡"} } }
Divider
{ "type": "divider", "divider": {} }
Table of contents
{ "type": "table_of_contents", "table_of_contents": {} }
Python - Block Operations:
Get page blocks (children)
blocks = notion.blocks.children.list(block_id="page-id") for block in blocks["results"]: print(f"Block type: {block['type']}")
Append blocks to page
notion.blocks.children.append( block_id="page-id", children=[ { "object": "block", "type": "heading_2", "heading_2": { "rich_text": [{"type": "text", "text": {"content": "New Section"}}] } }, { "object": "block", "type": "paragraph", "paragraph": { "rich_text": [ {"type": "text", "text": {"content": "Some "}}, {"type": "text", "text": {"content": "bold"}, "annotations": {"bold": True}}, {"type": "text", "text": {"content": " text."}} ] } }, { "object": "block", "type": "bulleted_list_item", "bulleted_list_item": { "rich_text": [{"type": "text", "text": {"content": "First item"}}] } }, { "object": "block", "type": "bulleted_list_item", "bulleted_list_item": { "rich_text": [{"type": "text", "text": {"content": "Second item"}}] } } ] )
Update block
notion.blocks.update( block_id="block-id", paragraph={ "rich_text": [{"type": "text", "text": {"content": "Updated content"}}] } )
Delete block
notion.blocks.delete(block_id="block-id")
Get all blocks recursively
def get_all_blocks(block_id): """Recursively get all blocks""" all_blocks = [] has_more = True start_cursor = None
while has_more:
response = notion.blocks.children.list(
block_id=block_id,
start_cursor=start_cursor,
page_size=100
)
for block in response["results"]:
all_blocks.append(block)
if block.get("has_children"):
children = get_all_blocks(block["id"])
all_blocks.extend(children)
has_more = response["has_more"]
start_cursor = response.get("next_cursor")
return all_blocks
all_blocks = get_all_blocks("page-id") print(f"Total blocks: {len(all_blocks)}")
- Rich Text Formatting
Rich Text Structure:
Basic text
{"type": "text", "text": {"content": "Plain text"}}
Styled text
{ "type": "text", "text": {"content": "Styled text"}, "annotations": { "bold": True, "italic": False, "strikethrough": False, "underline": False, "code": False, "color": "red" # default, gray, brown, orange, yellow, green, blue, purple, pink, red } }
Link
{ "type": "text", "text": { "content": "Click here", "link": {"url": "https://example.com"} } }
Mention user
{ "type": "mention", "mention": { "type": "user", "user": {"id": "user-id"} } }
Mention page
{ "type": "mention", "mention": { "type": "page", "page": {"id": "page-id"} } }
Mention date
{ "type": "mention", "mention": { "type": "date", "date": {"start": "2025-01-17"} } }
Equation
{ "type": "equation", "equation": {"expression": "E = mc^2"} }
Python - Rich Text Helper:
def create_rich_text(text, bold=False, italic=False, code=False, color="default", link=None): """Helper to create rich text objects""" rt = { "type": "text", "text": {"content": text}, "annotations": { "bold": bold, "italic": italic, "strikethrough": False, "underline": False, "code": code, "color": color } } if link: rt["text"]["link"] = {"url": link} return rt
Usage
paragraph_content = [ create_rich_text("This is "), create_rich_text("bold", bold=True), create_rich_text(" and "), create_rich_text("italic", italic=True), create_rich_text(" text with a "), create_rich_text("link", link="https://example.com"), create_rich_text(".") ]
notion.blocks.children.append( block_id="page-id", children=[{ "type": "paragraph", "paragraph": {"rich_text": paragraph_content} }] )
- Relations and Rollups
Create Related Databases:
Create Projects database
projects_db = notion.databases.create( parent={"type": "page_id", "page_id": "parent-page-id"}, title=[{"type": "text", "text": {"content": "Projects"}}], properties={ "Name": {"title": {}}, "Status": { "select": { "options": [ {"name": "Active", "color": "green"}, {"name": "Completed", "color": "gray"} ] } } } )
Create Tasks database with relation to Projects
tasks_db = notion.databases.create( parent={"type": "page_id", "page_id": "parent-page-id"}, title=[{"type": "text", "text": {"content": "Tasks"}}], properties={ "Name": {"title": {}}, "Status": { "select": { "options": [ {"name": "To Do", "color": "gray"}, {"name": "Done", "color": "green"} ] } }, "Project": { "relation": { "database_id": projects_db["id"], "single_property": {} } } } )
Add rollup to Projects for task count
notion.databases.update( database_id=projects_db["id"], properties={ "Task Count": { "rollup": { "relation_property_name": "Tasks", # This is auto-created "rollup_property_name": "Name", "function": "count" } } } )
Create task linked to project
notion.pages.create( parent={"database_id": tasks_db["id"]}, properties={ "Name": {"title": [{"text": {"content": "Task 1"}}]}, "Status": {"select": {"name": "To Do"}}, "Project": {"relation": [{"id": "project-page-id"}]} } )
- Search API
Search Operations:
Search all
results = notion.search() print(f"Total accessible items: {len(results['results'])}")
Search with query
results = notion.search(query="project plan") for item in results["results"]: obj_type = item["object"] if obj_type == "page": title = item["properties"].get("title", {}).get("title", [{}])[0].get("plain_text", "Untitled") elif obj_type == "database": title = item["title"][0]["plain_text"] if item["title"] else "Untitled" print(f"{obj_type}: {title}")
Search only pages
results = notion.search( query="meeting", filter={"property": "object", "value": "page"} )
Search only databases
results = notion.search( filter={"property": "object", "value": "database"} )
Search with sorting
results = notion.search( query="report", sort={ "direction": "descending", "timestamp": "last_edited_time" } )
Paginated search
def search_all(query=None, filter=None): """Search with pagination""" all_results = [] has_more = True start_cursor = None
while has_more:
response = notion.search(
query=query,
filter=filter,
start_cursor=start_cursor,
page_size=100
)
all_results.extend(response["results"])
has_more = response["has_more"]
start_cursor = response.get("next_cursor")
return all_results
Complete Examples
Example 1: Task Management System
#!/usr/bin/env python3 """notion_tasks.py - Complete task management with Notion"""
from notion_client import Client from datetime import datetime, timedelta import os
notion = Client(auth=os.environ["NOTION_API_KEY"])
class NotionTaskManager: def init(self, database_id): self.database_id = database_id
def create_task(self, name, status="To Do", priority="Medium",
due_date=None, tags=None, notes=None):
"""Create a new task"""
properties = {
"Name": {"title": [{"text": {"content": name}}]},
"Status": {"select": {"name": status}},
"Priority": {"select": {"name": priority}}
}
if due_date:
properties["Due Date"] = {"date": {"start": due_date}}
if tags:
properties["Tags"] = {
"multi_select": [{"name": tag} for tag in tags]
}
if notes:
properties["Notes"] = {
"rich_text": [{"text": {"content": notes}}]
}
return notion.pages.create(
parent={"database_id": self.database_id},
properties=properties
)
def get_tasks_by_status(self, status):
"""Get all tasks with given status"""
return notion.databases.query(
database_id=self.database_id,
filter={
"property": "Status",
"select": {"equals": status}
}
)
def get_overdue_tasks(self):
"""Get all overdue tasks"""
today = datetime.now().strftime("%Y-%m-%d")
return notion.databases.query(
database_id=self.database_id,
filter={
"and": [
{"property": "Due Date", "date": {"before": today}},
{"property": "Status", "select": {"does_not_equal": "Done"}}
]
}
)
def get_high_priority_tasks(self):
"""Get high priority incomplete tasks"""
return notion.databases.query(
database_id=self.database_id,
filter={
"and": [
{"property": "Priority", "select": {"equals": "High"}},
{"property": "Status", "select": {"does_not_equal": "Done"}}
]
},
sorts=[
{"property": "Due Date", "direction": "ascending"}
]
)
def complete_task(self, page_id):
"""Mark task as done"""
return notion.pages.update(
page_id=page_id,
properties={
"Status": {"select": {"name": "Done"}},
"Completed": {"checkbox": True}
}
)
def update_task_status(self, page_id, status):
"""Update task status"""
return notion.pages.update(
page_id=page_id,
properties={
"Status": {"select": {"name": status}}
}
)
def get_weekly_summary(self):
"""Get summary for the week"""
week_ago = (datetime.now() - timedelta(days=7)).strftime("%Y-%m-%d")
# Completed this week
completed = notion.databases.query(
database_id=self.database_id,
filter={
"and": [
{"property": "Status", "select": {"equals": "Done"}},
{"property": "Last edited time", "date": {"after": week_ago}}
]
}
)
# Due this week
next_week = (datetime.now() + timedelta(days=7)).strftime("%Y-%m-%d")
upcoming = notion.databases.query(
database_id=self.database_id,
filter={
"and": [
{"property": "Due Date", "date": {"on_or_before": next_week}},
{"property": "Status", "select": {"does_not_equal": "Done"}}
]
}
)
return {
"completed_count": len(completed["results"]),
"upcoming_count": len(upcoming["results"]),
"completed": completed["results"],
"upcoming": upcoming["results"]
}
Usage
if name == "main": tm = NotionTaskManager("your-database-id")
# Create task
task = tm.create_task(
name="Review Q1 report",
priority="High",
due_date="2025-01-20",
tags=["work", "quarterly"],
notes="Review and provide feedback"
)
print(f"Created task: {task['id']}")
# Get overdue tasks
overdue = tm.get_overdue_tasks()
print(f"\nOverdue tasks: {len(overdue['results'])}")
for t in overdue["results"]:
name = t["properties"]["Name"]["title"][0]["plain_text"]
print(f" - {name}")
# Weekly summary
summary = tm.get_weekly_summary()
print(f"\nWeekly Summary:")
print(f" Completed: {summary['completed_count']}")
print(f" Upcoming: {summary['upcoming_count']}")
Example 2: Content Management System
#!/usr/bin/env python3 """notion_cms.py - Content management with Notion"""
from notion_client import Client from datetime import datetime import os
notion = Client(auth=os.environ["NOTION_API_KEY"])
class NotionCMS: def init(self, content_db_id, categories_db_id=None): self.content_db_id = content_db_id self.categories_db_id = categories_db_id
def create_article(self, title, content_blocks, status="Draft",
category=None, tags=None, author=None):
"""Create a new article"""
properties = {
"Title": {"title": [{"text": {"content": title}}]},
"Status": {"select": {"name": status}},
"Created": {"date": {"start": datetime.now().isoformat()}}
}
if category:
properties["Category"] = {"select": {"name": category}}
if tags:
properties["Tags"] = {
"multi_select": [{"name": tag} for tag in tags]
}
if author:
properties["Author"] = {
"rich_text": [{"text": {"content": author}}]
}
return notion.pages.create(
parent={"database_id": self.content_db_id},
properties=properties,
children=content_blocks
)
def create_article_with_template(self, title, template="blog"):
"""Create article from template"""
templates = {
"blog": [
{"type": "heading_2", "heading_2": {"rich_text": [{"text": {"content": "Introduction"}}]}},
{"type": "paragraph", "paragraph": {"rich_text": [{"text": {"content": ""}}]}},
{"type": "heading_2", "heading_2": {"rich_text": [{"text": {"content": "Main Content"}}]}},
{"type": "paragraph", "paragraph": {"rich_text": [{"text": {"content": ""}}]}},
{"type": "heading_2", "heading_2": {"rich_text": [{"text": {"content": "Conclusion"}}]}},
{"type": "paragraph", "paragraph": {"rich_text": [{"text": {"content": ""}}]}},
],
"tutorial": [
{"type": "callout", "callout": {
"rich_text": [{"text": {"content": "Prerequisites: "}}],
"icon": {"emoji": "📋"}
}},
{"type": "heading_2", "heading_2": {"rich_text": [{"text": {"content": "Overview"}}]}},
{"type": "paragraph", "paragraph": {"rich_text": [{"text": {"content": ""}}]}},
{"type": "heading_2", "heading_2": {"rich_text": [{"text": {"content": "Step 1"}}]}},
{"type": "paragraph", "paragraph": {"rich_text": [{"text": {"content": ""}}]}},
{"type": "code", "code": {"rich_text": [{"text": {"content": "# code here"}}], "language": "python"}},
{"type": "heading_2", "heading_2": {"rich_text": [{"text": {"content": "Step 2"}}]}},
{"type": "paragraph", "paragraph": {"rich_text": [{"text": {"content": ""}}]}},
{"type": "heading_2", "heading_2": {"rich_text": [{"text": {"content": "Summary"}}]}},
{"type": "paragraph", "paragraph": {"rich_text": [{"text": {"content": ""}}]}},
]
}
return self.create_article(
title=title,
content_blocks=templates.get(template, templates["blog"]),
status="Draft"
)
def get_published_articles(self, category=None, limit=10):
"""Get published articles"""
filter_conditions = [
{"property": "Status", "select": {"equals": "Published"}}
]
if category:
filter_conditions.append(
{"property": "Category", "select": {"equals": category}}
)
return notion.databases.query(
database_id=self.content_db_id,
filter={"and": filter_conditions} if len(filter_conditions) > 1 else filter_conditions[0],
sorts=[{"property": "Created", "direction": "descending"}],
page_size=limit
)
def publish_article(self, page_id):
"""Publish an article"""
return notion.pages.update(
page_id=page_id,
properties={
"Status": {"select": {"name": "Published"}},
"Published Date": {"date": {"start": datetime.now().isoformat()}}
}
)
def export_article_to_markdown(self, page_id):
"""Export article content to markdown"""
page = notion.pages.retrieve(page_id)
blocks = notion.blocks.children.list(block_id=page_id)
title = page["properties"]["Title"]["title"][0]["plain_text"]
markdown = f"# {title}\n\n"
for block in blocks["results"]:
markdown += self._block_to_markdown(block)
return markdown
def _block_to_markdown(self, block):
"""Convert block to markdown"""
block_type = block["type"]
if block_type == "paragraph":
text = self._rich_text_to_markdown(block["paragraph"]["rich_text"])
return f"{text}\n\n"
elif block_type == "heading_1":
text = self._rich_text_to_markdown(block["heading_1"]["rich_text"])
return f"# {text}\n\n"
elif block_type == "heading_2":
text = self._rich_text_to_markdown(block["heading_2"]["rich_text"])
return f"## {text}\n\n"
elif block_type == "heading_3":
text = self._rich_text_to_markdown(block["heading_3"]["rich_text"])
return f"### {text}\n\n"
elif block_type == "bulleted_list_item":
text = self._rich_text_to_markdown(block["bulleted_list_item"]["rich_text"])
return f"- {text}\n"
elif block_type == "numbered_list_item":
text = self._rich_text_to_markdown(block["numbered_list_item"]["rich_text"])
return f"1. {text}\n"
elif block_type == "code":
text = self._rich_text_to_markdown(block["code"]["rich_text"])
lang = block["code"]["language"]
return f"```{lang}\n{text}\n```\n\n"
elif block_type == "quote":
text = self._rich_text_to_markdown(block["quote"]["rich_text"])
return f"> {text}\n\n"
elif block_type == "divider":
return "---\n\n"
return ""
def _rich_text_to_markdown(self, rich_text):
"""Convert rich text array to markdown"""
result = ""
for rt in rich_text:
text = rt.get("text", {}).get("content", "")
annotations = rt.get("annotations", {})
if annotations.get("bold"):
text = f"**{text}**"
if annotations.get("italic"):
text = f"*{text}*"
if annotations.get("code"):
text = f"`{text}`"
if annotations.get("strikethrough"):
text = f"~~{text}~~"
link = rt.get("text", {}).get("link")
if link:
text = f"[{text}]({link['url']})"
result += text
return result
Usage
if name == "main": cms = NotionCMS("your-content-database-id")
# Create article from template
article = cms.create_article_with_template(
title="Getting Started with Python",
template="tutorial"
)
print(f"Created article: {article['id']}")
# Get published articles
published = cms.get_published_articles(limit=5)
print(f"\nPublished articles: {len(published['results'])}")
# Export to markdown
md = cms.export_article_to_markdown("article-page-id")
print(md)
Example 3: Project Dashboard
#!/usr/bin/env python3 """notion_dashboard.py - Project dashboard with Notion"""
from notion_client import Client from datetime import datetime, timedelta import os
notion = Client(auth=os.environ["NOTION_API_KEY"])
def generate_project_dashboard(projects_db_id, tasks_db_id): """Generate project dashboard data"""
# Get all active projects
projects = notion.databases.query(
database_id=projects_db_id,
filter={
"property": "Status",
"select": {"equals": "Active"}
}
)
dashboard_data = []
for project in projects["results"]:
project_id = project["id"]
project_name = project["properties"]["Name"]["title"][0]["plain_text"]
# Get tasks for this project
tasks = notion.databases.query(
database_id=tasks_db_id,
filter={
"property": "Project",
"relation": {"contains": project_id}
}
)
# Calculate metrics
total_tasks = len(tasks["results"])
completed = sum(1 for t in tasks["results"]
if t["properties"]["Status"]["select"]["name"] == "Done")
in_progress = sum(1 for t in tasks["results"]
if t["properties"]["Status"]["select"]["name"] == "In Progress")
# Get overdue tasks
today = datetime.now().strftime("%Y-%m-%d")
overdue = sum(1 for t in tasks["results"]
if t["properties"].get("Due Date", {}).get("date", {}).get("start", "9999-99-99") < today
and t["properties"]["Status"]["select"]["name"] != "Done")
dashboard_data.append({
"project_id": project_id,
"project_name": project_name,
"total_tasks": total_tasks,
"completed": completed,
"in_progress": in_progress,
"pending": total_tasks - completed - in_progress,
"overdue": overdue,
"completion_rate": round(completed / total_tasks * 100, 1) if total_tasks > 0 else 0
})
return dashboard_data
def create_dashboard_page(parent_page_id, dashboard_data): """Create a dashboard page with metrics"""
# Build blocks for dashboard
blocks = [
{
"type": "heading_1",
"heading_1": {
"rich_text": [{"text": {"content": f"Project Dashboard - {datetime.now().strftime('%Y-%m-%d')}"}}]
}
},
{
"type": "divider",
"divider": {}
}
]
for project in dashboard_data:
blocks.extend([
{
"type": "heading_2",
"heading_2": {
"rich_text": [{"text": {"content": project["project_name"]}}]
}
},
{
"type": "callout",
"callout": {
"rich_text": [{
"text": {
"content": f"Completion: {project['completion_rate']}% | "
f"Tasks: {project['completed']}/{project['total_tasks']} | "
f"In Progress: {project['in_progress']} | "
f"Overdue: {project['overdue']}"
}
}],
"icon": {"emoji": "📊"}
}
}
])
# Add progress bar representation
progress = int(project['completion_rate'] / 10)
progress_bar = "🟩" * progress + "⬜" * (10 - progress)
blocks.append({
"type": "paragraph",
"paragraph": {
"rich_text": [{"text": {"content": f"Progress: {progress_bar}"}}]
}
})
# Create the page
return notion.pages.create(
parent={"page_id": parent_page_id},
properties={
"title": {"title": [{"text": {"content": "Project Dashboard"}}]}
},
children=blocks
)
Usage
if name == "main": data = generate_project_dashboard( projects_db_id="projects-database-id", tasks_db_id="tasks-database-id" )
print("Project Dashboard")
print("=" * 50)
for project in data:
print(f"\n{project['project_name']}")
print(f" Completion: {project['completion_rate']}%")
print(f" Tasks: {project['completed']}/{project['total_tasks']}")
print(f" In Progress: {project['in_progress']}")
print(f" Overdue: {project['overdue']}")
Integration Examples
Integration with Slack
#!/usr/bin/env python3 """slack_notion.py - Sync Slack messages to Notion"""
import os import requests from notion_client import Client
notion = Client(auth=os.environ["NOTION_API_KEY"]) SLACK_WEBHOOK = os.environ["SLACK_WEBHOOK_URL"]
def notify_slack_on_page_update(page_id, message): """Send Slack notification when Notion page is updated""" page = notion.pages.retrieve(page_id) title = page["properties"]["Name"]["title"][0]["plain_text"]
payload = {
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": f"*Notion Update*: {title}\n{message}"
}
},
{
"type": "actions",
"elements": [{
"type": "button",
"text": {"type": "plain_text", "text": "View in Notion"},
"url": page["url"]
}]
}
]
}
requests.post(SLACK_WEBHOOK, json=payload)
def create_notion_page_from_slack(database_id, title, content, channel): """Create Notion page from Slack message""" return notion.pages.create( parent={"database_id": database_id}, properties={ "Name": {"title": [{"text": {"content": title}}]}, "Source": {"select": {"name": "Slack"}}, "Channel": {"rich_text": [{"text": {"content": channel}}]} }, children=[{ "type": "paragraph", "paragraph": { "rich_text": [{"text": {"content": content}}] } }] )
Integration with GitHub
#!/usr/bin/env python3 """github_notion.py - Sync GitHub issues to Notion"""
import os import requests from notion_client import Client
notion = Client(auth=os.environ["NOTION_API_KEY"]) GITHUB_TOKEN = os.environ["GITHUB_TOKEN"]
def sync_github_issues_to_notion(repo, database_id): """Sync GitHub issues to Notion database""" # Fetch issues from GitHub response = requests.get( f"https://api.github.com/repos/{repo}/issues", headers={"Authorization": f"token {GITHUB_TOKEN}"} ) issues = response.json()
for issue in issues:
# Check if issue already exists in Notion
existing = notion.databases.query(
database_id=database_id,
filter={
"property": "GitHub ID",
"number": {"equals": issue["number"]}
}
)
properties = {
"Name": {"title": [{"text": {"content": issue["title"]}}]},
"GitHub ID": {"number": issue["number"]},
"Status": {
"select": {"name": "Open" if issue["state"] == "open" else "Closed"}
},
"URL": {"url": issue["html_url"]},
"Labels": {
"multi_select": [{"name": l["name"]} for l in issue["labels"]]
}
}
if existing["results"]:
# Update existing
notion.pages.update(
page_id=existing["results"][0]["id"],
properties=properties
)
else:
# Create new
notion.pages.create(
parent={"database_id": database_id},
properties=properties,
children=[{
"type": "paragraph",
"paragraph": {
"rich_text": [{"text": {"content": issue.get("body", "") or ""}}]
}
}]
)
print(f"Synced {len(issues)} issues from {repo}")
Best Practices
- Rate Limiting
import time from functools import wraps
def rate_limit(calls_per_second=3): """Decorator to rate limit API calls""" min_interval = 1.0 / calls_per_second last_called = [0.0]
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
elapsed = time.time() - last_called[0]
wait_time = min_interval - elapsed
if wait_time > 0:
time.sleep(wait_time)
result = func(*args, **kwargs)
last_called[0] = time.time()
return result
return wrapper
return decorator
Usage
@rate_limit(calls_per_second=3) def api_call(func, *args, **kwargs): return func(*args, **kwargs)
- Error Handling
from notion_client import APIResponseError
def safe_notion_call(func, *args, max_retries=3, **kwargs): """Execute Notion API call with retry logic""" for attempt in range(max_retries): try: return func(*args, **kwargs) except APIResponseError as e: if e.status == 429: # Rate limited wait_time = int(e.headers.get("Retry-After", 60)) print(f"Rate limited. Waiting {wait_time}s...") time.sleep(wait_time) elif e.status >= 500: # Server error, retry time.sleep(2 ** attempt) else: raise except Exception as e: if attempt < max_retries - 1: time.sleep(2 ** attempt) else: raise
raise Exception(f"Failed after {max_retries} retries")
3. Batch Operations
def batch_create_pages(database_id, pages_data, batch_size=10): """Create pages in batches to avoid rate limits""" results = [] for i in range(0, len(pages_data), batch_size): batch = pages_data[i:i + batch_size] for page_data in batch: result = notion.pages.create( parent={"database_id": database_id}, properties=page_data["properties"], children=page_data.get("children", []) ) results.append(result) if i + batch_size < len(pages_data): time.sleep(1) # Brief pause between batches return results
- Caching
import json from pathlib import Path from datetime import datetime, timedelta
CACHE_DIR = Path.home() / ".cache" / "notion" CACHE_TTL = timedelta(minutes=5)
def get_cached_or_fetch(key, fetch_func, ttl=CACHE_TTL): """Get from cache or fetch fresh data""" CACHE_DIR.mkdir(parents=True, exist_ok=True) cache_file = CACHE_DIR / f"{key}.json"
if cache_file.exists():
data = json.loads(cache_file.read_text())
cached_at = datetime.fromisoformat(data["cached_at"])
if datetime.now() - cached_at < ttl:
return data["value"]
value = fetch_func()
cache_data = {
"cached_at": datetime.now().isoformat(),
"value": value
}
cache_file.write_text(json.dumps(cache_data, default=str))
return value
Troubleshooting
Common Issues
Issue: 401 Unauthorized
Verify API key
curl -s "https://api.notion.com/v1/users/me"
-H "Authorization: Bearer $NOTION_API_KEY"
-H "Notion-Version: 2022-06-28"
Check if integration is connected to the page/database
Go to page > ... menu > Connections > Your integration
Issue: 404 Not Found
Ensure integration has access to the page
The integration must be explicitly connected to each page
For databases, check the database ID is correct
Database ID format: 32 hex characters (with or without hyphens)
Issue: 400 Bad Request
Check property names match exactly (case-sensitive)
Verify property types match the database schema
Common mistakes:
- Using "title" instead of actual title property name
- Wrong select/multi_select option names
- Invalid date format (use ISO 8601: "2025-01-17")
Issue: Rate limiting (429)
Notion allows ~3 requests/second
Implement exponential backoff
Check Retry-After header for wait time
Version History
Version Date Changes
1.0.0 2025-01-17 Initial release with comprehensive Notion API coverage
Resources
-
Notion API Documentation
-
API Reference
-
Python SDK
-
JavaScript SDK
-
Integration Gallery
-
API Changelog
This skill enables powerful workspace automation through Notion's comprehensive API, supporting databases, pages, blocks, queries, and integration patterns.