Install
openclaw skills install @gaolfun/notion-integrationIntegrate with Notion API v1 to search, read, create, update, comment on, and archive pages and databases within your workspace.
openclaw skills install @gaolfun/notion-integrationThis skill enables the following operations against the Notion API v1:
POST /searchGET /pages/{id} and GET /blocks/{id}/childrenPOST /pagesPATCH /pages/{id}POST /databasesPOST /databases/{id}/queryPOST /commentsPATCH /pages/{id} with archived: trueActivate this skill when the user says (or implies) any of the following:
secret_By default, an integration can only see content it has been explicitly given access to.
··· (three-dot) menu in the top-right cornerStore the token as an environment variable:
export NOTION_TOKEN="secret_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Or set it in the OpenClaw gateway config under plugins.entries.notion.config.token.
Base URL: https://api.notion.com/v1
Auth: Authorization: Bearer {NOTION_TOKEN}
Notion-Version: 2022-06-28 (required in all request headers)
All requests use JSON. Errors follow the Notion error format.
Endpoint: POST https://api.notion.com/v1/search
When to use: The user wants to find pages or databases by keyword.
Request headers:
Authorization: Bearer {NOTION_TOKEN}
Notion-Version: 2022-06-28
Content-Type: application/json
Request body:
{
"query": "quarterly report",
"filter": { "value": "page", "property": "object" },
"sort": { "direction": "descending", "timestamp": "last_edited_time" },
"page_size": 10
}
filter.value: "page", "database", or omit entirely to search bothpage_size: max 100 per pageSuccess response (200):
{
"object": "list",
"results": [
{
"object": "page",
"id": "abcd1234-abcd-1234-abcd-1234abcd1234",
"properties": {
"title": {
"title": [{ "type": "text", "text": { "content": "Q3 Quarterly Report" } }]
}
},
"last_edited_time": "2024-11-01T12:00:00.000Z",
"url": "https://www.notion.so/abcd1234abcd1234abcd1234abcd1234"
}
],
"has_more": false,
"next_cursor": null
}
Error response (401/403):
{
"object": "error",
"code": "unauthorized",
"message": "Make sure the token is valid and the page is shared with your integration."
}
Endpoint: GET https://api.notion.com/v1/pages/{page_id}
When to use: The user wants to read a specific page's metadata and properties.
Page ID extraction: The ID is the 32-character hex string (with hyphens) at the end of the Notion URL.
URL: https://www.notion.so/Team/Project-Status-1234abcd1234abcd
ID: 1234abcd-1234-abcd-1234-abcd1234abcd1234
Success response (200): Returns page object with properties, url, parent, created_time, and last_edited_time.
To retrieve the page's block content (the actual text):
Endpoint: GET https://api.notion.com/v1/blocks/{page_id}/children?page_size=100
Success response (200):
{
"object": "list",
"results": [
{
"id": "block-uuid-here",
"type": "paragraph",
"has_children": false,
"paragraph": {
"rich_text": [
{
"type": "text",
"text": { "content": "Hello world", "link": null },
"annotations": { "bold": false, "italic": false, "strikethrough": false, "underline": false, "code": false, "color": "default" }
}
]
}
},
{
"id": "heading-block-uuid",
"type": "heading_2",
"heading_2": {
"rich_text": [{ "type": "text", "text": { "content": "Section Title" } }]
}
}
],
"has_more": false,
"next_cursor": null
}
Common block types: paragraph, heading_1, heading_2, heading_3, bulleted_list_item, numbered_list_item, to_do, toggle, code, quote, callout, divider, image, video, embed, bookmark, table, table_row, child_page, unsupported
To check if a block has children and fetch them recursively:
{
"id": "parent-block-id",
"type": "toggle",
"has_children": true,
...
}
# Follow up with: GET /v1/blocks/{parent-block-id}/children
Endpoint: POST https://api.notion.com/v1/pages
When to use: The user wants to create a new Notion page.
Option A — Create as a child of an existing page (sub-page):
{
"parent": { "type": "page_id", "page_id": "parent-page-id" },
"properties": {
"title": {
"title": [{ "type": "text", "text": { "content": "New Project Page" } }]
}
},
"children": [
{
"object": "block",
"type": "paragraph",
"paragraph": {
"rich_text": [{ "type": "text", "text": { "content": "Project kickoff notes." } }]
}
},
{
"object": "block",
"type": "heading_2",
"heading_2": {
"rich_text": [{ "type": "text", "text": { "content": "Goals" } }]
}
}
]
}
Option B — Create as a new entry in a database:
{
"parent": { "type": "database_id", "database_id": "database-id" },
"properties": {
"Name": { "title": [{ "type": "text", "text": { "content": "New Task" } }] },
"Status": { "select": { "name": "To Do" } },
"Priority": { "select": { "name": "High" } },
"Due Date": { "date": { "start": "2024-12-31" } },
"Estimate": { "number": 3 }
}
}
Success response (200): Returns the created page object with id and url.
Error (400) — missing title:
{
"object": "error",
"code": "validation_error",
"message": "title is missing and is required."
}
Error (400) — unknown property:
{
"object": "error",
"code": "validation_error",
"message": "Unknown property 'Foo' in properties."
}
Endpoint: PATCH https://api.notion.com/v1/pages/{page_id}
When to use: The user wants to update a property on an existing page (rename, change status, set due date, etc.).
Update title:
{
"properties": {
"title": {
"title": [{ "type": "text", "text": { "content": "Updated Page Title" } }]
}
}
}
Update select / status:
{
"properties": {
"Status": { "select": { "name": "In Progress" } }
}
}
Clear a select (set to null):
{
"properties": {
"Status": { "select": null }
}
}
Update date:
{
"properties": {
"Due Date": { "date": { "start": "2024-12-31", "end": "2025-01-15" } }
}
}
Clear date:
{
"properties": {
"Due Date": { "date": null }
}
}
Update number:
{
"properties": {
"Estimate": { "number": 8 }
}
}
Update checkbox:
{
"properties": {
"Complete": { "checkbox": true }
}
}
Update people (by email):
{
"properties": {
"Assignee": { "people": [{ "object": "user", "id": "user-id-or-email" }] }
}
}
Update multi-select:
{
"properties": {
"Tags": { "multi_select": [{ "name": "urgent" }, { "name": "frontend" }] }
}
}
Success response (200): Returns the updated page object.
Endpoint: POST https://api.notion.com/v1/databases
When to use: The user wants to create a new database (table) under a parent page.
Request body:
{
"parent": { "type": "page_id", "page_id": "parent-page-id" },
"title": [{ "type": "text", "text": { "content": "Project Tracker" } }],
"properties": {
"Name": { "title": {} },
"Status": {
"select": {
"options": [
{ "name": "To Do", "color": "red" },
{ "name": "In Progress", "color": "yellow" },
{ "name": "Done", "color": "green" }
]
}
},
"Priority": {
"select": {
"options": [
{ "name": "High", "color": "red" },
{ "name": "Medium", "color": "orange" },
{ "name": "Low", "color": "blue" }
]
}
},
"Due Date": { "date": {} },
"Estimate": { "number": { "format": "number" } },
"Complete": { "checkbox": {} },
"URL": { "url": {} }
}
}
Success response (200): Returns the created database object. Save the id field — it is used as database_id in subsequent queries and page creation.
Note: Once created, the property schema (types and names) is fixed. To change it, you must modify the database in the Notion UI.
Endpoint: POST https://api.notion.com/v1/databases/{database_id}/query
When to use: The user wants to list, filter, and sort entries in a Notion database.
Basic query — return all entries (up to 100):
{
"page_size": 100
}
With filter — Status equals "In Progress":
{
"filter": {
"property": "Status",
"select": { "equals": "In Progress" }
}
}
With filter — Due Date before a date:
{
"filter": {
"property": "Due Date",
"date": { "before": "2024-12-31" }
}
}
With filter — Title contains text:
{
"filter": {
"property": "Name",
"title": { "contains": "launch" }
}
}
With compound filter (AND):
{
"filter": {
"and": [
{ "property": "Status", "select": { "equals": "To Do" } },
{ "property": "Priority", "select": { "equals": "High" } }
]
}
}
With compound filter (OR):
{
"filter": {
"or": [
{ "property": "Status", "select": { "equals": "Done" } },
{ "property": "Status", "select": { "equals": "Cancelled" } }
]
}
}
Sort by Due Date ascending:
{
"sorts": [{ "property": "Due Date", "direction": "ascending" }]
}
Sort by multiple fields:
{
"sorts": [
{ "property": "Status", "direction": "ascending" },
{ "property": "Priority", "direction": "ascending" }
]
}
Pagination — get next page:
{
"start_cursor": "cursor-string-from-previous-response",
"page_size": 100
}
Success response (200):
{
"object": "list",
"results": [
{
"object": "page",
"id": "page-uuid",
"properties": {
"Name": {
"title": [{ "text": { "content": "Task A" } }]
},
"Status": { "select": { "name": "In Progress" } },
"Priority": { "select": { "name": "High" } },
"Due Date": { "date": { "start": "2024-12-01" } }
},
"url": "https://www.notion.so/page-uuid"
}
],
"has_more": true,
"next_cursor": "next-page-cursor-string"
}
Endpoint: POST https://api.notion.com/v1/comments
When to use: The user wants to leave a comment on a Notion page.
Request body — new discussion thread:
{
"parent": { "type": "page_id", "page_id": "target-page-id" },
"rich_text": [
{
"type": "text",
"text": { "content": "This looks great! Let's discuss in the next standup." }
}
]
}
Request body — reply to an existing thread:
{
"discussion_id": "discussion-id-from-existing-comment",
"rich_text": [
{
"type": "text",
"text": { "content": "Agreed. Updating the estimate." }
}
]
}
Success response (200): Returns the created comment object including id and discussion_id.
Endpoint: PATCH https://api.notion.com/v1/pages/{page_id}
When to use: The user wants to move a page to trash.
Archive a page:
{
"archived": true
}
Restore an archived page:
{
"archived": false
}
Success response (200): Returns the updated page object.
Note: Notion retains archived pages for 30 days before permanent deletion. Archived pages do not appear in default search results.
✅ Notion API — Success
- Operation: [operation name]
- Page/Database ID: [id]
- Title: [title if available]
- URL: https://www.notion.so/[id]
- Last edited: [ISO timestamp]
- Details: [any additional relevant fields]
❌ Notion API — Error
- Operation: [operation name]
- Error code: [error code string]
- Message: [human-readable error message from Notion]
- Suggestion: [what to try or check]
User: "Search Notion for 'project roadmap'"
Agent (using this skill):
POST /search
Body: { "query": "project roadmap", "page_size": 10 }
✅ Notion API — Success
- Operation: Search pages/databases
- Query: "project roadmap"
- Results: 3 items found
1. 📄 Q4 Project Roadmap (page)
ID: abcd1234-abcd-1234-abcd-1234abcd1234
Last edited: 2024-11-01T10:00:00Z
URL: https://www.notion.so/abcd1234abcd1234abcd1234abcd1234
2. 🗃 Roadmap Tracker (database)
ID: efgh5678-5678-5678-efgh56785678
Last edited: 2024-10-28T14:30:00Z
URL: https://www.notion.so/efgh567856785678efgh56785678
3. 📄 Product Roadmap 2025 (page)
ID: ijkl9012-9012-9012-ijkl90129012
Last edited: 2024-10-15T09:00:00Z
URL: https://www.notion.so/ijkl901290129012ijkl90129012
User: "Show me all high-priority tasks that are not done in the Sprint database"
Agent (using this skill):
POST /databases/{database_id}/query
Body: {
"filter": {
"and": [
{ "property": "Priority", "select": { "equals": "High" } },
{ "property": "Status", "select": { "does_not_equal": "Done" } }
]
},
"sorts": [{ "property": "Due Date", "direction": "ascending" }]
}
✅ Notion API — Success
- Operation: Query database "Sprint"
- Filtered: Priority=High AND Status≠Done
- Sorted by: Due Date ↑
1. 🔴 Design Review
Status: To Do | Priority: High | Due: 2024-12-05
URL: https://www.notion.so/page-id-1
2. 🔴 API Integration
Status: In Progress | Priority: High | Due: 2024-12-08
URL: https://www.notion.so/page-id-2
3. 🔴 Testing Phase
Status: To Do | Priority: High | Due: 2024-12-10
URL: https://www.notion.so/page-id-3
User: "Create a new Notion page under 'Projects > Alpha' titled 'Sprint 5 Notes' and add a paragraph that says 'Kicked off sprint 5 planning.'"
Agent (using this skill):
POST /pages
Body: {
"parent": { "type": "page_id", "page_id": "parent-page-id" },
"properties": {
"title": { "title": [{ "type": "text", "text": { "content": "Sprint 5 Notes" } }] }
},
"children": [
{
"object": "block",
"type": "paragraph",
"paragraph": {
"rich_text": [{ "type": "text", "text": { "content": "Kicked off sprint 5 planning." } }]
}
}
]
}
✅ Notion API — Success
- Operation: Create page
- Title: Sprint 5 Notes
- Page ID: new-page-id-here
- URL: https://www.notion.so/newpageidhere
- Content: 1 block (paragraph) added
OpenClaw (or any preferred name)secret_)Add to your shell profile:
export NOTION_TOKEN="secret_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Or configure in the OpenClaw gateway config:
{
"plugins": {
"entries": {
"notion": {
"config": {
"token": "secret_xxx"
}
}
}
}
}
For the integration to access any page or database:
··· in the top-right cornerTest with a search call:
POST https://api.notion.com/v1/search
Authorization: Bearer {NOTION_TOKEN}
Notion-Version: 2022-06-28
Content-Type: application/json
Body: { "query": "test", "page_size": 1 }
| Plan | Requests per minute |
|---|---|
| Free | 3 requests/second (~180/min) |
| Plus | 7 requests/second (~420/min) |
| Business | 15 requests/second (~900/min) |
Retry-After header. Wait that many seconds before retrying.has_more before making follow-up requests to avoid infinite loops.has_children: true flag on a block to detect children, then fetch them with GET /blocks/{id}/children.parent: { "type": "page_id", "page_id": "..." } creates a standalone sub-pageparent: { "type": "database_id", "database_id": "..." } creates a new row (entry) in that databaseproperties must match the database schema exactly — unknown or mismatched properties cause a validation_error[{ ... }].rich_text array item is 2000 characters. For longer content, split into multiple blocks.archived: true moves a page to trash; it remains accessible via APIPATCH /pages/{id} with archived: falseGET /databases/{id} first to fetch the current schema before creating or updating entriesabcd1234-abcd-1234-abcd-1234abcd1234Last updated: 2024-12-01 — aligned with Notion API v1, Notion-Version 2022-06-28