Skill: jolestar/notion-openapi-skill (version 1.0.0)

--- name: notion-openapi-skill description: Operate Notion Public API through UXC with a curated OpenAPI schema for search, block traversal, page reads, content writes, and data source/database inspection. Use when tasks need recursive reads or structured writes that Notion MCP does not expose directly. --- # Notion Public API Skill Use this skill to run Notion Public API operations through `uxc` + OpenAPI. Reuse the `uxc` skill for shared execution, OAuth, and error-handling guidance. ## Prerequisites - `uxc` is installed and available in `PATH`. - Network access to `https://api.notion.com/v1`. - Access to the curated OpenAPI schema URL: - `https://raw.githubusercontent.com/holon-run/uxc/main/skills/notion-openapi-skill/references/notion-public.openapi.json` - A Notion integration token or OAuth credential with content read access. - For writes, the integration also needs the corresponding Notion insert/update content capabilities. ## Scope This skill covers a read-first Notion REST surface focused on traversal plus common content writes: - token identity validation - title search for pages, data sources, and databases - page lookup - block lookup - recursive traversal via block-children pagination - page property retrieval - page creation - page updates, including trash/restore via `in_trash` - block append - block updates - block deletion - data source retrieval and query - legacy database retrieval and query This skill does **not** cover: - full Notion REST coverage - comments, file uploads, webhooks, page move, or schema mutation - automatic recursive traversal loops inside one single command ## Endpoint And Version - base URL: `https://api.notion.com/v1` - required version header for this skill: `Notion-Version: 2026-03-11` The schema is intentionally curated around traversal and schema discovery. It is not a full dump of the Notion API. ## Authentication Notion Public API requires: - `Authorization: Bearer <token>` - `Notion-Version: 2026-03-11` ### Recommended: dedicated REST credential If you already have an internal integration token: ```bash uxc auth credential set notion-openapi \ --auth-type api_key \ --header "Authorization=Bearer {{secret}}" \ --header "Notion-Version=2026-03-11" \ --secret-env NOTION_API_TOKEN uxc auth binding add \ --id notion-openapi \ --host api.notion.com \ --path-prefix /v1 \ --scheme https \ --credential notion-openapi \ --priority 100 ``` How to get the internal integration token: 1. Open the Notion integrations dashboard and create an internal integration for your workspace. 2. In the integration configuration page, copy the API secret shown by Notion. 3. In Notion UI, open each target page, data source, or database and add this integration via `Share` or `Connections`. 4. Use that API secret as `NOTION_API_TOKEN` or pass it directly to `uxc auth credential set`. Without connecting the integration to the target content, REST calls may authenticate successfully but still fail with access errors or return incomplete search results. If you want OAuth-managed tokens for the REST host: ```bash uxc auth oauth start notion-openapi \ --endpoint https://api.notion.com/v1 \ --redirect-uri http://127.0.0.1:8788/callback \ --client-id <client_id> \ --scope read uxc auth oauth complete notion-openapi \ --session-id <session_id> \ --authorization-response 'http://127.0.0.1:8788/callback?code=...' uxc auth credential set notion-openapi \ --auth-type oauth \ --header "Authorization=Bearer {{secret}}" \ --header "Notion-Version=2026-03-11" uxc auth binding add \ --id notion-openapi \ --host api.notion.com \ --path-prefix /v1 \ --scheme https \ --credential notion-openapi \ --priority 100 ``` ### Advanced: reuse the same OAuth credential as `notion-mcp` This is technically possible in `uxc` if the existing credential already has a valid Notion OAuth access token. Important: - once an OAuth credential uses custom headers, include `Authorization=Bearer {{secret}}` explicitly - adding `Notion-Version=2026-03-11` on the shared credential means the same header will also be sent to `mcp.notion.com/mcp` - that extra header is expected to be harmless, but this is an interoperability assumption rather than an explicit Notion guarantee Shared-credential setup: ```bash uxc auth credential set notion-mcp \ --auth-type oauth \ --header "Authorization=Bearer {{secret}}" \ --header "Notion-Version=2026-03-11" uxc auth binding add \ --id notion-openapi-shared \ --host api.notion.com \ --path-prefix /v1 \ --scheme https \ --credential notion-mcp \ --priority 100 ``` Validate the effective mapping when auth looks wrong: ```bash uxc auth binding match https://api.notion.com/v1 ``` ## Core Workflow 1. Use the fixed link command by default: - `command -v notion-openapi-cli` - If missing, create it: `uxc link notion-openapi-cli https://api.notion.com/v1 --schema-url https://raw.githubusercontent.com/holon-run/uxc/main/skills/notion-openapi-skill/references/notion-public.openapi.json` - `notion-openapi-cli -h` 2. Inspect operation schema first: - `notion-openapi-cli post:/search -h` - `notion-openapi-cli get:/blocks/{block_id}/children -h` - `notion-openapi-cli post:/pages -h` - `notion-openapi-cli patch:/blocks/{block_id}/children -h` - `notion-openapi-cli post:/data_sources/{data_source_id}/query -h` 3. Prefer read validation before broader traversal: - `notion-openapi-cli get:/users/me` - `notion-openapi-cli post:/search '{"query":"Roadmap","filter":{"property":"object","value":"page"},"page_size":10}'` - `notion-openapi-cli get:/blocks/{block_id}/children block_id=<uuid> page_size=100` 4. Traverse recursively outside the API call boundary: - use `get:/blocks/{block_id}/children` page by page - for every returned child block with `has_children=true`, call `get:/blocks/{block_id}/children` again on that child ID 5. Use data source or legacy database reads to discover schema before property-sensitive queries: - `notion-openapi-cli get:/data_sources/{data_source_id} data_source_id=<uuid>` - `notion-openapi-cli post:/data_sources/{data_source_id}/query data_source_id=<uuid> '{"page_size":25}'` - `notion-openapi-cli get:/databases/{database_id} database_id=<uuid>` 6. Execute writes only after explicit user confirmation: - create page: `notion-openapi-cli post:/pages '{...}'` - append blocks: `notion-openapi-cli patch:/blocks/{block_id}/children '{...}'` - update page or block: `notion-openapi-cli patch:/pages/{page_id} '{...}'` - delete block: `notion-openapi-cli delete:/blocks/{block_id} block_id=<uuid>` ## Operation Groups ### Session / Discovery - `get:/users/me` - `post:/search` ### Page And Block Traversal - `get:/pages/{page_id}` - `get:/pages/{page_id}/properties/{property_id}` - `get:/blocks/{block_id}` - `get:/blocks/{block_id}/children` ### Page And Block Writes - `post:/pages` - `patch:/pages/{page_id}` - `patch:/blocks/{block_id}/children` - `patch:/blocks/{block_id}` - `delete:/blocks/{block_id}` ### Data Source Reads - `get:/data_sources/{data_source_id}` - `post:/data_sources/{data_source_id}/query` ### Legacy Database Reads - `get:/databases/{database_id}` - `post:/databases/{database_id}/query` ## Guardrails - Keep automation on the JSON output envelope; do not use `--text`. - Parse stable fields first: `ok`, `kind`, `protocol`, `data`, `error`. - This skill fixes `Notion-Version` at the credential/header layer instead of requiring it as an operation argument. Keep the credential header on `2026-03-11` unless you intentionally migrate the whole skill surface. - This skill is read-first, but it now includes common content writes. Always confirm intent before `post:/pages`, `patch:/pages/{page_id}`, `patch:/blocks/{block_id}/children`, `patch:/blocks/{block_id}`, or `delete:/blocks/{block_id}`. - On Notion API version `2026-03-11`, `archived` has been replaced by `in_trash` for request and response semantics. Prefer `in_trash` in update payloads. - `patch:/blocks/{block_id}/children` supports up to 100 new children in one request and up to two levels of nested blocks in a single payload. - `patch:/blocks/{block_id}` updates block content, but it does not update child lists. Use `patch:/blocks/{block_id}/children` to append nested content. - Prefer `data_sources` endpoints over legacy `databases` endpoints for new workflows. Keep legacy database reads only for compatibility with older shared links and IDs. - `get:/blocks/{block_id}/children` returns only one nesting level at a time. Recursive traversal must be performed by repeated calls. - Notion may return fewer results than `page_size`; always check `has_more` and `next_cursor`. - `notion-openapi-cli <operation> ...` is equivalent to `uxc https://api.notion.com/v1 --schema-url <notion_openapi_schema> <operation> ...`. ## References - Usage patterns: `references/usage-patterns.md` - Curated OpenAPI schema: `references/notion-public.openapi.json` - Notion API reference: https://developers.notion.com/reference - Retrieve a database: https://developers.notion.com/reference/retrieve-a-database - Retrieve a block: https://developers.notion.com/reference/retrieve-a-block - Versioning: https://developers.notion.com/reference/versioning

