Skip to main content
Notionary logo

Notionary

PyPI version Python Version License: MIT Downloads Documentation Notion API

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()

Notion API Reference: Pages · Markdown


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")

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 / await support 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.


Built with ❤️ for Python developers and AI agents

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)

Source distribution for notionary 0.7.0
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)

Table of built distributions (wheels) for notionary 0.7.0
File Interpreter ABI Platform Actions
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.

Release history Release notifications | RSS feed

This release

0.7.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page