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
markdown11. Go to https://www.notion.so/my-integrations 22. Click "New integration" 33. Name: "My Integration" 44. Select workspace 55. Set capabilities (Read/Write content, etc.) 66. Copy the "Internal Integration Token"
Connect Integration to Pages
markdown11. Open the Notion page/database you want to access 22. Click "..." menu (top right) 33. Click "Connections" > "Connect to" > Your integration 44. Integration can now access this page and children
Environment Setup
bash1# Set environment variable 2export NOTION_API_KEY="secret_xxxxxxxxxxxxxxxxxxxxx" 3 4# Verify connection 5curl -s "https://api.notion.com/v1/users/me" \ 6 -H "Authorization: Bearer $NOTION_API_KEY" \ 7 -H "Notion-Version: 2022-06-28" | jq
Python SDK Installation
bash1# Install official Python client 2pip install notion-client 3 4# Or with uv 5uv pip install notion-client 6 7# Additional dependencies 8pip install python-dotenv requests
Verify Setup
python1from notion_client import Client 2import os 3 4notion = Client(auth=os.environ["NOTION_API_KEY"]) 5 6# Test connection 7me = notion.users.me() 8print(f"Connected as: {me['name']}") 9 10# List accessible databases 11databases = notion.search(filter={"property": "object", "value": "database"}) 12print(f"Found {len(databases['results'])} databases")
Core Capabilities
1. Database Operations
List and Search Databases:
bash1# Search for databases 2curl -s -X POST "https://api.notion.com/v1/search" \ 3 -H "Authorization: Bearer $NOTION_API_KEY" \ 4 -H "Notion-Version: 2022-06-28" \ 5 -H "Content-Type: application/json" \ 6 -d '{ 7 "filter": { 8 "property": "object", 9 "value": "database" 10 } 11 }' | jq '.results[] | {id: .id, title: .title[0].plain_text}' 12 13# Get database schema 14curl -s "https://api.notion.com/v1/databases/DATABASE_ID" \ 15 -H "Authorization: Bearer $NOTION_API_KEY" \ 16 -H "Notion-Version: 2022-06-28" | jq '.properties'
Python - Database Operations:
python1from notion_client import Client 2import os 3 4notion = Client(auth=os.environ["NOTION_API_KEY"]) 5 6# Search for databases 7results = notion.search( 8 filter={"property": "object", "value": "database"} 9) 10 11for db in results["results"]: 12 title = db["title"][0]["plain_text"] if db["title"] else "Untitled" 13 print(f"Database: {title} (ID: {db['id']})") 14 15# Get database details 16database = notion.databases.retrieve(database_id="your-database-id") 17print(f"Properties: {list(database['properties'].keys())}") 18 19# Create database 20new_db = notion.databases.create( 21 parent={"type": "page_id", "page_id": "parent-page-id"}, 22 title=[{"type": "text", "text": {"content": "Tasks Database"}}], 23 properties={ 24 "Name": {"title": {}}, 25 "Status": { 26 "select": { 27 "options": [ 28 {"name": "To Do", "color": "gray"}, 29 {"name": "In Progress", "color": "blue"}, 30 {"name": "Done", "color": "green"} 31 ] 32 } 33 }, 34 "Priority": { 35 "select": { 36 "options": [ 37 {"name": "High", "color": "red"}, 38 {"name": "Medium", "color": "yellow"}, 39 {"name": "Low", "color": "gray"} 40 ] 41 } 42 }, 43 "Due Date": {"date": {}}, 44 "Assignee": {"people": {}}, 45 "Tags": {"multi_select": {"options": []}}, 46 "Completed": {"checkbox": {}}, 47 "Notes": {"rich_text": {}} 48 } 49) 50print(f"Created database: {new_db['id']}") 51 52# Update database 53notion.databases.update( 54 database_id="your-database-id", 55 title=[{"type": "text", "text": {"content": "Updated Title"}}], 56 properties={ 57 "New Property": {"rich_text": {}} 58 } 59)
2. Query Databases
Basic Query:
bash1# Query all items 2curl -s -X POST "https://api.notion.com/v1/databases/DATABASE_ID/query" \ 3 -H "Authorization: Bearer $NOTION_API_KEY" \ 4 -H "Notion-Version: 2022-06-28" \ 5 -H "Content-Type: application/json" \ 6 -d '{}' | jq '.results' 7 8# Query with filter 9curl -s -X POST "https://api.notion.com/v1/databases/DATABASE_ID/query" \ 10 -H "Authorization: Bearer $NOTION_API_KEY" \ 11 -H "Notion-Version: 2022-06-28" \ 12 -H "Content-Type: application/json" \ 13 -d '{ 14 "filter": { 15 "property": "Status", 16 "select": { 17 "equals": "In Progress" 18 } 19 } 20 }' | jq '.results' 21 22# Query with sort 23curl -s -X POST "https://api.notion.com/v1/databases/DATABASE_ID/query" \ 24 -H "Authorization: Bearer $NOTION_API_KEY" \ 25 -H "Notion-Version: 2022-06-28" \ 26 -H "Content-Type: application/json" \ 27 -d '{ 28 "sorts": [ 29 { 30 "property": "Due Date", 31 "direction": "ascending" 32 } 33 ] 34 }' | jq '.results'
Python - Query Operations:
python1# Simple query 2results = notion.databases.query(database_id="your-database-id") 3for page in results["results"]: 4 props = page["properties"] 5 name = props["Name"]["title"][0]["plain_text"] if props["Name"]["title"] else "Untitled" 6 print(f"- {name}") 7 8# Query with filter 9results = notion.databases.query( 10 database_id="your-database-id", 11 filter={ 12 "property": "Status", 13 "select": { 14 "equals": "In Progress" 15 } 16 } 17) 18 19# Query with multiple filters (AND) 20results = notion.databases.query( 21 database_id="your-database-id", 22 filter={ 23 "and": [ 24 { 25 "property": "Status", 26 "select": {"equals": "In Progress"} 27 }, 28 { 29 "property": "Priority", 30 "select": {"equals": "High"} 31 } 32 ] 33 } 34) 35 36# Query with OR filter 37results = notion.databases.query( 38 database_id="your-database-id", 39 filter={ 40 "or": [ 41 {"property": "Status", "select": {"equals": "To Do"}}, 42 {"property": "Status", "select": {"equals": "In Progress"}} 43 ] 44 } 45) 46 47# Query with sorting 48results = notion.databases.query( 49 database_id="your-database-id", 50 sorts=[ 51 {"property": "Priority", "direction": "ascending"}, 52 {"property": "Due Date", "direction": "ascending"} 53 ] 54) 55 56# Paginated query 57def query_all_pages(database_id, filter=None): 58 """Query all pages with pagination""" 59 all_results = [] 60 has_more = True 61 start_cursor = None 62 63 while has_more: 64 response = notion.databases.query( 65 database_id=database_id, 66 filter=filter, 67 start_cursor=start_cursor, 68 page_size=100 69 ) 70 all_results.extend(response["results"]) 71 has_more = response["has_more"] 72 start_cursor = response.get("next_cursor") 73 74 return all_results 75 76all_items = query_all_pages("your-database-id") 77print(f"Total items: {len(all_items)}")
3. Filter Syntax Reference
Text Filters:
python1# Text property filters 2{"property": "Name", "title": {"equals": "Exact Match"}} 3{"property": "Name", "title": {"does_not_equal": "Not This"}} 4{"property": "Name", "title": {"contains": "partial"}} 5{"property": "Name", "title": {"does_not_contain": "exclude"}} 6{"property": "Name", "title": {"starts_with": "Prefix"}} 7{"property": "Name", "title": {"ends_with": "suffix"}} 8{"property": "Name", "title": {"is_empty": True}} 9{"property": "Name", "title": {"is_not_empty": True}} 10 11# Rich text property 12{"property": "Notes", "rich_text": {"contains": "keyword"}}
Number Filters:
python1{"property": "Amount", "number": {"equals": 100}} 2{"property": "Amount", "number": {"does_not_equal": 0}} 3{"property": "Amount", "number": {"greater_than": 50}} 4{"property": "Amount", "number": {"less_than": 100}} 5{"property": "Amount", "number": {"greater_than_or_equal_to": 10}} 6{"property": "Amount", "number": {"less_than_or_equal_to": 99}} 7{"property": "Amount", "number": {"is_empty": True}} 8{"property": "Amount", "number": {"is_not_empty": True}}
Date Filters:
python1{"property": "Due Date", "date": {"equals": "2025-01-17"}} 2{"property": "Due Date", "date": {"before": "2025-01-20"}} 3{"property": "Due Date", "date": {"after": "2025-01-10"}} 4{"property": "Due Date", "date": {"on_or_before": "2025-01-17"}} 5{"property": "Due Date", "date": {"on_or_after": "2025-01-01"}} 6{"property": "Due Date", "date": {"is_empty": True}} 7{"property": "Due Date", "date": {"is_not_empty": True}} 8 9# Relative date filters 10{"property": "Due Date", "date": {"past_week": {}}} 11{"property": "Due Date", "date": {"past_month": {}}} 12{"property": "Due Date", "date": {"past_year": {}}} 13{"property": "Due Date", "date": {"next_week": {}}} 14{"property": "Due Date", "date": {"next_month": {}}} 15{"property": "Due Date", "date": {"next_year": {}}} 16{"property": "Due Date", "date": {"this_week": {}}}
Select/Multi-Select Filters:
python1# Select 2{"property": "Status", "select": {"equals": "Done"}} 3{"property": "Status", "select": {"does_not_equal": "Done"}} 4{"property": "Status", "select": {"is_empty": True}} 5{"property": "Status", "select": {"is_not_empty": True}} 6 7# Multi-select 8{"property": "Tags", "multi_select": {"contains": "urgent"}} 9{"property": "Tags", "multi_select": {"does_not_contain": "archived"}} 10{"property": "Tags", "multi_select": {"is_empty": True}} 11{"property": "Tags", "multi_select": {"is_not_empty": True}}
Checkbox Filters:
python1{"property": "Completed", "checkbox": {"equals": True}} 2{"property": "Completed", "checkbox": {"equals": False}}
Relation and Rollup Filters:
python1# Relation 2{"property": "Project", "relation": {"contains": "page-id"}} 3{"property": "Project", "relation": {"does_not_contain": "page-id"}} 4{"property": "Project", "relation": {"is_empty": True}} 5{"property": "Project", "relation": {"is_not_empty": True}} 6 7# Rollup (depends on rollup type) 8{"property": "Total Tasks", "rollup": {"number": {"greater_than": 5}}} 9{"property": "Completion", "rollup": {"number": {"equals": 100}}}
Compound Filters:
python1# AND 2{ 3 "and": [ 4 {"property": "Status", "select": {"equals": "In Progress"}}, 5 {"property": "Priority", "select": {"equals": "High"}}, 6 {"property": "Due Date", "date": {"before": "2025-02-01"}} 7 ] 8} 9 10# OR 11{ 12 "or": [ 13 {"property": "Status", "select": {"equals": "To Do"}}, 14 {"property": "Status", "select": {"equals": "In Progress"}} 15 ] 16} 17 18# Nested (AND with OR) 19{ 20 "and": [ 21 { 22 "or": [ 23 {"property": "Priority", "select": {"equals": "High"}}, 24 {"property": "Priority", "select": {"equals": "Medium"}} 25 ] 26 }, 27 {"property": "Status", "select": {"does_not_equal": "Done"}} 28 ] 29}
4. Page Operations
Create Pages:
bash1# Create page in database 2curl -s -X POST "https://api.notion.com/v1/pages" \ 3 -H "Authorization: Bearer $NOTION_API_KEY" \ 4 -H "Notion-Version: 2022-06-28" \ 5 -H "Content-Type: application/json" \ 6 -d '{ 7 "parent": {"database_id": "DATABASE_ID"}, 8 "properties": { 9 "Name": { 10 "title": [{"text": {"content": "New Task"}}] 11 }, 12 "Status": { 13 "select": {"name": "To Do"} 14 }, 15 "Priority": { 16 "select": {"name": "High"} 17 }, 18 "Due Date": { 19 "date": {"start": "2025-01-20"} 20 } 21 } 22 }' | jq
Python - Page Operations:
python1# Create page in database 2new_page = notion.pages.create( 3 parent={"database_id": "your-database-id"}, 4 properties={ 5 "Name": { 6 "title": [{"text": {"content": "New Task"}}] 7 }, 8 "Status": { 9 "select": {"name": "To Do"} 10 }, 11 "Priority": { 12 "select": {"name": "High"} 13 }, 14 "Due Date": { 15 "date": {"start": "2025-01-20", "end": "2025-01-25"} 16 }, 17 "Tags": { 18 "multi_select": [ 19 {"name": "development"}, 20 {"name": "urgent"} 21 ] 22 }, 23 "Assignee": { 24 "people": [{"id": "user-id"}] 25 }, 26 "Notes": { 27 "rich_text": [{"text": {"content": "Task description here"}}] 28 }, 29 "Completed": { 30 "checkbox": False 31 }, 32 "Amount": { 33 "number": 100 34 }, 35 "URL": { 36 "url": "https://example.com" 37 }, 38 "Email": { 39 "email": "user@example.com" 40 } 41 } 42) 43print(f"Created page: {new_page['id']}") 44 45# Create page with content (blocks) 46new_page = notion.pages.create( 47 parent={"database_id": "your-database-id"}, 48 properties={ 49 "Name": {"title": [{"text": {"content": "Page with Content"}}]} 50 }, 51 children=[ 52 { 53 "object": "block", 54 "type": "heading_2", 55 "heading_2": { 56 "rich_text": [{"type": "text", "text": {"content": "Overview"}}] 57 } 58 }, 59 { 60 "object": "block", 61 "type": "paragraph", 62 "paragraph": { 63 "rich_text": [{"type": "text", "text": {"content": "This is the content."}}] 64 } 65 }, 66 { 67 "object": "block", 68 "type": "to_do", 69 "to_do": { 70 "rich_text": [{"type": "text", "text": {"content": "Task item"}}], 71 "checked": False 72 } 73 } 74 ] 75) 76 77# Retrieve page 78page = notion.pages.retrieve(page_id="page-id") 79print(f"Page: {page['properties']['Name']['title'][0]['plain_text']}") 80 81# Update page properties 82notion.pages.update( 83 page_id="page-id", 84 properties={ 85 "Status": {"select": {"name": "Done"}}, 86 "Completed": {"checkbox": True} 87 } 88) 89 90# Archive page (soft delete) 91notion.pages.update( 92 page_id="page-id", 93 archived=True 94) 95 96# Restore page 97notion.pages.update( 98 page_id="page-id", 99 archived=False 100)
5. Block Operations
Block Types:
python1# Paragraph 2{ 3 "type": "paragraph", 4 "paragraph": { 5 "rich_text": [{"type": "text", "text": {"content": "Text content"}}] 6 } 7} 8 9# Headings 10{ 11 "type": "heading_1", 12 "heading_1": { 13 "rich_text": [{"type": "text", "text": {"content": "Heading 1"}}] 14 } 15} 16# Also: heading_2, heading_3 17 18# Bulleted list 19{ 20 "type": "bulleted_list_item", 21 "bulleted_list_item": { 22 "rich_text": [{"type": "text", "text": {"content": "List item"}}] 23 } 24} 25 26# Numbered list 27{ 28 "type": "numbered_list_item", 29 "numbered_list_item": { 30 "rich_text": [{"type": "text", "text": {"content": "Item 1"}}] 31 } 32} 33 34# To-do 35{ 36 "type": "to_do", 37 "to_do": { 38 "rich_text": [{"type": "text", "text": {"content": "Task"}}], 39 "checked": False 40 } 41} 42 43# Toggle 44{ 45 "type": "toggle", 46 "toggle": { 47 "rich_text": [{"type": "text", "text": {"content": "Toggle header"}}], 48 "children": [] # Nested blocks 49 } 50} 51 52# Code block 53{ 54 "type": "code", 55 "code": { 56 "rich_text": [{"type": "text", "text": {"content": "print('hello')"}}], 57 "language": "python" 58 } 59} 60 61# Quote 62{ 63 "type": "quote", 64 "quote": { 65 "rich_text": [{"type": "text", "text": {"content": "Quote text"}}] 66 } 67} 68 69# Callout 70{ 71 "type": "callout", 72 "callout": { 73 "rich_text": [{"type": "text", "text": {"content": "Important note"}}], 74 "icon": {"emoji": "💡"} 75 } 76} 77 78# Divider 79{ 80 "type": "divider", 81 "divider": {} 82} 83 84# Table of contents 85{ 86 "type": "table_of_contents", 87 "table_of_contents": {} 88}
Python - Block Operations:
python1# Get page blocks (children) 2blocks = notion.blocks.children.list(block_id="page-id") 3for block in blocks["results"]: 4 print(f"Block type: {block['type']}") 5 6# Append blocks to page 7notion.blocks.children.append( 8 block_id="page-id", 9 children=[ 10 { 11 "object": "block", 12 "type": "heading_2", 13 "heading_2": { 14 "rich_text": [{"type": "text", "text": {"content": "New Section"}}] 15 } 16 }, 17 { 18 "object": "block", 19 "type": "paragraph", 20 "paragraph": { 21 "rich_text": [ 22 {"type": "text", "text": {"content": "Some "}}, 23 {"type": "text", "text": {"content": "bold"}, "annotations": {"bold": True}}, 24 {"type": "text", "text": {"content": " text."}} 25 ] 26 } 27 }, 28 { 29 "object": "block", 30 "type": "bulleted_list_item", 31 "bulleted_list_item": { 32 "rich_text": [{"type": "text", "text": {"content": "First item"}}] 33 } 34 }, 35 { 36 "object": "block", 37 "type": "bulleted_list_item", 38 "bulleted_list_item": { 39 "rich_text": [{"type": "text", "text": {"content": "Second item"}}] 40 } 41 } 42 ] 43) 44 45# Update block 46notion.blocks.update( 47 block_id="block-id", 48 paragraph={ 49 "rich_text": [{"type": "text", "text": {"content": "Updated content"}}] 50 } 51) 52 53# Delete block 54notion.blocks.delete(block_id="block-id") 55 56# Get all blocks recursively 57def get_all_blocks(block_id): 58 """Recursively get all blocks""" 59 all_blocks = [] 60 has_more = True 61 start_cursor = None 62 63 while has_more: 64 response = notion.blocks.children.list( 65 block_id=block_id, 66 start_cursor=start_cursor, 67 page_size=100 68 ) 69 for block in response["results"]: 70 all_blocks.append(block) 71 if block.get("has_children"): 72 children = get_all_blocks(block["id"]) 73 all_blocks.extend(children) 74 has_more = response["has_more"] 75 start_cursor = response.get("next_cursor") 76 77 return all_blocks 78 79all_blocks = get_all_blocks("page-id") 80print(f"Total blocks: {len(all_blocks)}")
6. Rich Text Formatting
Rich Text Structure:
python1# Basic text 2{"type": "text", "text": {"content": "Plain text"}} 3 4# Styled text 5{ 6 "type": "text", 7 "text": {"content": "Styled text"}, 8 "annotations": { 9 "bold": True, 10 "italic": False, 11 "strikethrough": False, 12 "underline": False, 13 "code": False, 14 "color": "red" # default, gray, brown, orange, yellow, green, blue, purple, pink, red 15 } 16} 17 18# Link 19{ 20 "type": "text", 21 "text": { 22 "content": "Click here", 23 "link": {"url": "https://example.com"} 24 } 25} 26 27# Mention user 28{ 29 "type": "mention", 30 "mention": { 31 "type": "user", 32 "user": {"id": "user-id"} 33 } 34} 35 36# Mention page 37{ 38 "type": "mention", 39 "mention": { 40 "type": "page", 41 "page": {"id": "page-id"} 42 } 43} 44 45# Mention date 46{ 47 "type": "mention", 48 "mention": { 49 "type": "date", 50 "date": {"start": "2025-01-17"} 51 } 52} 53 54# Equation 55{ 56 "type": "equation", 57 "equation": {"expression": "E = mc^2"} 58}
Python - Rich Text Helper:
python1def create_rich_text(text, bold=False, italic=False, code=False, color="default", link=None): 2 """Helper to create rich text objects""" 3 rt = { 4 "type": "text", 5 "text": {"content": text}, 6 "annotations": { 7 "bold": bold, 8 "italic": italic, 9 "strikethrough": False, 10 "underline": False, 11 "code": code, 12 "color": color 13 } 14 } 15 if link: 16 rt["text"]["link"] = {"url": link} 17 return rt 18 19# Usage 20paragraph_content = [ 21 create_rich_text("This is "), 22 create_rich_text("bold", bold=True), 23 create_rich_text(" and "), 24 create_rich_text("italic", italic=True), 25 create_rich_text(" text with a "), 26 create_rich_text("link", link="https://example.com"), 27 create_rich_text(".") 28] 29 30notion.blocks.children.append( 31 block_id="page-id", 32 children=[{ 33 "type": "paragraph", 34 "paragraph": {"rich_text": paragraph_content} 35 }] 36)
7. Relations and Rollups
Create Related Databases:
python1# Create Projects database 2projects_db = notion.databases.create( 3 parent={"type": "page_id", "page_id": "parent-page-id"}, 4 title=[{"type": "text", "text": {"content": "Projects"}}], 5 properties={ 6 "Name": {"title": {}}, 7 "Status": { 8 "select": { 9 "options": [ 10 {"name": "Active", "color": "green"}, 11 {"name": "Completed", "color": "gray"} 12 ] 13 } 14 } 15 } 16) 17 18# Create Tasks database with relation to Projects 19tasks_db = notion.databases.create( 20 parent={"type": "page_id", "page_id": "parent-page-id"}, 21 title=[{"type": "text", "text": {"content": "Tasks"}}], 22 properties={ 23 "Name": {"title": {}}, 24 "Status": { 25 "select": { 26 "options": [ 27 {"name": "To Do", "color": "gray"}, 28 {"name": "Done", "color": "green"} 29 ] 30 } 31 }, 32 "Project": { 33 "relation": { 34 "database_id": projects_db["id"], 35 "single_property": {} 36 } 37 } 38 } 39) 40 41# Add rollup to Projects for task count 42notion.databases.update( 43 database_id=projects_db["id"], 44 properties={ 45 "Task Count": { 46 "rollup": { 47 "relation_property_name": "Tasks", # This is auto-created 48 "rollup_property_name": "Name", 49 "function": "count" 50 } 51 } 52 } 53) 54 55# Create task linked to project 56notion.pages.create( 57 parent={"database_id": tasks_db["id"]}, 58 properties={ 59 "Name": {"title": [{"text": {"content": "Task 1"}}]}, 60 "Status": {"select": {"name": "To Do"}}, 61 "Project": {"relation": [{"id": "project-page-id"}]} 62 } 63)
8. Search API
Search Operations:
python1# Search all 2results = notion.search() 3print(f"Total accessible items: {len(results['results'])}") 4 5# Search with query 6results = notion.search(query="project plan") 7for item in results["results"]: 8 obj_type = item["object"] 9 if obj_type == "page": 10 title = item["properties"].get("title", {}).get("title", [{}])[0].get("plain_text", "Untitled") 11 elif obj_type == "database": 12 title = item["title"][0]["plain_text"] if item["title"] else "Untitled" 13 print(f"{obj_type}: {title}") 14 15# Search only pages 16results = notion.search( 17 query="meeting", 18 filter={"property": "object", "value": "page"} 19) 20 21# Search only databases 22results = notion.search( 23 filter={"property": "object", "value": "database"} 24) 25 26# Search with sorting 27results = notion.search( 28 query="report", 29 sort={ 30 "direction": "descending", 31 "timestamp": "last_edited_time" 32 } 33) 34 35# Paginated search 36def search_all(query=None, filter=None): 37 """Search with pagination""" 38 all_results = [] 39 has_more = True 40 start_cursor = None 41 42 while has_more: 43 response = notion.search( 44 query=query, 45 filter=filter, 46 start_cursor=start_cursor, 47 page_size=100 48 ) 49 all_results.extend(response["results"]) 50 has_more = response["has_more"] 51 start_cursor = response.get("next_cursor") 52 53 return all_results
Complete Examples
Example 1: Task Management System
python1#!/usr/bin/env python3 2"""notion_tasks.py - Complete task management with Notion""" 3 4from notion_client import Client 5from datetime import datetime, timedelta 6import os 7 8notion = Client(auth=os.environ["NOTION_API_KEY"]) 9 10class NotionTaskManager: 11 def __init__(self, database_id): 12 self.database_id = database_id 13 14 def create_task(self, name, status="To Do", priority="Medium", 15 due_date=None, tags=None, notes=None): 16 """Create a new task""" 17 properties = { 18 "Name": {"title": [{"text": {"content": name}}]}, 19 "Status": {"select": {"name": status}}, 20 "Priority": {"select": {"name": priority}} 21 } 22 23 if due_date: 24 properties["Due Date"] = {"date": {"start": due_date}} 25 26 if tags: 27 properties["Tags"] = { 28 "multi_select": [{"name": tag} for tag in tags] 29 } 30 31 if notes: 32 properties["Notes"] = { 33 "rich_text": [{"text": {"content": notes}}] 34 } 35 36 return notion.pages.create( 37 parent={"database_id": self.database_id}, 38 properties=properties 39 ) 40 41 def get_tasks_by_status(self, status): 42 """Get all tasks with given status""" 43 return notion.databases.query( 44 database_id=self.database_id, 45 filter={ 46 "property": "Status", 47 "select": {"equals": status} 48 } 49 ) 50 51 def get_overdue_tasks(self): 52 """Get all overdue tasks""" 53 today = datetime.now().strftime("%Y-%m-%d") 54 return notion.databases.query( 55 database_id=self.database_id, 56 filter={ 57 "and": [ 58 {"property": "Due Date", "date": {"before": today}}, 59 {"property": "Status", "select": {"does_not_equal": "Done"}} 60 ] 61 } 62 ) 63 64 def get_high_priority_tasks(self): 65 """Get high priority incomplete tasks""" 66 return notion.databases.query( 67 database_id=self.database_id, 68 filter={ 69 "and": [ 70 {"property": "Priority", "select": {"equals": "High"}}, 71 {"property": "Status", "select": {"does_not_equal": "Done"}} 72 ] 73 }, 74 sorts=[ 75 {"property": "Due Date", "direction": "ascending"} 76 ] 77 ) 78 79 def complete_task(self, page_id): 80 """Mark task as done""" 81 return notion.pages.update( 82 page_id=page_id, 83 properties={ 84 "Status": {"select": {"name": "Done"}}, 85 "Completed": {"checkbox": True} 86 } 87 ) 88 89 def update_task_status(self, page_id, status): 90 """Update task status""" 91 return notion.pages.update( 92 page_id=page_id, 93 properties={ 94 "Status": {"select": {"name": status}} 95 } 96 ) 97 98 def get_weekly_summary(self): 99 """Get summary for the week""" 100 week_ago = (datetime.now() - timedelta(days=7)).strftime("%Y-%m-%d") 101 102 # Completed this week 103 completed = notion.databases.query( 104 database_id=self.database_id, 105 filter={ 106 "and": [ 107 {"property": "Status", "select": {"equals": "Done"}}, 108 {"property": "Last edited time", "date": {"after": week_ago}} 109 ] 110 } 111 ) 112 113 # Due this week 114 next_week = (datetime.now() + timedelta(days=7)).strftime("%Y-%m-%d") 115 upcoming = notion.databases.query( 116 database_id=self.database_id, 117 filter={ 118 "and": [ 119 {"property": "Due Date", "date": {"on_or_before": next_week}}, 120 {"property": "Status", "select": {"does_not_equal": "Done"}} 121 ] 122 } 123 ) 124 125 return { 126 "completed_count": len(completed["results"]), 127 "upcoming_count": len(upcoming["results"]), 128 "completed": completed["results"], 129 "upcoming": upcoming["results"] 130 } 131 132# Usage 133if __name__ == "__main__": 134 tm = NotionTaskManager("your-database-id") 135 136 # Create task 137 task = tm.create_task( 138 name="Review Q1 report", 139 priority="High", 140 due_date="2025-01-20", 141 tags=["work", "quarterly"], 142 notes="Review and provide feedback" 143 ) 144 print(f"Created task: {task['id']}") 145 146 # Get overdue tasks 147 overdue = tm.get_overdue_tasks() 148 print(f"\nOverdue tasks: {len(overdue['results'])}") 149 for t in overdue["results"]: 150 name = t["properties"]["Name"]["title"][0]["plain_text"] 151 print(f" - {name}") 152 153 # Weekly summary 154 summary = tm.get_weekly_summary() 155 print(f"\nWeekly Summary:") 156 print(f" Completed: {summary['completed_count']}") 157 print(f" Upcoming: {summary['upcoming_count']}")
Example 2: Content Management System
python1#!/usr/bin/env python3 2"""notion_cms.py - Content management with Notion""" 3 4from notion_client import Client 5from datetime import datetime 6import os 7 8notion = Client(auth=os.environ["NOTION_API_KEY"]) 9 10class NotionCMS: 11 def __init__(self, content_db_id, categories_db_id=None): 12 self.content_db_id = content_db_id 13 self.categories_db_id = categories_db_id 14 15 def create_article(self, title, content_blocks, status="Draft", 16 category=None, tags=None, author=None): 17 """Create a new article""" 18 properties = { 19 "Title": {"title": [{"text": {"content": title}}]}, 20 "Status": {"select": {"name": status}}, 21 "Created": {"date": {"start": datetime.now().isoformat()}} 22 } 23 24 if category: 25 properties["Category"] = {"select": {"name": category}} 26 27 if tags: 28 properties["Tags"] = { 29 "multi_select": [{"name": tag} for tag in tags] 30 } 31 32 if author: 33 properties["Author"] = { 34 "rich_text": [{"text": {"content": author}}] 35 } 36 37 return notion.pages.create( 38 parent={"database_id": self.content_db_id}, 39 properties=properties, 40 children=content_blocks 41 ) 42 43 def create_article_with_template(self, title, template="blog"): 44 """Create article from template""" 45 templates = { 46 "blog": [ 47 {"type": "heading_2", "heading_2": {"rich_text": [{"text": {"content": "Introduction"}}]}}, 48 {"type": "paragraph", "paragraph": {"rich_text": [{"text": {"content": ""}}]}}, 49 {"type": "heading_2", "heading_2": {"rich_text": [{"text": {"content": "Main Content"}}]}}, 50 {"type": "paragraph", "paragraph": {"rich_text": [{"text": {"content": ""}}]}}, 51 {"type": "heading_2", "heading_2": {"rich_text": [{"text": {"content": "Conclusion"}}]}}, 52 {"type": "paragraph", "paragraph": {"rich_text": [{"text": {"content": ""}}]}}, 53 ], 54 "tutorial": [ 55 {"type": "callout", "callout": { 56 "rich_text": [{"text": {"content": "Prerequisites: "}}], 57 "icon": {"emoji": "📋"} 58 }}, 59 {"type": "heading_2", "heading_2": {"rich_text": [{"text": {"content": "Overview"}}]}}, 60 {"type": "paragraph", "paragraph": {"rich_text": [{"text": {"content": ""}}]}}, 61 {"type": "heading_2", "heading_2": {"rich_text": [{"text": {"content": "Step 1"}}]}}, 62 {"type": "paragraph", "paragraph": {"rich_text": [{"text": {"content": ""}}]}}, 63 {"type": "code", "code": {"rich_text": [{"text": {"content": "# code here"}}], "language": "python"}}, 64 {"type": "heading_2", "heading_2": {"rich_text": [{"text": {"content": "Step 2"}}]}}, 65 {"type": "paragraph", "paragraph": {"rich_text": [{"text": {"content": ""}}]}}, 66 {"type": "heading_2", "heading_2": {"rich_text": [{"text": {"content": "Summary"}}]}}, 67 {"type": "paragraph", "paragraph": {"rich_text": [{"text": {"content": ""}}]}}, 68 ] 69 } 70 71 return self.create_article( 72 title=title, 73 content_blocks=templates.get(template, templates["blog"]), 74 status="Draft" 75 ) 76 77 def get_published_articles(self, category=None, limit=10): 78 """Get published articles""" 79 filter_conditions = [ 80 {"property": "Status", "select": {"equals": "Published"}} 81 ] 82 83 if category: 84 filter_conditions.append( 85 {"property": "Category", "select": {"equals": category}} 86 ) 87 88 return notion.databases.query( 89 database_id=self.content_db_id, 90 filter={"and": filter_conditions} if len(filter_conditions) > 1 else filter_conditions[0], 91 sorts=[{"property": "Created", "direction": "descending"}], 92 page_size=limit 93 ) 94 95 def publish_article(self, page_id): 96 """Publish an article""" 97 return notion.pages.update( 98 page_id=page_id, 99 properties={ 100 "Status": {"select": {"name": "Published"}}, 101 "Published Date": {"date": {"start": datetime.now().isoformat()}} 102 } 103 ) 104 105 def export_article_to_markdown(self, page_id): 106 """Export article content to markdown""" 107 page = notion.pages.retrieve(page_id) 108 blocks = notion.blocks.children.list(block_id=page_id) 109 110 title = page["properties"]["Title"]["title"][0]["plain_text"] 111 markdown = f"# {title}\n\n" 112 113 for block in blocks["results"]: 114 markdown += self._block_to_markdown(block) 115 116 return markdown 117 118 def _block_to_markdown(self, block): 119 """Convert block to markdown""" 120 block_type = block["type"] 121 122 if block_type == "paragraph": 123 text = self._rich_text_to_markdown(block["paragraph"]["rich_text"]) 124 return f"{text}\n\n" 125 elif block_type == "heading_1": 126 text = self._rich_text_to_markdown(block["heading_1"]["rich_text"]) 127 return f"# {text}\n\n" 128 elif block_type == "heading_2": 129 text = self._rich_text_to_markdown(block["heading_2"]["rich_text"]) 130 return f"## {text}\n\n" 131 elif block_type == "heading_3": 132 text = self._rich_text_to_markdown(block["heading_3"]["rich_text"]) 133 return f"### {text}\n\n" 134 elif block_type == "bulleted_list_item": 135 text = self._rich_text_to_markdown(block["bulleted_list_item"]["rich_text"]) 136 return f"- {text}\n" 137 elif block_type == "numbered_list_item": 138 text = self._rich_text_to_markdown(block["numbered_list_item"]["rich_text"]) 139 return f"1. {text}\n" 140 elif block_type == "code": 141 text = self._rich_text_to_markdown(block["code"]["rich_text"]) 142 lang = block["code"]["language"] 143 return f"```{lang}\n{text}\n```\n\n" 144 elif block_type == "quote": 145 text = self._rich_text_to_markdown(block["quote"]["rich_text"]) 146 return f"> {text}\n\n" 147 elif block_type == "divider": 148 return "---\n\n" 149 150 return "" 151 152 def _rich_text_to_markdown(self, rich_text): 153 """Convert rich text array to markdown""" 154 result = "" 155 for rt in rich_text: 156 text = rt.get("text", {}).get("content", "") 157 annotations = rt.get("annotations", {}) 158 159 if annotations.get("bold"): 160 text = f"**{text}**" 161 if annotations.get("italic"): 162 text = f"*{text}*" 163 if annotations.get("code"): 164 text = f"`{text}`" 165 if annotations.get("strikethrough"): 166 text = f"~~{text}~~" 167 168 link = rt.get("text", {}).get("link") 169 if link: 170 text = f"[{text}]({link['url']})" 171 172 result += text 173 return result 174 175# Usage 176if __name__ == "__main__": 177 cms = NotionCMS("your-content-database-id") 178 179 # Create article from template 180 article = cms.create_article_with_template( 181 title="Getting Started with Python", 182 template="tutorial" 183 ) 184 print(f"Created article: {article['id']}") 185 186 # Get published articles 187 published = cms.get_published_articles(limit=5) 188 print(f"\nPublished articles: {len(published['results'])}") 189 190 # Export to markdown 191 md = cms.export_article_to_markdown("article-page-id") 192 print(md)
Example 3: Project Dashboard
python1#!/usr/bin/env python3 2"""notion_dashboard.py - Project dashboard with Notion""" 3 4from notion_client import Client 5from datetime import datetime, timedelta 6import os 7 8notion = Client(auth=os.environ["NOTION_API_KEY"]) 9 10def generate_project_dashboard(projects_db_id, tasks_db_id): 11 """Generate project dashboard data""" 12 13 # Get all active projects 14 projects = notion.databases.query( 15 database_id=projects_db_id, 16 filter={ 17 "property": "Status", 18 "select": {"equals": "Active"} 19 } 20 ) 21 22 dashboard_data = [] 23 24 for project in projects["results"]: 25 project_id = project["id"] 26 project_name = project["properties"]["Name"]["title"][0]["plain_text"] 27 28 # Get tasks for this project 29 tasks = notion.databases.query( 30 database_id=tasks_db_id, 31 filter={ 32 "property": "Project", 33 "relation": {"contains": project_id} 34 } 35 ) 36 37 # Calculate metrics 38 total_tasks = len(tasks["results"]) 39 completed = sum(1 for t in tasks["results"] 40 if t["properties"]["Status"]["select"]["name"] == "Done") 41 in_progress = sum(1 for t in tasks["results"] 42 if t["properties"]["Status"]["select"]["name"] == "In Progress") 43 44 # Get overdue tasks 45 today = datetime.now().strftime("%Y-%m-%d") 46 overdue = sum(1 for t in tasks["results"] 47 if t["properties"].get("Due Date", {}).get("date", {}).get("start", "9999-99-99") < today 48 and t["properties"]["Status"]["select"]["name"] != "Done") 49 50 dashboard_data.append({ 51 "project_id": project_id, 52 "project_name": project_name, 53 "total_tasks": total_tasks, 54 "completed": completed, 55 "in_progress": in_progress, 56 "pending": total_tasks - completed - in_progress, 57 "overdue": overdue, 58 "completion_rate": round(completed / total_tasks * 100, 1) if total_tasks > 0 else 0 59 }) 60 61 return dashboard_data 62 63def create_dashboard_page(parent_page_id, dashboard_data): 64 """Create a dashboard page with metrics""" 65 66 # Build blocks for dashboard 67 blocks = [ 68 { 69 "type": "heading_1", 70 "heading_1": { 71 "rich_text": [{"text": {"content": f"Project Dashboard - {datetime.now().strftime('%Y-%m-%d')}"}}] 72 } 73 }, 74 { 75 "type": "divider", 76 "divider": {} 77 } 78 ] 79 80 for project in dashboard_data: 81 blocks.extend([ 82 { 83 "type": "heading_2", 84 "heading_2": { 85 "rich_text": [{"text": {"content": project["project_name"]}}] 86 } 87 }, 88 { 89 "type": "callout", 90 "callout": { 91 "rich_text": [{ 92 "text": { 93 "content": f"Completion: {project['completion_rate']}% | " 94 f"Tasks: {project['completed']}/{project['total_tasks']} | " 95 f"In Progress: {project['in_progress']} | " 96 f"Overdue: {project['overdue']}" 97 } 98 }], 99 "icon": {"emoji": "📊"} 100 } 101 } 102 ]) 103 104 # Add progress bar representation 105 progress = int(project['completion_rate'] / 10) 106 progress_bar = "🟩" * progress + "⬜" * (10 - progress) 107 blocks.append({ 108 "type": "paragraph", 109 "paragraph": { 110 "rich_text": [{"text": {"content": f"Progress: {progress_bar}"}}] 111 } 112 }) 113 114 # Create the page 115 return notion.pages.create( 116 parent={"page_id": parent_page_id}, 117 properties={ 118 "title": {"title": [{"text": {"content": "Project Dashboard"}}]} 119 }, 120 children=blocks 121 ) 122 123# Usage 124if __name__ == "__main__": 125 data = generate_project_dashboard( 126 projects_db_id="projects-database-id", 127 tasks_db_id="tasks-database-id" 128 ) 129 130 print("Project Dashboard") 131 print("=" * 50) 132 for project in data: 133 print(f"\n{project['project_name']}") 134 print(f" Completion: {project['completion_rate']}%") 135 print(f" Tasks: {project['completed']}/{project['total_tasks']}") 136 print(f" In Progress: {project['in_progress']}") 137 print(f" Overdue: {project['overdue']}")
Integration Examples
Integration with Slack
python1#!/usr/bin/env python3 2"""slack_notion.py - Sync Slack messages to Notion""" 3 4import os 5import requests 6from notion_client import Client 7 8notion = Client(auth=os.environ["NOTION_API_KEY"]) 9SLACK_WEBHOOK = os.environ["SLACK_WEBHOOK_URL"] 10 11def notify_slack_on_page_update(page_id, message): 12 """Send Slack notification when Notion page is updated""" 13 page = notion.pages.retrieve(page_id) 14 title = page["properties"]["Name"]["title"][0]["plain_text"] 15 16 payload = { 17 "blocks": [ 18 { 19 "type": "section", 20 "text": { 21 "type": "mrkdwn", 22 "text": f"*Notion Update*: {title}\n{message}" 23 } 24 }, 25 { 26 "type": "actions", 27 "elements": [{ 28 "type": "button", 29 "text": {"type": "plain_text", "text": "View in Notion"}, 30 "url": page["url"] 31 }] 32 } 33 ] 34 } 35 36 requests.post(SLACK_WEBHOOK, json=payload) 37 38def create_notion_page_from_slack(database_id, title, content, channel): 39 """Create Notion page from Slack message""" 40 return notion.pages.create( 41 parent={"database_id": database_id}, 42 properties={ 43 "Name": {"title": [{"text": {"content": title}}]}, 44 "Source": {"select": {"name": "Slack"}}, 45 "Channel": {"rich_text": [{"text": {"content": channel}}]} 46 }, 47 children=[{ 48 "type": "paragraph", 49 "paragraph": { 50 "rich_text": [{"text": {"content": content}}] 51 } 52 }] 53 )
Integration with GitHub
python1#!/usr/bin/env python3 2"""github_notion.py - Sync GitHub issues to Notion""" 3 4import os 5import requests 6from notion_client import Client 7 8notion = Client(auth=os.environ["NOTION_API_KEY"]) 9GITHUB_TOKEN = os.environ["GITHUB_TOKEN"] 10 11def sync_github_issues_to_notion(repo, database_id): 12 """Sync GitHub issues to Notion database""" 13 # Fetch issues from GitHub 14 response = requests.get( 15 f"https://api.github.com/repos/{repo}/issues", 16 headers={"Authorization": f"token {GITHUB_TOKEN}"} 17 ) 18 issues = response.json() 19 20 for issue in issues: 21 # Check if issue already exists in Notion 22 existing = notion.databases.query( 23 database_id=database_id, 24 filter={ 25 "property": "GitHub ID", 26 "number": {"equals": issue["number"]} 27 } 28 ) 29 30 properties = { 31 "Name": {"title": [{"text": {"content": issue["title"]}}]}, 32 "GitHub ID": {"number": issue["number"]}, 33 "Status": { 34 "select": {"name": "Open" if issue["state"] == "open" else "Closed"} 35 }, 36 "URL": {"url": issue["html_url"]}, 37 "Labels": { 38 "multi_select": [{"name": l["name"]} for l in issue["labels"]] 39 } 40 } 41 42 if existing["results"]: 43 # Update existing 44 notion.pages.update( 45 page_id=existing["results"][0]["id"], 46 properties=properties 47 ) 48 else: 49 # Create new 50 notion.pages.create( 51 parent={"database_id": database_id}, 52 properties=properties, 53 children=[{ 54 "type": "paragraph", 55 "paragraph": { 56 "rich_text": [{"text": {"content": issue.get("body", "") or ""}}] 57 } 58 }] 59 ) 60 61 print(f"Synced {len(issues)} issues from {repo}")
Best Practices
1. Rate Limiting
python1import time 2from functools import wraps 3 4def rate_limit(calls_per_second=3): 5 """Decorator to rate limit API calls""" 6 min_interval = 1.0 / calls_per_second 7 last_called = [0.0] 8 9 def decorator(func): 10 @wraps(func) 11 def wrapper(*args, **kwargs): 12 elapsed = time.time() - last_called[0] 13 wait_time = min_interval - elapsed 14 if wait_time > 0: 15 time.sleep(wait_time) 16 result = func(*args, **kwargs) 17 last_called[0] = time.time() 18 return result 19 return wrapper 20 return decorator 21 22# Usage 23@rate_limit(calls_per_second=3) 24def api_call(func, *args, **kwargs): 25 return func(*args, **kwargs)
2. Error Handling
python1from notion_client import APIResponseError 2 3def safe_notion_call(func, *args, max_retries=3, **kwargs): 4 """Execute Notion API call with retry logic""" 5 for attempt in range(max_retries): 6 try: 7 return func(*args, **kwargs) 8 except APIResponseError as e: 9 if e.status == 429: 10 # Rate limited 11 wait_time = int(e.headers.get("Retry-After", 60)) 12 print(f"Rate limited. Waiting {wait_time}s...") 13 time.sleep(wait_time) 14 elif e.status >= 500: 15 # Server error, retry 16 time.sleep(2 ** attempt) 17 else: 18 raise 19 except Exception as e: 20 if attempt < max_retries - 1: 21 time.sleep(2 ** attempt) 22 else: 23 raise 24 25 raise Exception(f"Failed after {max_retries} retries")
3. Batch Operations
python1def batch_create_pages(database_id, pages_data, batch_size=10): 2 """Create pages in batches to avoid rate limits""" 3 results = [] 4 for i in range(0, len(pages_data), batch_size): 5 batch = pages_data[i:i + batch_size] 6 for page_data in batch: 7 result = notion.pages.create( 8 parent={"database_id": database_id}, 9 properties=page_data["properties"], 10 children=page_data.get("children", []) 11 ) 12 results.append(result) 13 if i + batch_size < len(pages_data): 14 time.sleep(1) # Brief pause between batches 15 return results
4. Caching
python1import json 2from pathlib import Path 3from datetime import datetime, timedelta 4 5CACHE_DIR = Path.home() / ".cache" / "notion" 6CACHE_TTL = timedelta(minutes=5) 7 8def get_cached_or_fetch(key, fetch_func, ttl=CACHE_TTL): 9 """Get from cache or fetch fresh data""" 10 CACHE_DIR.mkdir(parents=True, exist_ok=True) 11 cache_file = CACHE_DIR / f"{key}.json" 12 13 if cache_file.exists(): 14 data = json.loads(cache_file.read_text()) 15 cached_at = datetime.fromisoformat(data["cached_at"]) 16 if datetime.now() - cached_at < ttl: 17 return data["value"] 18 19 value = fetch_func() 20 cache_data = { 21 "cached_at": datetime.now().isoformat(), 22 "value": value 23 } 24 cache_file.write_text(json.dumps(cache_data, default=str)) 25 return value
Troubleshooting
Common Issues
Issue: 401 Unauthorized
python1# Verify API key 2curl -s "https://api.notion.com/v1/users/me" \ 3 -H "Authorization: Bearer $NOTION_API_KEY" \ 4 -H "Notion-Version: 2022-06-28" 5 6# Check if integration is connected to the page/database 7# Go to page > ... menu > Connections > Your integration
Issue: 404 Not Found
python1# Ensure integration has access to the page 2# The integration must be explicitly connected to each page 3 4# For databases, check the database ID is correct 5# Database ID format: 32 hex characters (with or without hyphens)
Issue: 400 Bad Request
python1# Check property names match exactly (case-sensitive) 2# Verify property types match the database schema 3 4# Common mistakes: 5# - Using "title" instead of actual title property name 6# - Wrong select/multi_select option names 7# - Invalid date format (use ISO 8601: "2025-01-17")
Issue: Rate limiting (429)
python1# Notion allows ~3 requests/second 2# Implement exponential backoff 3# 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
This skill enables powerful workspace automation through Notion's comprehensive API, supporting databases, pages, blocks, queries, and integration patterns.