path: agents/openai.yaml
content: agent:
  display_name: "Notion Public API"
  short_description: "Traverse Notion pages, blocks, and data sources via UXC + OpenAPI"
  default_prompt: "Use $notion-openapi-skill to search, traverse, and inspect Notion workspace content through the Notion Public API with UXC, following version-header and read-first guardrails."


path: references/notion-public.openapi.json
content: {
  "openapi": "3.0.3",
  "info": {
    "title": "Notion Public API (Curated Traversal Surface)",
    "version": "1.0.0",
    "description": "Curated Notion Public API schema for search, block traversal, page reads, and data source inspection in UXC."
  },
  "servers": [
    {
      "url": "https://api.notion.com/v1"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/users/me": {
      "get": {
        "operationId": "get:/users/me",
        "summary": "Retrieve the bot user for the active token",
        "responses": {
          "200": {
            "description": "Current bot user",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenericObject"
                }
              }
            }
          }
        },
        "tags": [
          "users"
        ]
      }
    },
    "/search": {
      "post": {
        "operationId": "post:/search",
        "summary": "Search pages, data sources, and databases by title or object filter",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SearchRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Search results",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginatedObjectList"
                }
              }
            }
          }
        },
        "tags": [
          "search"
        ]
      }
    },
    "/pages/{page_id}": {
      "get": {
        "operationId": "get:/pages/{page_id}",
        "summary": "Retrieve a page by ID",
        "parameters": [
          {
            "name": "page_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "filter_properties[]",
            "in": "query",
            "required": false,
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenericObject"
                }
              }
            }
          }
        },
        "tags": [
          "pages"
        ]
      },
      "patch": {
        "operationId": "patch:/pages/{page_id}",
        "summary": "Update page properties, icon, cover, lock state, or trash state",
        "parameters": [
          {
            "name": "page_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PageUpdateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated page object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenericObject"
                }
              }
            }
          }
        },
        "tags": [
          "pages"
        ]
      }
    },
    "/pages": {
      "post": {
        "operationId": "post:/pages",
        "summary": "Create a page under a page, block, data source, or workspace parent",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PageCreateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Created page object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenericObject"
                }
              }
            }
          }
        },
        "tags": [
          "pages"
        ]
      }
    },
    "/pages/{page_id}/properties/{property_id}": {
      "get": {
        "operationId": "get:/pages/{page_id}/properties/{property_id}",
        "summary": "Retrieve one page property item",
        "parameters": [
          {
            "name": "page_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "property_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "start_cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page_size",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page property item response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenericObject"
                }
              }
            }
          }
        },
        "tags": [
          "pages"
        ]
      }
    },
    "/blocks/{block_id}": {
      "get": {
        "operationId": "get:/blocks/{block_id}",
        "summary": "Retrieve a block by ID",
        "parameters": [
          {
            "name": "block_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Block object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenericObject"
                }
              }
            }
          }
        },
        "tags": [
          "blocks"
        ]
      },
      "patch": {
        "operationId": "patch:/blocks/{block_id}",
        "summary": "Update the content or trash state of a block",
        "parameters": [
          {
            "name": "block_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BlockUpdateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated block object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenericObject"
                }
              }
            }
          }
        },
        "tags": [
          "blocks"
        ]
      },
      "delete": {
        "operationId": "delete:/blocks/{block_id}",
        "summary": "Move a block to trash",
        "parameters": [
          {
            "name": "block_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted or trashed block object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenericObject"
                }
              }
            }
          }
        },
        "tags": [
          "blocks"
        ]
      }
    },
    "/blocks/{block_id}/children": {
      "get": {
        "operationId": "get:/blocks/{block_id}/children",
        "summary": "Retrieve one level of child blocks for traversal",
        "parameters": [
          {
            "name": "block_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "start_cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page_size",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated child block list",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginatedObjectList"
                }
              }
            }
          }
        },
        "tags": [
          "blocks"
        ]
      },
      "patch": {
        "operationId": "patch:/blocks/{block_id}/children",
        "summary": "Append child blocks to a parent block, page, or database block",
        "parameters": [
          {
            "name": "block_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AppendBlockChildrenRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Paginated list of appended first-level blocks",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginatedObjectList"
                }
              }
            }
          }
        },
        "tags": [
          "blocks"
        ]
      }
    },
    "/data_sources/{data_source_id}": {
      "get": {
        "operationId": "get:/data_sources/{data_source_id}",
        "summary": "Retrieve a data source by ID",
        "parameters": [
          {
            "name": "data_source_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Data source object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenericObject"
                }
              }
            }
          }
        },
        "tags": [
          "data_sources"
        ]
      }
    },
    "/data_sources/{data_source_id}/query": {
      "post": {
        "operationId": "post:/data_sources/{data_source_id}/query",
        "summary": "Query pages contained in a data source",
        "parameters": [
          {
            "name": "data_source_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "filter_properties[]",
            "in": "query",
            "required": false,
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DataSourceQueryRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Paginated data source results",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginatedObjectList"
                }
              }
            }
          }
        },
        "tags": [
          "data_sources"
        ]
      }
    },
    "/databases/{database_id}": {
      "get": {
        "operationId": "get:/databases/{database_id}",
        "summary": "Retrieve a legacy database by ID",
        "parameters": [
          {
            "name": "database_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Database object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenericObject"
                }
              }
            }
          }
        },
        "tags": [
          "databases"
        ]
      }
    },
    "/databases/{database_id}/query": {
      "post": {
        "operationId": "post:/databases/{database_id}/query",
        "summary": "Query pages contained in a legacy database",
        "parameters": [
          {
            "name": "database_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "filter_properties[]",
            "in": "query",
            "required": false,
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DatabaseQueryRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Paginated database results",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginatedObjectList"
                }
              }
            }
          }
        },
        "tags": [
          "databases"
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer"
      }
    },
    "schemas": {
      "GenericObject": {
        "type": "object",
        "additionalProperties": true
      },
      "PaginatedObjectList": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GenericObject"
            }
          }
        },
        "additionalProperties": true
      },
      "SearchRequest": {
        "type": "object",
        "properties": {
          "query": {
            "type": "string"
          },
          "sort": {
            "$ref": "#/components/schemas/GenericObject"
          },
          "filter": {
            "$ref": "#/components/schemas/GenericObject"
          },
          "start_cursor": {
            "type": "string"
          },
          "page_size": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100
          }
        },
        "additionalProperties": true
      },
      "DataSourceQueryRequest": {
        "type": "object",
        "properties": {
          "filter": {
            "$ref": "#/components/schemas/GenericObject"
          },
          "sorts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GenericObject"
            }
          },
          "start_cursor": {
            "type": "string"
          },
          "page_size": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100
          },
          "in_trash": {
            "type": "boolean"
          },
          "result_type": {
            "type": "string",
            "enum": [
              "page",
              "data_source"
            ]
          }
        },
        "additionalProperties": true
      },
      "DatabaseQueryRequest": {
        "type": "object",
        "properties": {
          "filter": {
            "$ref": "#/components/schemas/GenericObject"
          },
          "sorts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GenericObject"
            }
          },
          "start_cursor": {
            "type": "string"
          },
          "page_size": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100
          },
          "in_trash": {
            "type": "boolean"
          }
        },
        "additionalProperties": true
      },
      "PositionObject": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "end",
              "start",
              "after_block"
            ]
          },
          "after_block": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              }
            },
            "required": [
              "id"
            ],
            "additionalProperties": true
          }
        },
        "additionalProperties": true
      },
      "PageCreateRequest": {
        "type": "object",
        "properties": {
          "parent": {
            "$ref": "#/components/schemas/GenericObject"
          },
          "properties": {
            "$ref": "#/components/schemas/GenericObject"
          },
          "children": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GenericObject"
            },
            "maxItems": 100
          },
          "icon": {
            "$ref": "#/components/schemas/GenericObject"
          },
          "cover": {
            "$ref": "#/components/schemas/GenericObject"
          },
          "template": {
            "$ref": "#/components/schemas/GenericObject"
          }
        },
        "required": [
          "parent",
          "properties"
        ],
        "additionalProperties": true
      },
      "PageUpdateRequest": {
        "type": "object",
        "properties": {
          "properties": {
            "$ref": "#/components/schemas/GenericObject"
          },
          "icon": {
            "$ref": "#/components/schemas/GenericObject"
          },
          "cover": {
            "$ref": "#/components/schemas/GenericObject"
          },
          "is_locked": {
            "type": "boolean"
          },
          "in_trash": {
            "type": "boolean"
          }
        },
        "additionalProperties": true
      },
      "AppendBlockChildrenRequest": {
        "type": "object",
        "properties": {
          "children": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GenericObject"
            },
            "maxItems": 100
          },
          "position": {
            "$ref": "#/components/schemas/PositionObject"
          }
        },
        "required": [
          "children"
        ],
        "additionalProperties": true
      },
      "BlockUpdateRequest": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string"
          },
          "in_trash": {
            "type": "boolean"
          }
        },
        "additionalProperties": true
      }
    }
  }
}


