Notion

Notion API integration with managed OAuth. Query databases, search pages, and read workspace content. Write operations require explicit user confirmation of the target resource and connection. Use this skill when users want to interact with Notion workspaces, databases, or pages. For other third party apps, use the api-gateway skill (https://clawhub.ai/byungkyu/api-gateway).

Audits

Pass

Install

openclaw skills install notion-api-skill

Notion

Access the Notion API with managed OAuth authentication. Query databases, search pages, and read workspace content. All write operations (creating, updating, or deleting pages, blocks, and databases) require explicit user confirmation specifying the target resource and connection before execution.

Quick Start

CLI:

maton notion search 'meeting notes'
maton api '/notion/v1/search'

Python:

python <<'EOF'
import urllib.request, os, json
data = json.dumps({'query': 'meeting notes'}).encode()
req = urllib.request.Request('https://api.maton.ai/notion/v1/search', data=data, method='POST')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('Content-Type', 'application/json')
req.add_header('Notion-Version', '2025-09-03')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF

Base URL

https://api.maton.ai/notion/{native-api-path}

Maton proxies requests to api.notion.com and automatically injects your OAuth token. Write operations (POST, PATCH, DELETE) must only be executed after the user confirms the target page/database ID and intended connection.

Required Headers

All Notion API requests require the version header:

Notion-Version: 2025-09-03

Installation

NPM:

npm install -g @maton-ai/cli

Homebrew:

brew install maton-ai/cli/maton

Authentication

CLI:

maton login                          # Opens browser for API key
maton login --interactive            # Skip browser, paste API key directly
maton whoami                         # Show current auth state

Manual:

  1. Sign in or create an account at maton.ai
  2. Go to maton.ai/settings
  3. Copy your API key
  4. Set your API key as MATON_API_KEY:
export MATON_API_KEY="YOUR_API_KEY"

Connection Management

Manage your Notion OAuth connections at https://api.maton.ai.

List Connections

CLI:

maton connection list notion --status ACTIVE
maton api -X GET /connections -f app=notion -f status=ACTIVE

Python:

python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/connections?app=notion&status=ACTIVE')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF

Create Connection

CLI:

maton connection create notion
maton api /connections -f app=notion

Python:

python <<'EOF'
import urllib.request, os, json
data = json.dumps({'app': 'notion'}).encode()
req = urllib.request.Request('https://api.maton.ai/connections', data=data, method='POST')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('Content-Type', 'application/json')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF

Get Connection

CLI:

maton connection view {connection_id}
maton api /connections/{connection_id}

Python:

python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/connections/{connection_id}')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF

Response:

{
  "connection": {
    "connection_id": "{connection_id}",
    "status": "ACTIVE",
    "creation_time": "2025-12-08T07:20:53.488460Z",
    "last_updated_time": "2026-01-31T20:03:32.593153Z",
    "url": "https://connect.maton.ai/?session_token=...",
    "app": "notion",
    "method": "OAUTH2",
    "metadata": {}
  }
}

Open the returned url in a browser to complete OAuth authorization.

Delete Connection

CLI:

maton connection delete {connection_id}
maton api -X DELETE /connections/{connection_id}

Python:

python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/connections/{connection_id}', method='DELETE')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF

Specifying Connection

If you have multiple Notion connections, specify which one to use:

CLI:

maton notion search 'meeting notes' --connection {connection_id}
maton api /notion/v1/search --connection {connection_id}

Python:

python <<'EOF'
import urllib.request, os, json
data = json.dumps({'query': 'meeting notes'}).encode()
req = urllib.request.Request('https://api.maton.ai/notion/v1/search', data=data, method='POST')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('Content-Type', 'application/json')
req.add_header('Notion-Version', '2025-09-03')
req.add_header('Maton-Connection', '{connection_id}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF

If you have multiple connections, always specify the connection to ensure requests go to the intended account.

Key Concept: Databases vs Data Sources

In API version 2025-09-03, databases and data sources are separate:

ConceptUse For
DatabaseCreating databases, getting data source IDs
Data SourceQuerying, updating schema, updating properties

Use GET /databases/{id} to get the data_sources array, then use /data_sources/ endpoints:

{
  "object": "database",
  "id": "abc123",
  "data_sources": [
    {"id": "def456", "name": "My Database"}
  ]
}

Security & Permissions

  • Access is scoped to pages, databases, blocks, users, and search within the connected Notion account.
  • All write operations require explicit user approval. Before executing any create, update, or delete call:
    1. Confirm the exact target (page ID, database ID, block ID) with the user.
    2. Verify the correct connection ID when multiple connections exist.
    3. State whether the action is reversible or destructive.
  • Irreversible / high-risk operations (require extra caution):
    • Deleting pages or blocks (archived, not permanently deleted, but may disrupt workflows)
    • Bulk updates across multiple pages or databases
    • Modifying shared workspace pages visible to other team members
  • Scope boundaries:
    • Only operate on pages and databases the user explicitly names or identifies. Never enumerate or modify resources outside the current task context.
    • Use the least-privileged Notion connection available for the task.
    • Do not perform bulk or batch operations without explicit user approval for each batch.

API Reference

Search

Search for pages:

POST /notion/v1/search
Content-Type: application/json
Notion-Version: 2025-09-03

{
  "query": "meeting notes",
  "filter": {"property": "object", "value": "page"}
}

Example:

maton notion search 'meeting notes' --filter page

Search for data sources:

POST /notion/v1/search
Content-Type: application/json
Notion-Version: 2025-09-03

{
  "filter": {"property": "object", "value": "data_source"}
}

Example:

maton notion search --filter data_source

Data Sources

Get Data Source

GET /notion/v1/data_sources/{dataSourceId}
Notion-Version: 2025-09-03

Example:

maton notion data-source view <dataSourceId>

Query Data Source

POST /notion/v1/data_sources/{dataSourceId}/query
Content-Type: application/json
Notion-Version: 2025-09-03

{
  "filter": {
    "property": "Status",
    "select": {"equals": "Active"}
  },
  "sorts": [
    {"property": "Created", "direction": "descending"}
  ],
  "page_size": 100
}

Example:

maton notion data-source query <dataSourceId> \
  --filter '{"property":"Status","select":{"equals":"Active"}}' \
  --sorts '[{"property":"Created","direction":"descending"}]' \
  --page-size 100

Update Data Source

PATCH /notion/v1/data_sources/{dataSourceId}
Content-Type: application/json
Notion-Version: 2025-09-03

{
  "title": [{"type": "text", "text": {"content": "Updated Title"}}],
  "properties": {
    "NewColumn": {"rich_text": {}}
  }
}

Example:

maton notion data-source update <dataSourceId> \
  --body '{"title":[{"type":"text","text":{"content":"Updated Title"}}],"properties":{"NewColumn":{"rich_text":{}}}}'

Databases

Get Database

GET /notion/v1/databases/{databaseId}
Notion-Version: 2025-09-03

Example:

maton notion database view <databaseId>

Create Database

POST /notion/v1/databases
Content-Type: application/json
Notion-Version: 2025-09-03

{
  "parent": {"type": "page_id", "page_id": "PARENT_PAGE_ID"},
  "title": [{"type": "text", "text": {"content": "New Database"}}],
  "properties": {
    "Name": {"title": {}}
  }
}

Example:

maton notion database create --parent-page PARENT_PAGE_ID --title 'New Database'

In API version 2025-09-03, POST /databases only accepts the title property — any other entries in properties are silently dropped. To define a schema, follow up with PATCH /data_sources/{dataSourceId} (see Update Data Source) using the data_sources[0].id returned by the create call.

Pages

Get Page

GET /notion/v1/pages/{pageId}
Notion-Version: 2025-09-03

Example:

maton notion page view <pageId>

Create Page

POST /notion/v1/pages
Content-Type: application/json
Notion-Version: 2025-09-03

{
  "parent": {"page_id": "PARENT_PAGE_ID"},
  "properties": {
    "title": {"title": [{"text": {"content": "New Page"}}]}
  }
}

Example:

maton notion page create --parent-page PARENT_PAGE_ID --title 'New Page'

Create Page in Data Source

POST /notion/v1/pages
Content-Type: application/json
Notion-Version: 2025-09-03

{
  "parent": {"data_source_id": "DATA_SOURCE_ID"},
  "properties": {
    "Name": {"title": [{"text": {"content": "New Page"}}]},
    "Status": {"select": {"name": "Active"}}
  }
}

Example:

maton notion page create --data-source DATA_SOURCE_ID --title 'New Page' \
  --properties '{"Status":{"select":{"name":"Active"}}}'

Update Page Properties

PATCH /notion/v1/pages/{pageId}
Content-Type: application/json
Notion-Version: 2025-09-03

{
  "properties": {
    "Status": {"select": {"name": "Done"}}
  }
}

Example:

maton notion page update {pageId} --properties '{"Status":{"select":{"name":"Done"}}}'

Update Page Icon

PATCH /notion/v1/pages/{pageId}
Content-Type: application/json
Notion-Version: 2025-09-03

{
  "icon": {"type": "emoji", "emoji": "🚀"}
}

Example:

maton notion page update {pageId} --icon 🚀

Or with an image URL:

maton notion page update {pageId} --icon https://example.com/icon.png

Archive Page

PATCH /notion/v1/pages/{pageId}
Content-Type: application/json
Notion-Version: 2025-09-03

{
  "archived": true
}

Example:

maton notion page archive {pageId}

Blocks

Get Block Children

GET /notion/v1/blocks/{blockId}/children
Notion-Version: 2025-09-03

Example:

maton notion block children <blockId>

Append Block Children

PATCH /notion/v1/blocks/{blockId}/children
Content-Type: application/json
Notion-Version: 2025-09-03

{
  "children": [
    {
      "object": "block",
      "type": "paragraph",
      "paragraph": {
        "rich_text": [{"type": "text", "text": {"content": "New paragraph"}}]
      }
    }
  ]
}

Example:

maton notion block append <blockId> \
  --children '[{"object":"block","type":"paragraph","paragraph":{"rich_text":[{"type":"text","text":{"content":"New paragraph"}}]}}]'

Delete Block

DELETE /notion/v1/blocks/{blockId}
Notion-Version: 2025-09-03

Example:

maton notion block delete <blockId>

Users

List Users

GET /notion/v1/users
Notion-Version: 2025-09-03

Example:

maton notion user list

Get Current User

GET /notion/v1/users/me
Notion-Version: 2025-09-03

Example:

maton notion whoami

Filter Operators

  • equals, does_not_equal
  • contains, does_not_contain
  • starts_with, ends_with
  • is_empty, is_not_empty
  • greater_than, less_than

Block Types

  • paragraph, heading_1, heading_2, heading_3
  • bulleted_list_item, numbered_list_item
  • to_do, code, quote, divider

Pagination

Notion uses cursor-based pagination. The CLI automatically paginates with '--paginate'.

Example:

maton notion data-source query <dataSourceId> --paginate

Code Examples

CLI

# Search for pages matching a query
maton notion search 'roadmap'

# View a specific page
maton notion page view 0123456789abcdef0123456789abcdef

# Query a data source with a filter
maton notion data-source query <dataSourceId> --filter '{"property":"Status","select":{"equals":"Active"}}'

# Filter with jq — e.g., only pages (responses are wrapped in {"results": [...]})
# Note: --jq requires --json
maton notion search 'roadmap' --json --jq '.results | map(select(.object == "page"))'

JavaScript

const response = await fetch('https://api.maton.ai/notion/v1/search', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${process.env.MATON_API_KEY}`,
    'Notion-Version': '2025-09-03'
  },
  body: JSON.stringify({ query: 'meeting' })
});

Python

import os
import requests

response = requests.post(
    'https://api.maton.ai/notion/v1/search',
    headers={
        'Authorization': f'Bearer {os.environ["MATON_API_KEY"]}',
        'Notion-Version': '2025-09-03'
    },
    json={'query': 'meeting'}
)

Notes

  • All IDs are UUIDs (with or without hyphens)
  • Use GET /databases/{id} to get the data_sources array containing data source IDs
  • Creating databases requires POST /databases endpoint
  • Delete blocks returns the block with archived: true
  • IMPORTANT: When using curl commands, use curl -g when URLs contain brackets (fields[], sort[], records[]) to disable glob parsing
  • IMPORTANT: When piping curl output to jq or other commands, environment variables like $MATON_API_KEY may not expand correctly in some shell environments. You may get "Invalid API key" errors when piping.

Error Handling

StatusMeaning
400Missing Notion connection
401Invalid or missing Maton API key
429Rate limited (10 req/sec per account)
4xx/5xxPassthrough error from Notion API

Troubleshooting: API Key Issues

CLI:

  1. Check your auth state:
maton whoami
  1. Verify the API key is valid by listing connections:
maton connection list

Manual:

  1. Check that the MATON_API_KEY environment variable is set:
echo $MATON_API_KEY
  1. Verify the API key is valid by listing connections:
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/connections')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF

Troubleshooting: Invalid App Name

  1. Ensure your URL path starts with notion. For example:
  • Correct: https://api.maton.ai/notion/v1/search
  • Incorrect: https://api.maton.ai/v1/search

Resources