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.
Metadata
Release files for notionary 0.7.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| notionary-0.7.0.tar.gz | 17.8 MB | Without provenance | Details for notionary-0.7.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| notionary-0.7.0-py3-none-any.whl | Python 3 | none | any | Details for notionary-0.7.0-py3-none-any.whl |
Total release size: 17.9 MB
Release files / notionary-0.7.0.tar.gz
| Download URL | notionary-0.7.0.tar.gz |
|---|---|
| Size | 17.8 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1615c67ddd8641bde3e0c812861f377d5168c7b34815921b437cfdc378415ade
|
|
BLAKE2b-256 checksum How to use checksums |
60d8953f3699eee599eff64b97083cc1e11f62e1cb4f9f8ad5142478f84803df
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.2
|
Release files / notionary-0.7.0-py3-none-any.whl
| Download URL | notionary-0.7.0-py3-none-any.whl |
|---|---|
| Size | 84.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a223b8d4cca99c43a108108ad13bc5ef1f1205b37bbdcb8daac01b8dbf3ee841
|
|
BLAKE2b-256 checksum How to use checksums |
856af5a9824f815439cb2d5f8a22ba0ec7bc48911b66e0da74a3d7891d9e499a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.2
|
Security for notionary 0.7.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.