path: references/usage-patterns.md
content: # Notion Public API Skill - Usage Patterns

All commands below assume the credential injects both:

- `Authorization: Bearer <token>`
- `Notion-Version: 2026-03-11`

## Link Setup

```bash
command -v notion-openapi-cli
uxc link notion-openapi-cli https://api.notion.com/v1 \
  --schema-url https://raw.githubusercontent.com/holon-run/uxc/main/skills/notion-openapi-skill/references/notion-public.openapi.json
notion-openapi-cli -h
```

## Auth Setup

Existing internal integration token:

```bash
uxc auth credential set notion-openapi \
  --auth-type api_key \
  --header "Authorization=Bearer {{secret}}" \
  --header "Notion-Version=2026-03-11" \
  --secret-env NOTION_API_TOKEN

uxc auth binding add \
  --id notion-openapi \
  --host api.notion.com \
  --path-prefix /v1 \
  --scheme https \
  --credential notion-openapi \
  --priority 100
```

Internal integration token source:

- Create an internal integration in Notion's integrations dashboard.
- Copy the API secret from the integration configuration page.
- Connect that integration to each target page, data source, or database in Notion UI.
- Export it as `NOTION_API_TOKEN` or substitute the secret directly in the credential command.

Shared OAuth credential from Notion MCP:

