home / skills / vm0-ai / vm0-skills / notion

notion skill

/notion

This skill helps you manage Notion content via API by creating pages, querying databases, and manipulating blocks across workspaces.

npx playbooks add skill vm0-ai/vm0-skills --skill notion

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
13.8 KB
---
name: notion
description: Notion API for managing pages, databases, and blocks. Use this skill to create pages, query databases, search content, and build integrations with Notion workspaces.
vm0_secrets:
  - NOTION_TOKEN
---

# Notion API

Manage pages, databases, and content blocks in Notion workspaces.

## When to Use

- Create and update Notion pages
- Query and filter database entries
- Search across workspace content
- Append content blocks to pages
- Manage database schemas and properties

## Prerequisites

```bash
export NOTION_TOKEN=ntn_your-integration-token
```

### Get API Key

1. Go to https://www.notion.so/profile/integrations
2. Click "New integration"
3. Name your integration and select workspace
4. Copy the "Internal Integration Secret" (`ntn_...` or `secret_...`)
5. **Important**: Share pages/databases with the integration via "Add connections" in Notion

### Page ID Format

Notion URLs contain page IDs. Extract and normalize them:

```
URL: https://www.notion.so/My-Page-2b70e96f0134807d8450c8793839c659
Page ID: 2b70e96f0134807d8450c8793839c659 (remove hyphens if present)
```

```bash
# Normalize page ID (remove hyphens)
PAGE_ID=$(echo "2b70e96f-0134-807d-8450-c8793839c659" | tr -d '-')
```

> **Important:** When using `$VAR` in a command that pipes to another command, wrap the command containing `$VAR` in `bash -c '...'`. Due to a Claude Code bug, environment variables are silently cleared when pipes are used directly.
> ```bash
> bash -c 'curl -s "https://api.example.com" -H "Authorization: Bearer $API_KEY"'
> ```

## Core APIs

### Read Page with Content

Replace `<your-page-id>` with your actual Notion page ID:

```bash
# Get page metadata
bash -c 'curl -s -X GET "https://api.notion.com/v1/pages/<your-page-id>" --header "Authorization: Bearer $NOTION_TOKEN" --header "Notion-Version: 2022-06-28" | jq '"'"'{title: .properties.title.title[0].plain_text, url, last_edited_time}'"'"

# Get page content blocks
bash -c 'curl -s -X GET "https://api.notion.com/v1/blocks/<your-page-id>/children?page_size=100" --header "Authorization: Bearer $NOTION_TOKEN" --header "Notion-Version: 2022-06-28" | jq '"'"'.results[] | {type, text: (.[.type].rich_text // [] | map(.plain_text) | join("")), has_children}'"'"
```

### Read Nested Blocks (Toggle, etc.)

Blocks with `has_children: true` contain nested content. Replace `<your-block-id>` with your actual block ID:

```bash
bash -c 'curl -s -X GET "https://api.notion.com/v1/blocks/<your-block-id>/children" --header "Authorization: Bearer $NOTION_TOKEN" --header "Notion-Version: 2022-06-28" | jq '"'"'.results[] | {type, text: (.[.type].rich_text // [] | map(.plain_text) | join(""))}'"'"
```

### Search Workspace

Write to `/tmp/notion_request.json`:

```json
{
  "query": "Meeting Notes",
  "page_size": 10
}
```

Then run:

```bash
bash -c 'curl -s -X POST "https://api.notion.com/v1/search" --header "Authorization: Bearer $NOTION_TOKEN" --header "Notion-Version: 2022-06-28" --header "Content-Type: application/json" -d @/tmp/notion_request.json | jq '"'"'.results[] | {id, object, title: .properties.title.title[0].plain_text // .title[0].plain_text}'"'"
```

Docs: https://developers.notion.com/reference/post-search

### List Database Entries

Write to `/tmp/notion_request.json`:

```json
{
  "page_size": 100
}
```

Replace `<your-database-id>` with your actual database ID and run:

```bash
bash -c 'curl -s -X POST "https://api.notion.com/v1/databases/<your-database-id>/query" --header "Authorization: Bearer $NOTION_TOKEN" --header "Notion-Version: 2022-06-28" --header "Content-Type: application/json" -d @/tmp/notion_request.json | jq '"'"'.results[] | {id, properties}'"'"
```

Docs: https://developers.notion.com/reference/post-database-query

### Query with Filter

Replace `<your-database-id>` with your actual database ID:

```bash
curl -s -X POST "https://api.notion.com/v1/databases/<your-database-id>/query" --header "Authorization: Bearer $NOTION_TOKEN" --header 'Notion-Version: 2022-06-28' --header 'Content-Type: application/json' -d @- << 'EOF'
{
  "filter": {
  "property": "Status",
  "select": {"equals": "Done"}
  },
  "sorts": [{"property": "Date", "direction": "descending"}],
  "page_size": 50
}
EOF
```

