| Β | Β |
|---|---|
| AI-friendly | Composable APIs that drop cleanly into agent workflows |
| Smart discovery | Find pages/databases by title with fuzzy matching β no ID spelunking |
| Markdown content | Read & write page content as Markdown via the Notion Markdown API |
| Async-first | Modern Python with full async / await |
| Round-trip editing | Read a page as Markdown, transform it, write it back |
| Full coverage | Pages, databases, data sources, file uploads, users, workspace search |
pip install notionary
Set your Notion integration token:
export NOTION_API_KEY=your_integration_key
All access goes through the Notionary client, which exposes namespace objects β each mapping to a Notion API area.
import asyncio
from notionary import Notionary
async def main():
async with Notionary() as notion:
# Find a page by title (fuzzy matching)
page = await notion.pages.find("Meeting Notes")
print(page.title, page.url)
# Read content as Markdown
md = await page.get_markdown()
print(md)
# Append content
await page.append("## Action Items\n- [ ] Review proposal")
# Replace all content
await page.replace("# Fresh Start\nThis page was rewritten.")
asyncio.run(main())
async with Notionary() as notion:
# Lookup
page = await notion.pages.find("Sprint Board")
page = await notion.pages.from_id(page_uuid)
# List & search
pages = await notion.pages.list(query="roadmap")
md = await page.get_markdown() # read as Markdown
await page.append("## New Section") # append blocks
await page.replace("# Replaced") # overwrite all content
await page.clear() # remove all blocks
await page.rename("New Title")
await page.set_icon("π")
await page.set_cover("https://example.com/cover.png")
await page.random_cover()
# Set a single property (type-validated against the schema)
await page.set_property("Status", "Done")
await page.set_property("Due Date", "2025-12-31")
await page.set_property("Priority", 3)
await page.set_property("Archived", True)
await page.set_property("Tags", ["backend", "urgent"])
# Set multiple properties in one API call
await page.set_properties({
"Status": "In Progress",
"Due Date": "2025-12-31",
"Priority": 2,
})
# Inspect the property schema (types, current values, valid options)
schema = await page.describe_properties()
# schema["Status"] β PagePropertyDescription(type="status", current="Todo", options=["Todo", "In Progress", "Done"])
Supported property types: checkbox, date, email, multi_select, number, phone_number, rich_text, select, status, title, url, relation.
For relation properties on data-source pages, you can pass a page ID (UUID string) or a page title β the title is automatically resolved to an ID:
# By page ID
await page.set_property("Project", "abc123...")
# By title (auto-resolved via the related data source)
await page.set_property("Project", "Quarterly Review")
await page.comment("Review completed")
await page.lock()
await page.trash()
async with Notionary() as notion:
# Lookup
db = await notion.databases.find("Tasks")
db = await notion.databases.from_id(db_uuid)
# Create
db = await notion.databases.create(
parent_page_id=page_uuid,
title="New Database",
icon_emoji="π",
)
# Metadata
await db.set_title("Project Tracker")
await db.set_description("All current projects")
await db.set_icon("π")
await db.lock()
Notion API Reference: Databases
Data sources represent queryable Notion databases with schema awareness β useful for building structured content pipelines.
async with Notionary() as notion:
ds = await notion.data_sources.find("Engineering Backlog")
# Schema introspection β property types, current options, and relation pages
schema = await ds.describe_properties()
# schema["Status"] β DataSourcePropertyDescription(type="status", options=["Todo", "In Progress", "Done"])
# schema["Assignee"] β DataSourcePropertyDescription(type="relation", relation_options=[...])
# Create a page inside the data source
page = await ds.create_page(title="New Feature")
# Query with filters
results = await ds.query(filter={"property": "Status", "select": {"equals": "In Progress"}})
# Metadata
await ds.set_title("Sprint Board")
await ds.set_icon("π§")
Notion API Reference: Data Sources
from pathlib import Path
async with Notionary() as notion:
# Upload from disk
result = await notion.file_uploads.upload(Path("./report.pdf"))
# Upload from bytes (e.g. generated images, in-memory content)
result = await notion.file_uploads.upload_from_bytes(
content=image_bytes,
filename="chart.png",
)
# List previous uploads
uploads = await notion.file_uploads.list()
async with Notionary() as notion:
all_users = await notion.users.list()
people = await notion.users.list(filter="person")
bots = await notion.users.list(filter="bot")
me = await notion.users.me()
matches = await notion.users.search("alex")
async with Notionary() as notion:
results = await notion.workspace.search(query="roadmap")
for r in results:
print(type(r).__name__, r.title)
# Read β transform β write back
md = await page.get_markdown()
updated = md.replace("Draft", "Final")
await page.replace(updated)
async / await support throughoutContributions are welcome β whether youβre fixing bugs, adding features, improving docs, or sharing examples. Check the Contributing Guide to get started.
mathisarends.github.io/notionary β Complete API reference auto-generated from source.