Python library for programmatic Notion workspace management - databases, pages, and content with advanced Markdown support
Project description
Notionary
The Modern Notion API for Python & AI Agents
Transform complex Notion API interactions into simple, Pythonic code. Perfect for developers building AI agents, automation workflows, and dynamic content systems.
Why Notionary?
| 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 |
Installation
pip install notionary
Set your Notion integration token:
export NOTION_API_KEY=your_integration_key
Quick Start
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())
Core API
Pages
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")
Content (Markdown API)
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
Metadata
await page.rename("New Title")
await page.set_icon("🚀")
await page.set_cover("https://example.com/cover.png")
await page.random_cover()
Properties
# 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")
Comments & Lifecycle
await page.comment("Review completed")
await page.lock()
await page.trash()
Databases
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
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
File Uploads
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()
Users
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")
Workspace Search
async with Notionary() as notion:
results = await notion.workspace.search(query="roadmap")
for r in results:
print(type(r).__name__, r.title)
Key Features
Smart Discovery
- Find pages and databases by human-readable name
- Fuzzy matching handles typos and partial titles
- No more hunting for opaque IDs or copying page URLs
Markdown Content API
- Read any Notion page as clean Markdown
- Append or replace blocks using Markdown syntax
- Powered by the official Notion Markdown API
Round-Trip Editing
# Read → transform → write back
md = await page.get_markdown()
updated = md.replace("Draft", "Final")
await page.replace(updated)
Modern Python
- Full
async/awaitsupport throughout - Type hints on all public APIs
- Pydantic models for all API responses
- Context-manager client for clean resource handling
AI-Ready Architecture
- Namespace-based API maps naturally to agent tool sets
- Predictable response models enable prompt chaining
- Works with LangChain, LlamaIndex, OpenAI Agents SDK, Claude, and custom agent runtimes
Contributing
Contributions are welcome — whether you're fixing bugs, adding features, improving docs, or sharing examples. Check the Contributing Guide to get started.
Documentation
mathisarends.github.io/notionary — Complete API reference auto-generated from source.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file notionary-0.7.0.tar.gz.
File metadata
- Download URL: notionary-0.7.0.tar.gz
- Upload date:
- Size: 17.8 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1615c67ddd8641bde3e0c812861f377d5168c7b34815921b437cfdc378415ade
|
|
| MD5 |
23920f43c07b2c8f7b6d5f20e3438b4e
|
|
| BLAKE2b-256 |
60d8953f3699eee599eff64b97083cc1e11f62e1cb4f9f8ad5142478f84803df
|
File details
Details for the file notionary-0.7.0-py3-none-any.whl.
File metadata
- Download URL: notionary-0.7.0-py3-none-any.whl
- Upload date:
- Size: 84.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a223b8d4cca99c43a108108ad13bc5ef1f1205b37bbdcb8daac01b8dbf3ee841
|
|
| MD5 |
e4afb83abc0fb46de10988ef61b58406
|
|
| BLAKE2b-256 |
856af5a9824f815439cb2d5f8a22ba0ec7bc48911b66e0da74a3d7891d9e499a
|