```bash
uxc auth credential set notion-mcp \
  --auth-type oauth \
  --header "Authorization=Bearer {{secret}}" \
  --header "Notion-Version=2026-03-11"

uxc auth binding add \
  --id notion-openapi-shared \
  --host api.notion.com \
  --path-prefix /v1 \
  --scheme https \
  --credential notion-mcp \
  --priority 100
```

## Read Examples

```bash
# Validate token identity
notion-openapi-cli get:/users/me

# Search pages by title
notion-openapi-cli post:/search \
  '{"query":"Roadmap","filter":{"property":"object","value":"page"},"page_size":10}'

# Retrieve one page
notion-openapi-cli get:/pages/{page_id} page_id=<page_uuid>

# Retrieve one block
notion-openapi-cli get:/blocks/{block_id} block_id=<block_uuid>

# List one level of child blocks
notion-openapi-cli get:/blocks/{block_id}/children \
  block_id=<block_uuid> \
  page_size=100

# Continue block traversal with a pagination cursor
notion-openapi-cli get:/blocks/{block_id}/children \
  block_id=<block_uuid> \
  page_size=100 \
  start_cursor=<cursor>

# Read one page property item
notion-openapi-cli get:/pages/{page_id}/properties/{property_id} \
  page_id=<page_uuid> \
  property_id=<property_id_or_name>

# Read current data source schema
notion-openapi-cli get:/data_sources/{data_source_id} data_source_id=<data_source_uuid>

# Query pages in a data source
notion-openapi-cli post:/data_sources/{data_source_id}/query \
  data_source_id=<data_source_uuid> \
  '{"page_size":25}'

# Query a data source with filter + sort
notion-openapi-cli post:/data_sources/{data_source_id}/query \
  data_source_id=<data_source_uuid> \
  '{"filter":{"property":"Status","status":{"equals":"In progress"}},"sorts":[{"property":"Updated time","direction":"descending"}],"page_size":25}'

# Compatibility path for older database-based workflows
notion-openapi-cli get:/databases/{database_id} database_id=<database_uuid>
```