### Query with Multiple Filters

Replace `<your-database-id>` with your actual database ID:

```bash
curl -s -X POST "https://api.notion.com/v1/databases/<your-database-id>/query" --header "Authorization: Bearer $NOTION_TOKEN" --header 'Notion-Version: 2022-06-28' --header 'Content-Type: application/json' -d @- << 'EOF'
{
  "filter": {
  "and": [
  {"property": "Status", "select": {"does_not_equal": "Archived"}},
  {"property": "Due", "date": {"on_or_before": "2024-12-31"}}
  ]
  }
}
EOF
```

### Get Database Schema

Replace `<your-database-id>` with your actual database ID:

```bash
bash -c 'curl -s "https://api.notion.com/v1/databases/<your-database-id>" --header "Authorization: Bearer $NOTION_TOKEN" --header '"'"'Notion-Version: 2022-06-28'"'"' | jq '"'"'{title: .title[0].plain_text, properties: .properties | keys}'"'"
```

Docs: https://developers.notion.com/reference/retrieve-a-database

### Get Page

Replace `<your-page-id>` with your actual page ID:

```bash
bash -c 'curl -s "https://api.notion.com/v1/pages/<your-page-id>" --header "Authorization: Bearer $NOTION_TOKEN" --header '"'"'Notion-Version: 2022-06-28'"'"' | jq '"'"'{id, url, properties}'"'"
```

Docs: https://developers.notion.com/reference/retrieve-a-page

### Create Page in Database

```bash
curl -s -X POST 'https://api.notion.com/v1/pages' --header "Authorization: Bearer $NOTION_TOKEN" --header 'Notion-Version: 2022-06-28' --header 'Content-Type: application/json' -d @- << 'EOF'
{
  "parent": {"database_id": "your-database-id"},
  "properties": {
  "Name": {"title": [{"text": {"content": "New Task"}}]},
  "Status": {"select": {"name": "To Do"}},
  "Due": {"date": {"start": "2024-12-31"}}
  }
}
EOF
```

Docs: https://developers.notion.com/reference/post-page

### Create Page with Content

```bash
curl -s -X POST 'https://api.notion.com/v1/pages' --header "Authorization: Bearer $NOTION_TOKEN" --header 'Notion-Version: 2022-06-28' --header 'Content-Type: application/json' -d @- << 'EOF'
{
  "parent": {"page_id": "parent-page-id"},
  "properties": {
  "title": {"title": [{"text": {"content": "My New Page"}}]}
  },
  "children": [
  {
  "object": "block",
  "type": "heading_2",
  "heading_2": {"rich_text": [{"text": {"content": "Introduction"}}]}
  },
  {
  "object": "block",
  "type": "paragraph",
  "paragraph": {"rich_text": [{"text": {"content": "This is the content."}}]}
  }
  ]
}
EOF
```

### Update Page Properties

Replace `<your-page-id>` with your actual page ID:

```bash
curl -s -X PATCH "https://api.notion.com/v1/pages/<your-page-id>" --header "Authorization: Bearer $NOTION_TOKEN" --header 'Notion-Version: 2022-06-28' --header 'Content-Type: application/json' -d @- << 'EOF'
{
  "properties": {
  "Status": {"select": {"name": "In Progress"}}
  }
}
EOF
```

Docs: https://developers.notion.com/reference/patch-page

### Archive Page

Write to `/tmp/notion_request.json`:

```json
{
  "archived": true
}
```

Replace `<your-page-id>` with your actual page ID and run:

```bash
bash -c 'curl -s -X PATCH "https://api.notion.com/v1/pages/<your-page-id>" --header "Authorization: Bearer $NOTION_TOKEN" --header "Notion-Version: 2022-06-28" --header "Content-Type: application/json" -d @/tmp/notion_request.json'
```

### Get Block Children

Replace `<your-block-id>` with your actual block ID:

```bash
bash -c 'curl -s "https://api.notion.com/v1/blocks/<your-block-id>/children?page_size=100" --header "Authorization: Bearer $NOTION_TOKEN" --header '"'"'Notion-Version: 2022-06-28'"'"' | jq '"'"'.results[] | {type, id}'"'"
```

Docs: https://developers.notion.com/reference/get-block-children

### Append Blocks to Page

Replace `<your-page-id>` with your actual page ID:

