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. Also ships with a built-in MCP server for tool-use with any MCP-compatible AI agent.
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 # core library
pip install notionary[mcp] # + MCP server
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
await page.properties.set_property("Status", "Done")
await page.properties.set_property("Due Date", "2025-12-31")
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
schema = await ds.get_schema()
# 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 any MCP-compatible host
MCP Server
Notionary ships a built-in Model Context Protocol server so any MCP-compatible AI agent can manage your Notion workspace out of the box.
pip install notionary[mcp]
Claude Desktop / Claude Code
{
"mcpServers": {
"notionary": {
"command": "notionary-mcp",
"env": {
"NOTION_API_KEY": "your_integration_key"
}
}
}
}
OpenAI Agents SDK
import asyncio, sys
from agents import Agent, Runner
from agents.mcp import MCPServerStdio
async def main():
async with MCPServerStdio(
name="Notionary",
params={"command": sys.executable, "args": ["-m", "notionary.mcp.server"]},
) as server:
agent = Agent(
name="Notion Assistant",
instructions="You help users manage their Notion workspace.",
mcp_servers=[server],
)
result = await Runner.run(agent, "Search my workspace and list what you find.")
print(result.final_output)
asyncio.run(main())
Available MCP Tools
| Area | Tools |
|---|---|
| Workspace | search_workspace |
| Pages | list_pages, find_page, get_page_content, get_page_comments, update_page, append_to_page, replace_page_content, clear_page, comment_on_page, rename_page, set_page_property, trash_page, restore_page, lock_page, unlock_page |
| Data Sources | list_data_sources, find_data_source, get_data_source_schema, create_page_in_data_source, update_data_source, list_data_source_templates, trash_data_source, restore_data_source |
| Databases | list_databases, find_database, create_database, update_database, trash_database, restore_database |
| Users | list_users, search_users, get_me |
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.6.2.tar.gz.
File metadata
- Download URL: notionary-0.6.2.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 |
d0b5c2879f6ebe9d5454527dadb48b9f374c59cea59a0721a4a102fa6dc8dc47
|
|
| MD5 |
fbd151b880e13b89e9453b8a6488ca8c
|
|
| BLAKE2b-256 |
5cae65201bf1c989584eed8b6053706d84b193783305c1956a6c376c4e9c8f80
|
File details
Details for the file notionary-0.6.2-py3-none-any.whl.
File metadata
- Download URL: notionary-0.6.2-py3-none-any.whl
- Upload date:
- Size: 80.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2b478706ec2b0451d8a178cfd2dd8ab4a212694f5f56c290f3d913b640a37dcb
|
|
| MD5 |
303ae080187f380e075180e92540deec
|
|
| BLAKE2b-256 |
213e9c9327dbd81c76e77e9949cf1e12526d982363cc68a11f479ae3a72d586a
|