## Write Examples (Confirm Intent First)

```bash
# Create a page under a parent page
notion-openapi-cli post:/pages \
  '{"parent":{"page_id":"<parent_page_uuid>"},"properties":{"title":[{"text":{"content":"Draft from UXC"}}]}}'

# Create a page under a data source
notion-openapi-cli post:/pages \
  '{"parent":{"data_source_id":"<data_source_uuid>"},"properties":{"Name":{"title":[{"text":{"content":"Task from UXC"}}]}}}'

# Update page properties or trash state
notion-openapi-cli patch:/pages/{page_id} \
  page_id=<page_uuid> \
  '{"properties":{"Status":{"status":{"name":"In progress"}}},"in_trash":false}'

# Append paragraph blocks to a page or block
notion-openapi-cli patch:/blocks/{block_id}/children \
  block_id=<parent_block_or_page_uuid> \
  '{"children":[{"object":"block","type":"paragraph","paragraph":{"rich_text":[{"type":"text","text":{"content":"Hello from UXC"}}]}}]}'

# Append after a specific sibling using 2026-03-11 position syntax
notion-openapi-cli patch:/blocks/{block_id}/children \
  block_id=<parent_block_uuid> \
  '{"children":[{"object":"block","type":"to_do","to_do":{"rich_text":[{"type":"text","text":{"content":"Follow-up item"}}]}}],"position":{"type":"after_block","after_block":{"id":"<sibling_block_uuid>"}}}'

# Update one block's content
notion-openapi-cli patch:/blocks/{block_id} \
  block_id=<block_uuid> \
  '{"paragraph":{"rich_text":[{"type":"text","text":{"content":"Updated paragraph text"}}]}}'

# Delete (trash/archive) a block
notion-openapi-cli delete:/blocks/{block_id} block_id=<block_uuid>
```

