Notion is where I keep everything — notes, project trackers, reading lists, CRM. But manually maintaining databases gets tedious fast. The good news is that Notion's official API is genuinely usable, and with a bit of Python you can automate a surprising amount.
In this post I'll show you how to query a database, create new pages, and update properties — the three operations that cover 90% of automation use cases.
Setting Up
First, create an integration at notion.so/my-integrations. Give it read/write access to your workspace (or just the specific databases you need). Copy the Internal Integration Secret — this is your API key.
Then install the official Python client:
pip install notion-client
from notion_client import Client
notion = Client(auth="your-integration-secret")
Next, share the specific database you want to work with your integration. Open the database in Notion → Share → Invite → select your integration.
Querying a Database
Every database has an ID you can find in its URL: notion.so/your-workspace/DATABASE_ID?v=.... Use that to query:
DATABASE_ID = "your-database-id-here"
response = notion.databases.query(
database_id=DATABASE_ID,
filter={
"property": "Status",
"select": {
"equals": "In Progress"
}
},
sorts=[
{
"property": "Created",
"direction": "descending"
}
]
)
for page in response["results"]:
title = page["properties"]["Name"]["title"][0]["plain_text"]
print(title)
Creating a New Page (Row)
def add_task(title: str, status: str = "Not Started"):
notion.pages.create(
parent={"database_id": DATABASE_ID},
properties={
"Name": {
"title": [{"text": {"content": title}}]
},
"Status": {
"select": {"name": status}
},
"Created": {
"date": {"start": "2026-09-15"}
}
}
)
add_task("Write blog post about Notion API", "In Progress")
Updating an Existing Page
def complete_task(page_id: str):
notion.pages.update(
page_id=page_id,
properties={
"Status": {
"select": {"name": "Done"}
}
}
)
A Practical Example: Daily Standup Generator
Here's a script I run every morning that queries my task database and generates a standup summary:
from datetime import date, timedelta
from notion_client import Client
notion = Client(auth="your-secret")
DATABASE_ID = "your-db-id"
yesterday = (date.today() - timedelta(days=1)).isoformat()
done = notion.databases.query(
database_id=DATABASE_ID,
filter={
"and": [
{"property": "Status", "select": {"equals": "Done"}},
{"property": "Completed", "date": {"on_or_after": yesterday}}
]
}
)["results"]
in_progress = notion.databases.query(
database_id=DATABASE_ID,
filter={"property": "Status", "select": {"equals": "In Progress"}}
)["results"]
print("Yesterday:")
for p in done:
print(f" ✓ {p['properties']['Name']['title'][0]['plain_text']}")
print("\nToday:")
for p in in_progress:
print(f" → {p['properties']['Name']['title'][0]['plain_text']}")
Limitations Worth Knowing
The API has a rate limit of 3 requests per second. For bulk operations, add a time.sleep(0.35) between calls.
Rich text formatting (bold, inline code, etc.) requires constructing rich_text objects rather than plain strings. It's verbose but workable once you see the pattern once.
Page content (blocks) is separate from page properties. If you need to read or write the body of a page, use notion.blocks.children.list(block_id=page_id).
The Notion API is still evolving. Check the official changelog before shipping automation to production.