```bash
curl -s -X PATCH "https://api.notion.com/v1/blocks/<your-page-id>/children" --header "Authorization: Bearer $NOTION_TOKEN" --header 'Notion-Version: 2022-06-28' --header 'Content-Type: application/json' -d @- << 'EOF'
{
  "children": [
  {
  "object": "block",
  "type": "paragraph",
  "paragraph": {"rich_text": [{"text": {"content": "New paragraph added."}}]}
  },
  {
  "object": "block",
  "type": "to_do",
  "to_do": {
  "rich_text": [{"text": {"content": "Task item"}}],
  "checked": false
  }
  }
  ]
}
EOF
```

Docs: https://developers.notion.com/reference/patch-block-children

### Delete Block

Replace `<your-block-id>` with your actual block ID:

```bash
curl -s -X DELETE "https://api.notion.com/v1/blocks/<your-block-id>" --header "Authorization: Bearer $NOTION_TOKEN" --header 'Notion-Version: 2022-06-28'
```

### List Users

```bash
bash -c 'curl -s '"'"'https://api.notion.com/v1/users'"'"' --header "Authorization: Bearer $NOTION_TOKEN" --header '"'"'Notion-Version: 2022-06-28'"'"' | jq '"'"'.results[] | {id, name, type}'"'"
```

Docs: https://developers.notion.com/reference/get-users

### Get Current Bot

```bash
curl -s 'https://api.notion.com/v1/users/me' --header "Authorization: Bearer $NOTION_TOKEN" --header 'Notion-Version: 2022-06-28'
```

### Create Database

```bash
curl -s -X POST 'https://api.notion.com/v1/databases' --header "Authorization: Bearer $NOTION_TOKEN" --header 'Notion-Version: 2022-06-28' --header 'Content-Type: application/json' -d @- << 'EOF'
{
  "parent": {"page_id": "parent-page-id"},
  "title": [{"text": {"content": "Task Tracker"}}],
  "properties": {
  "Name": {"title": {}},
  "Status": {"select": {"options": [
  {"name": "To Do", "color": "gray"},
  {"name": "In Progress", "color": "blue"},
  {"name": "Done", "color": "green"}
  ]}},
  "Due Date": {"date": {}},
  "Assignee": {"people": {}},
  "Priority": {"select": {"options": [
  {"name": "High", "color": "red"},
  {"name": "Medium", "color": "yellow"},
  {"name": "Low", "color": "green"}
  ]}}
  }
}
EOF
```

Docs: https://developers.notion.com/reference/create-a-database

## Property Types

### Setting Properties (Create/Update)

| Type | Format |
|------|--------|
| Title | `{"title": [{"text": {"content": "value"}}]}` |
| Rich Text | `{"rich_text": [{"text": {"content": "value"}}]}` |
| Number | `{"number": 42}` |
| Select | `{"select": {"name": "Option"}}` |
| Multi-select | `{"multi_select": [{"name": "Tag1"}, {"name": "Tag2"}]}` |
| Date | `{"date": {"start": "2024-01-01", "end": "2024-01-31"}}` |
| Checkbox | `{"checkbox": true}` |
| URL | `{"url": "https://example.com"}` |
| Email | `{"email": "[email protected]"}` |
| Phone | `{"phone_number": "+1234567890"}` |
| People | `{"people": [{"id": "user-id"}]}` |
| Relation | `{"relation": [{"id": "page-id"}]}` |

## Block Types

### Text Blocks

```json
{"type": "paragraph", "paragraph": {"rich_text": [{"text": {"content": "Text"}}]}}
{"type": "heading_1", "heading_1": {"rich_text": [{"text": {"content": "H1"}}]}}
{"type": "heading_2", "heading_2": {"rich_text": [{"text": {"content": "H2"}}]}}
{"type": "heading_3", "heading_3": {"rich_text": [{"text": {"content": "H3"}}]}}
{"type": "quote", "quote": {"rich_text": [{"text": {"content": "Quote"}}]}}
{"type": "callout", "callout": {"rich_text": [{"text": {"content": "Note"}}], "icon": {"emoji": "💡"}}}
```

### List Blocks

```json
{"type": "bulleted_list_item", "bulleted_list_item": {"rich_text": [{"text": {"content": "Item"}}]}}
{"type": "numbered_list_item", "numbered_list_item": {"rich_text": [{"text": {"content": "Item"}}]}}
{"type": "to_do", "to_do": {"rich_text": [{"text": {"content": "Task"}}], "checked": false}}
{"type": "toggle", "toggle": {"rich_text": [{"text": {"content": "Toggle"}}]}}
```

### Code Block

```json
{"type": "code", "code": {"rich_text": [{"text": {"content": "console.log('Hello')"}}], "language": "javascript"}}
```

### Divider

```json
{"type": "divider", "divider": {}}
```