## Recursive Traversal Pattern

```bash
# 1. Retrieve one parent block or page
notion-openapi-cli get:/blocks/{block_id} block_id=<root_block_uuid>

# 2. Retrieve its first-level children
notion-openapi-cli get:/blocks/{block_id}/children block_id=<root_block_uuid> page_size=100

# 3. For each returned child block where has_children=true, call:
notion-openapi-cli get:/blocks/{block_id}/children block_id=<child_block_uuid> page_size=100
```

## Fallback Equivalence

- `notion-openapi-cli <operation> ...` is equivalent to
  `uxc https://api.notion.com/v1 --schema-url <notion_openapi_schema> <operation> ...`.


path: scripts/validate.sh
content: #!/usr/bin/env bash
set -euo pipefail

ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
SKILL_DIR="${ROOT_DIR}/skills/notion-openapi-skill"
SKILL_FILE="${SKILL_DIR}/SKILL.md"
OPENAI_FILE="${SKILL_DIR}/agents/openai.yaml"
USAGE_FILE="${SKILL_DIR}/references/usage-patterns.md"
SCHEMA_FILE="${SKILL_DIR}/references/notion-public.openapi.json"

fail() {
  printf '[validate] error: %s\n' "$*" >&2
  exit 1
}

need_cmd() {
  command -v "$1" >/dev/null 2>&1 || fail "required command not found: $1"
}

