Install
openclaw skills install @local-corpus-everette-halvorson/notion-api-skill| 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 p
openclaw skills install @local-corpus-everette-halvorson/notion-api-skill--- name: notion description: | 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). compatibility: Requires network access and valid Maton API key metadata: author: maton version: "1.0" clawdbot: emoji: 🧠 homepage: "https://maton.ai" requires: env: - MATON_API_KEY --- # 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: bash maton notion search 'meeting notes' bash maton api '/notion/v1/search' Python: bash 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: bash npm install -g @maton-ai/cli Homebrew: bash brew install maton-ai/cli/maton ## Authentication CLI: bash 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: bash export MATON_API_KEY="YOUR_API_KEY" ## Connection Management Manage your Notion OAuth connections at https://api.maton.ai. ### List Connections CLI: bash maton connection list notion --status ACTIVE bash maton api -X GET /connections -f app=notion -f status=ACTIVE Python: bash 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: bash maton connection create notion bash maton api /connections -f app=notion Python: bash 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: bash maton connection view {connection_id} bash maton api /connections/{connection_id} Python: bash 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: json { "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: bash maton connection delete {connection_id} bash maton api -X DELETE /connections/{connection_id} Python: bash 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: bash maton notion search 'meeting notes' --connection {connection_id} bash maton api /notion/v1/search --connection {connection_id} Python: bash 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: | Concept | Use For | |---------|---------| | Database | Creating databases, getting data source IDs | | Data Source | Querying, updating schema, updating properties | Use GET /databases/{id} to get the data_sources array, then use /data_sources/ endpoints: json { "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: bash POST /notion/v1/search Content-Type: application/json Notion-Version: 2025-09-03 { "query": "meeting notes", "filter": {"property": "object", "value": "page"} } Example: bash maton notion search 'meeting notes' --filter page Search for data sources: bash POST /notion/v1/search Content-Type: application/json Notion-Version: 2025-09-03 { "filter": {"property": "object", "value": "data_source"} } Example: bash maton notion search --filter data_source ### Data Sources #### Get Data Source bash GET /notion/v1/data_sources/{dataSourceId} Notion-Version: 2025-09-03 Example: bash maton notion data-source view <dataSourceId> #### Query Data Source bash 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: bash maton notion data-source query <dataSourceId> \ --filter '{"property":"Status","select":{"equals":"Active"}}' \ --sorts '[{"property":"Created","direction":"descending"}]' \ --page-size 100 #### Update Data Source bash 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: bash maton notion data-source update <dataSourceId> \ --body '{"title":[{"type":"text","text":{"content":"Updated Title"}}],"properties":{"NewColumn":{"rich_text":{}}}}' ### Databases #### Get Database bash GET /notion/v1/databases/{databaseId} Notion-Version: 2025-09-03 Example: bash maton notion database view <databaseId> #### Create Database bash 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: bash 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 bash GET /notion/v1/pages/{pageId} Notion-Version: 2025-09-03 Example: bash maton notion page view <pageId> #### Create Page bash 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: bash maton notion page create --parent-page PARENT_PAGE_ID --title 'New Page' #### Create Page in Data Source bash 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: bash maton notion page create --data-source DATA_SOURCE_ID --title 'New Page' \ --properties '{"Status":{"select":{"name":"Active"}}}' #### Update Page Properties bash PATCH /notion/v1/pages/{pageId} Content-Type: application/json Notion-Version: 2025-09-03 { "properties": { "Status": {"select": {"name": "Done"}} } } Example: bash maton notion page update {pageId} --properties '{"Status":{"select":{"name":"Done"}}}' #### Update Page Icon bash PATCH /notion/v1/pages/{pageId} Content-Type: application/json Notion-Version: 2025-09-03 { "icon": {"type": "emoji", "emoji": "🚀"} } Example: bash maton notion page update {pageId} --icon 🚀 Or with an image URL: bash maton notion page update {pageId} --icon https://example.com/icon.png #### Archive Page bash PATCH /notion/v1/pages/{pageId} Content-Type: application/json Notion-Version: 2025-09-03 { "archived": true } Example: bash maton notion page archive {pageId} ### Blocks #### Get Block Children bash GET /notion/v1/blocks/{blockId}/children Notion-Version: 2025-09-03 Example: bash maton notion block children <blockId> #### Append Block Children bash 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: bash maton notion block append <blockId> \ --children '[{"object":"block","type":"paragraph","paragraph":{"rich_text":[{"type":"text","text":{"content":"New paragraph"}}]}}]' #### Delete Block bash DELETE /notion/v1/blocks/{blockId} Notion-Version: 2025-09-03 Example: bash maton notion block delete <blockId> ### Users #### List Users bash GET /notion/v1/users Notion-Version: 2025-09-03 Example: bash maton notion user list #### Get Current User bash GET /notion/v1/users/me Notion-Version: 2025-09-03 Example: bash 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: bash maton notion data-source query <dataSourceId> --paginate ## Code Examples ### CLI bash # 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 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 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 | Status | Meaning | |--------|---------| | 400 | Missing Notion connection | | 401 | Invalid or missing Maton API key | | 429 | Rate limited (10 req/sec per account) | | 4xx/5xx | Passthrough error from Notion API | ### Troubleshooting: API Key Issues CLI: 1. Check your auth state: bash maton whoami 2. Verify the API key is valid by listing connections: bash maton connection list Manual: 1. Check that the MATON_API_KEY environment variable is set: bash echo $MATON_API_KEY 2. Verify the API key is valid by listing connections: bash 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 - Notion API Introduction - Search - Query Database - Get Page - Create Page - Update Page - Append Block Children - Filter Reference - LLM Reference - Maton CLI Manual - Maton Community - Maton Support