## Filter Operators

### Text/Title/Rich Text
`equals`, `does_not_equal`, `contains`, `does_not_contain`, `starts_with`, `ends_with`, `is_empty`, `is_not_empty`

### Number
`equals`, `does_not_equal`, `greater_than`, `less_than`, `greater_than_or_equal_to`, `less_than_or_equal_to`, `is_empty`, `is_not_empty`

### Date
`equals`, `before`, `after`, `on_or_before`, `on_or_after`, `is_empty`, `is_not_empty`, `past_week`, `past_month`, `past_year`, `next_week`, `next_month`, `next_year`

### Select/Multi-select
`equals`, `does_not_equal`, `is_empty`, `is_not_empty`, `contains` (multi-select), `does_not_contain` (multi-select)

### Checkbox
`equals`, `does_not_equal`

## Rich Text Annotations

```json
{
  "text": {"content": "Styled text", "link": {"url": "https://example.com"}},
  "annotations": {
  "bold": true,
  "italic": false,
  "strikethrough": false,
  "underline": false,
  "code": false,
  "color": "red"
  }
}
```

**Colors**: `default`, `gray`, `brown`, `orange`, `yellow`, `green`, `blue`, `purple`, `pink`, `red`, `gray_background`, `brown_background`, etc.

## Pagination

All list endpoints support cursor-based pagination:

Write to `/tmp/notion_request.json`:

```json
{
  "page_size": 100
}
```

```bash
# First request
bash -c 'curl -s -X POST "https://api.notion.com/v1/databases/<your-database-id>/query" --header "Authorization: Bearer $NOTION_TOKEN" --header "Notion-Version: 2022-06-28" --header "Content-Type: application/json" -d @/tmp/notion_request.json'
# Response includes: {"next_cursor": "abc123", "has_more": true}
```

Write to `/tmp/notion_request.json`:

```json
{
  "page_size": 100,
  "start_cursor": "<your-cursor>"
}
```

```bash
# Next page
bash -c 'curl -s -X POST "https://api.notion.com/v1/databases/<your-database-id>/query" --header "Authorization: Bearer $NOTION_TOKEN" --header "Notion-Version: 2022-06-28" --header "Content-Type: application/json" -d @/tmp/notion_request.json'
```

## Rate Limits

- Average: 3 requests/second
- Burst: Higher for short periods
- 429 response = retry with exponential backoff

## API Reference

- Main Docs: https://developers.notion.com
- API Reference: https://developers.notion.com/reference
- Integration Settings: https://www.notion.so/profile/integrations

Overview

This skill provides a practical wrapper for the Notion API to create and manage pages, databases, and content blocks inside Notion workspaces. It includes common shell examples for reading page content, querying databases, searching workspace content, and appending or deleting blocks. Use it to automate workspace integrations, content syncs, and lightweight ETL with Notion data.

How this skill works

The skill issues authenticated HTTP requests to Notion endpoints using an integration token in NOTION_TOKEN. It shows curl and bash -c examples to handle environment variables safely, read page metadata and block children, query and filter databases, search the workspace, and create or update pages and databases. Responses are typically piped to jq to extract useful fields like titles, ids, properties, and block text.

When to use it

  • Automate creating or updating project pages and tasks from scripts or CI pipelines.
  • Export or index Notion content by querying databases and reading block children.
  • Build integrations that search workspace content and surface matching pages.
  • Append notes, checklists, or code blocks into existing pages programmatically.
  • Create or modify database schemas and properties from deployment scripts.

Best practices

  • Set NOTION_TOKEN as an environment variable and share relevant pages/databases with the integration.
  • Normalize Notion page IDs by removing hyphens before using them in requests.
  • Wrap curl calls in bash -c '...' when piping or using environment variables to avoid silent clearing.
  • Use page_size and cursor-based pagination for large result sets and implement exponential backoff on 429 errors.
  • Validate database property types (title, select, date, people, etc.) before sending create/update requests.

Example use cases

  • Create a daily meeting notes page with headings and paragraphs using the Create Page with Content example.
  • Query a task database for items with Status = Done and sort by Date to generate a weekly report.
  • Append a to-do list or code snippet into an existing page using the Append Blocks endpoint.
  • Search the workspace for pages matching keywords like 'Meeting Notes' and extract page ids and titles.
  • Create a team task tracker database with predefined Status and Priority select options.

FAQ

How do I get an API key and grant access?

Create an integration at notion.so/profile/integrations, copy the Internal Integration Secret, set NOTION_TOKEN, and add the integration to pages/databases via Add connections.

What rate limits should I expect?

Aim for an average of about 3 requests/second, handle 429 responses with exponential backoff, and use paging for large queries.