Skip to content
Back to skills

Notion Api

ASecurity

Integrates with Notion API to manage pages, databases, blocks, search,

  • 4 stars
  • 0 votes
  • 0 copies
  • 4 views
  • Added June 12, 2026
securitypythongodebuggingapidatabaseperformancedocumentation

Works with

  • cursor
  • cli
  • api

Security analysis

A100/100

Scanned June 12, 2026

npx -y skills add paulpas/agent-skill-router --skill notion-api --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Notion Api?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Notion Api
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/paulpas-notion-api/badge)](https://www.skillsdirectory.com/skills/paulpas-notion-api)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---




name: notion-api
description: Integrates with Notion API to manage pages, databases, blocks, search,
  and comments using notion-client for Python with typed property access and pagination.
license: MIT
compatibility: opencode
metadata:
  version: "1.0.0"
  domain: coding
  triggers: notion api, notion pages, notion databases, notion-client, notion python,
    notion integration, notion blocks
  archetypes:
  - tactical
  - generation
  anti_triggers:
  - brainstorming
  - vague ideation
  - code golf
  - over-engineering
  response_profile:
    verbosity: low
    directive_strength: high
    abstraction_level: operational
  role: implementation
  scope: implementation
  output-format: code
  content-types:
  - code
  - guidance
  - do-dont
  - examples
  related-skills: coding-google-workspace-api, coding-atlassian-api, coding-airtable-api




---





# Notion API Integration

Integrates with the Notion API to programmatically create and update pages, query databases, manipulate blocks, perform searches, and manage comments using `notion-client` (the Python SDK for the official Notion API).

## TL;DR for Code Generation

- Use `notion_client.Client` with an integration token from the Notion Integrations dashboard
- Always call `databases.query()` with typed `filter` and `sorts` dicts — Notion requires structured queries
- Use `pages.update()` with `properties` dict for partial page updates
- Paginate with `has_more` / `next_cursor` — Notion returns max 100 results per page
- Append block children via `blocks.children.append()`, not through page update
- Handle `notion_client.errors.APIResponseError` for permission, validation, and rate-limit errors

## When to Use

Use this skill when:

- Creating, reading, updating, or archiving Notion pages
- Querying Notion databases with filters (text, date, select, multi-select, formula)
- Appending or modifying page content (blocks: paragraphs, headings, lists, code, embeds)
- Searching across pages and databases with full-text search
- Adding or listing page comments and discussions
- Synchronizing Notion databases with external data sources (CRM, spreadsheets, APIs)
- Building self-updating documentation portals backed by Notion

## When NOT to Use

- High-frequency real-time sync (Notion has rate limits — use webhook alternatives or queue updates)
- File storage (upload files to a dedicated store and embed links in Notion)
- Large-scale ETL (Notion query performance degrades beyond 10K+ records per database)
- Replacing a traditional relational database (Notion is designed for flexible content, not ACID compliance)
- Anonymous API access — Notion requires an integration token or OAuth for every request

## Core Workflow

1. **Create an Integration** — Go to `https://www.notion.so/my-integrations` and create a new internal integration. Copy the "Internal Integration Secret" token. **Checkpoint:** Verify the integration is shared with the target page or database (the "Connections" menu in Notion).

2. **Initialize the Client** — `client = Client(auth=os.environ["NOTION_TOKEN"])`. The client wraps `httpx` and handles authentication. **Checkpoint:** Call `client.search(query="")` with an empty query to list accessible pages.

3. **Query a Database** — Use `client.databases.query(database_id=..., filter=..., sorts=...)`. Build filters using the Notion filter structure: `{"property": "Status", "select": {"equals": "Done"}}`. **Checkpoint:** Verify the response contains `results` and parse `has_more` for pagination.

4. **Create or Update a Page** — Create with `client.pages.create(parent=..., properties=...)`. Update with `client.pages.update(page_id=..., properties=...)`. Map Python types to Notion property types. **Checkpoint:** Confirm the page URL is accessible in the browser.

5. **Manipulate Blocks** — Fetch blocks with `client.blocks.children.list(block_id=...)`. Append with `client.blocks.children.append(block_id=..., children=[...])`. Each child is a typed dict. **Checkpoint:** Verify the new block renders at the correct position in the page.

6. **Handle Errors** — Wrap in `try/except APIResponseError`. Check `error.code` for `unauthorized` (401), `restricted_resource` (403), `conflict_error` (409), `rate_limited` (429). **Checkpoint:** Log the `error.cursor` if available for debugging.

## Implementation Patterns

### Pattern 1: Query a Database with Filters

```python
import os
from notion_client import Client

notion = Client(auth=os.environ["NOTION_TOKEN"])

def query_completed_tasks(database_id: str, days: int = 7) -> list[dict]:
    """Fetch tasks completed within the last N days from a Notion database."""
    from datetime import datetime, timedelta

    cutoff = datetime.now() - timedelta(days=days)

    results = notion.databases.query(
        database_id=database_id,
        filter={
            "and": [
                {"property": "Status", "select": {"equals": "Done"}},
                {
                    "property": "Completed Date",
                    "date": {"on_or_after": cutoff.strftime("%Y-%m-%d")},
                },
            ]
        },
        sorts=[{"property": "Completed Date", "direction": "descending"}],
    )
    return results.get("results", [])
```

### Pattern 2: Create a Page in a Database

```python
def create_task_page(
    database_id: str,
    title: str,
    status: str = "To Do",
    priority: str | None = None,
) -> dict:
    """Add a new task page to a Notion database."""
    properties = {
        "Title": {"title": [{"text": {"content": title}}]},
        "Status": {"select": {"name": status}},
    }
    if priority:
        properties["Priority"] = {"select": {"name": priority}}

    return notion.pages.create(
        parent={"database_id": database_id},
        properties=properties,
    )

task = create_task_page("db123abc", "Fix login timeout", "In Progress", "High")
print(f"Created page: {task['url']}")
```

### Pattern 3: Append Blocks to a Page

```python
def append_code_block(page_id: str, code: str, language: str = "python") -> dict:
    """Append a code block to an existing Notion page."""
    return notion.blocks.children.append(
        block_id=page_id,
        children=[
            {
                "object": "block",
                "type": "code",
                "code": {
                    "rich_text": [{"type": "text", "text": {"content": code}}],
                    "language": language,
                },
            }
        ],
    )

code_snippet = 'print("Hello, Notion!")\ndef deploy():\n    pass'
append_code_block("page456xyz", code_snippet, "python")
```

### Pattern 4: BAD vs GOOD — Property Updates

```python
# ❌ BAD — replaces entire properties dict, can delete other fields
notion.pages.update(page_id="abc", properties={"Status": {"select": {"name": "Done"}}})

# ✅ GOOD — fetches current, merges status update only
def update_page_status(page_id: str, status: str) -> dict:
    """Update only the Status property of a page."""
    return notion.pages.update(
        page_id=page_id,
        properties={"Status": {"select": {"name": status}}},
    )
```

### Pattern 5: BAD vs GOOD — Pagination

```python
# ❌ BAD — assumes single page, misses data
results = notion.databases.query(database_id="db123")
all_pages = results["results"]

# ✅ GOOD — paginates through all results
def query_all(database_id: str, **kwargs) -> list[dict]:
    """Paginate through all database results."""
    all_results = []
    start_cursor = None
    while True:
        response = notion.databases.query(
            database_id=database_id,
            start_cursor=start_cursor,
            **kwargs,
        )
        all_results.extend(response.get("results", []))
        if not response.get("has_more"):
            break
        start_cursor = response.get("next_cursor")
    return all_results
```

## Constraints

### MUST DO
- Use environment variables or secret managers for the Notion integration token
- Always paginate database queries — Notion caps at 100 items per response
- Share each integration with the specific pages/databases it needs (least privilege)
- Validate property names and types match the database schema before writing
- Use `rich_text` array structure (not a plain string) for text properties

### MUST NOT DO
- Hardcode page IDs or database IDs — use configuration or environment variables
- Poll Notion for changes (no webhook API exists yet — use a sync layer or poll sparingly)
- Assume property values always exist — check for `None` before accessing
- Use the token in client-side code where it can be extracted

## Output Template

Every integration function should expose:

1. **Client Initialization** — `Client(auth=token)` with token from environment
2. **Query or Command** — Typed database query or page update structure
3. **Pagination** — `has_more`/`next_cursor` loop for list endpoints
4. **Data Transformation** — Map Notion property types to Python types
5. **Error Handling** — `try/except APIResponseError` with `error.code` differentiation

## Related Skills

| Skill | Purpose |
|

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.