need_cmd jq
need_cmd rg

for file in "${SKILL_FILE}" "${OPENAI_FILE}" "${USAGE_FILE}" "${SCHEMA_FILE}"; do
  [[ -f "${file}" ]] || fail "missing required file: ${file}"
done

jq -e '.openapi and .paths and .components' "${SCHEMA_FILE}" >/dev/null 2>&1 || fail 'invalid OpenAPI schema JSON or missing .openapi/.paths/.components'
jq -e '.paths["/blocks/{block_id}/children"] and .paths["/data_sources/{data_source_id}/query"] and .paths["/databases/{database_id}"] and .paths["/pages"].post and .paths["/pages/{page_id}"].patch and .paths["/blocks/{block_id}"].patch and .paths["/blocks/{block_id}"].delete and .paths["/blocks/{block_id}/children"].patch' "${SCHEMA_FILE}" >/dev/null 2>&1 || fail 'OpenAPI schema missing expected traversal, write, or compatibility paths'
jq -e '.components.parameters == null or .components.parameters.NotionVersion == null' "${SCHEMA_FILE}" >/dev/null 2>&1 || fail 'Notion-Version should not remain as an OpenAPI operation parameter in this skill'

rg -q '^name:\s*notion-openapi-skill\s*$' "${SKILL_FILE}" || fail 'invalid skill name'
rg -q '^description:\s*.+' "${SKILL_FILE}" || fail 'missing description'

rg -q 'command -v notion-openapi-cli' "${SKILL_FILE}" || fail 'missing link-first command check'
rg -q 'uxc link notion-openapi-cli https://api.notion.com/v1 --schema-url ' "${SKILL_FILE}" || fail 'missing fixed link create command with schema-url'
rg -q 'notion-openapi-cli -h' "${SKILL_FILE}" || fail 'missing help-first host discovery example'
rg -q 'notion-openapi-cli post:/search -h' "${SKILL_FILE}" || fail 'missing operation-level help example'
rg -Fq 'notion-openapi-cli post:/pages -h' "${SKILL_FILE}" || fail 'missing page-create help example'
rg -Fq 'notion-openapi-cli patch:/blocks/{block_id}/children -h' "${SKILL_FILE}" || fail 'missing block-append help example'
rg -Fq 'Notion-Version=2026-03-11' "${SKILL_FILE}" "${USAGE_FILE}" || fail 'missing required Notion-Version guidance'
rg -Fq 'Authorization=Bearer {{secret}}' "${SKILL_FILE}" "${USAGE_FILE}" || fail 'missing explicit Authorization header guidance'
rg -q 'notion-mcp' "${SKILL_FILE}" || fail 'missing shared oauth credential guidance'
rg -q 'read-first' "${SKILL_FILE}" || fail 'missing read-first guardrail'
rg -Fq 'delete:/blocks/{block_id}' "${SKILL_FILE}" "${USAGE_FILE}" || fail 'missing delete block guidance'
rg -Fq 'patch:/pages/{page_id}' "${SKILL_FILE}" "${USAGE_FILE}" || fail 'missing update page guidance'

if rg -q -- "--args\\s+'\\{" "${SKILL_FILE}" "${USAGE_FILE}"; then
  fail 'found banned legacy JSON argument pattern'
fi

rg -q '^\s*display_name:\s*"Notion Public API"\s*$' "${OPENAI_FILE}" || fail 'missing display_name'
rg -q '^\s*short_description:\s*".+"\s*$' "${OPENAI_FILE}" || fail 'missing short_description'
rg -q '^\s*default_prompt:\s*".*\$notion-openapi-skill.*"\s*$' "${OPENAI_FILE}" || fail 'default_prompt must mention $notion-openapi-skill'

echo "skills/notion-openapi-skill validation passed"


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)