notion
Notion API + ntn CLI: pages, databases, markdown, Workers.
Notion API + ntn CLI: pages, databases, markdown, Workers.
Real data. Real impact.
Emerging
Developers
Per week
Excellent
Skills give you superpowers. Install in 30 seconds.
Talk to Notion two ways. Same integration token works for both — pick by what's available.
◆
CLI — Notion's official CLI. Shorter syntax, one-line file uploads, required for Workers. macOS + Linux only as of May 2026 (Windows support "coming soon"). Default when installed.
◆ HTTP + curl — works everywhere including Windows. Default fallback when ntn
ntn isn't installed.
ntn_ or secret_)~/.hermes/.env:
NOTION_API_KEY=ntn_your_key_here
... → Connect to → your integration name. Without this, the API returns 404 for that page even though it exists.ntn (preferred path on macOS / Linux)# Recommended curl -fsSL https://ntn.dev | bash # Or via npm (needs Node 22+, npm 10+) npm install --global ntn ntn --version # verify
Skip
— use the integration token instead. This works headlessly, no browser needed:ntn login
export NOTION_API_TOKEN=$NOTION_API_KEY # ntn reads NOTION_API_TOKEN export NOTION_KEYRING=0 # don't try to use the OS keychain
Add those exports to your shell profile (or to
~/.hermes/.env) so every session inherits them.
if command -v ntn >/dev/null 2>&1; then # use ntn else # fall back to curl fi
Windows users: skip step 2 entirely until native
ntn ships — Path B works fine. If you want CLI ergonomics now, install ntn inside WSL2.
Notion-Version: 2025-09-03 is required on all HTTP requests. ntn handles this for you. In this version, what users call "databases" are called data sources in the API.
ntn CLI (preferred, macOS / Linux)ntn api v1/users # GET ntn api v1/pages parent[page_id]=abc123 \ # POST with inline body properties[title][0][text][content]="Notes" ntn api v1/pages/abc123 -X PATCH archived:=true # PATCH; := is non-string (bool/num/null)
Syntax notes:
key=value — string fieldskey[nested]=value — nested object fieldskey:=value — typed assignment (booleans, numbers, null, arrays)ntn api v1/search query="page title"
ntn api v1/pages/{page_id}
ntn api v1/pages/{page_id}/markdown
ntn api v1/blocks/{page_id}/children
ntn api v1/pages \ parent[page_id]=xxx \ properties[title][0][text][content]="Notes from meeting" \ markdown="# Agenda - Q3 roadmap - Hiring"
ntn api v1/pages/{page_id}/markdown -X PATCH \ markdown="## Update Shipped the prototype."
ntn api v1/data_sources/{data_source_id}/query -X POST \ filter[property]=Status filter[select][equals]=Active
For complex queries with
sorts, multiple filter clauses, or compound logic, pipe JSON in:
echo '{"filter": {"property": "Status", "select": {"equals": "Active"}}, "sorts": [{"property": "Date", "direction": "descending"}]}' | \ ntn api v1/data_sources/{data_source_id}/query -X POST --json -
ntn files create < photo.png ntn files create --external-url https://example.com/photo.png ntn files list
Compare to the 3-step HTTP flow (create upload → PUT bytes → reference).
| Var | Effect |
|---|---|
| Auth token (overrides keychain) — set this to your integration token |
| File-based creds at instead of OS keychain |
| Skip the workspace picker prompt |
All requests share this pattern:
curl -s -X GET "https://api.notion.com/v1/..." \ -H "Authorization: Bearer $NOTION_API_KEY" \ -H "Notion-Version: 2025-09-03" \ -H "Content-Type: application/json"
On Windows the
curl shipped with Windows 10+ works as-is. PowerShell users can also use Invoke-RestMethod.
curl -s -X POST "https://api.notion.com/v1/search" \ -H "Authorization: Bearer $NOTION_API_KEY" \ -H "Notion-Version: 2025-09-03" \ -H "Content-Type: application/json" \ -d '{"query": "page title"}'
curl -s "https://api.notion.com/v1/pages/{page_id}" \ -H "Authorization: Bearer $NOTION_API_KEY" \ -H "Notion-Version: 2025-09-03"
Easier to feed to a model than block JSON.
curl -s "https://api.notion.com/v1/pages/{page_id}/markdown" \ -H "Authorization: Bearer $NOTION_API_KEY" \ -H "Notion-Version: 2025-09-03"
curl -s "https://api.notion.com/v1/blocks/{page_id}/children" \ -H "Authorization: Bearer $NOTION_API_KEY" \ -H "Notion-Version: 2025-09-03"
POST /v1/pages accepts a markdown body param.
curl -s -X POST "https://api.notion.com/v1/pages" \ -H "Authorization: Bearer $NOTION_API_KEY" \ -H "Notion-Version: 2025-09-03" \ -H "Content-Type: application/json" \ -d '{ "parent": {"page_id": "xxx"}, "properties": {"title": [{"text": {"content": "Notes from meeting"}}]}, "markdown": "# Agenda\n\n- Q3 roadmap\n- Hiring\n\n## Decisions\n- Ship MVP Friday" }'
curl -s -X PATCH "https://api.notion.com/v1/pages/{page_id}/markdown" \ -H "Authorization: Bearer $NOTION_API_KEY" \ -H "Notion-Version: 2025-09-03" \ -H "Content-Type: application/json" \ -d '{"markdown": "## Update\n\nShipped the prototype."}'
curl -s -X POST "https://api.notion.com/v1/pages" \ -H "Authorization: Bearer $NOTION_API_KEY" \ -H "Notion-Version: 2025-09-03" \ -H "Content-Type: application/json" \ -d '{ "parent": {"database_id": "xxx"}, "properties": { "Name": {"title": [{"text": {"content": "New Item"}}]}, "Status": {"select": {"name": "Todo"}} } }'
curl -s -X POST "https://api.notion.com/v1/data_sources/{data_source_id}/query" \ -H "Authorization: Bearer $NOTION_API_KEY" \ -H "Notion-Version: 2025-09-03" \ -H "Content-Type: application/json" \ -d '{ "filter": {"property": "Status", "select": {"equals": "Active"}}, "sorts": [{"property": "Date", "direction": "descending"}] }'
curl -s -X POST "https://api.notion.com/v1/data_sources" \ -H "Authorization: Bearer $NOTION_API_KEY" \ -H "Notion-Version: 2025-09-03" \ -H "Content-Type: application/json" \ -d '{ "parent": {"page_id": "xxx"}, "title": [{"text": {"content": "My Database"}}], "properties": { "Name": {"title": {}}, "Status": {"select": {"options": [{"name": "Todo"}, {"name": "Done"}]}}, "Date": {"date": {}} } }'
curl -s -X PATCH "https://api.notion.com/v1/pages/{page_id}" \ -H "Authorization: Bearer $NOTION_API_KEY" \ -H "Notion-Version: 2025-09-03" \ -H "Content-Type: application/json" \ -d '{"properties": {"Status": {"select": {"name": "Done"}}}}'
curl -s -X PATCH "https://api.notion.com/v1/blocks/{page_id}/children" \ -H "Authorization: Bearer $NOTION_API_KEY" \ -H "Notion-Version: 2025-09-03" \ -H "Content-Type: application/json" \ -d '{ "children": [ {"object": "block", "type": "paragraph", "paragraph": {"rich_text": [{"text": {"content": "Hello from Hermes!"}}]}} ] }'
# 1. Create upload curl -s -X POST "https://api.notion.com/v1/file_uploads" \ -H "Authorization: Bearer $NOTION_API_KEY" \ -H "Notion-Version: 2025-09-03" \ -H "Content-Type: application/json" \ -d '{"filename": "photo.png", "content_type": "image/png"}' # 2. PUT bytes to the upload_url returned above curl -s -X PUT "{upload_url}" --data-binary @photo.png # 3. Reference {file_upload_id} in a page/block payload
Common property formats for database items:
{"title": [{"text": {"content": "..."}}]}{"rich_text": [{"text": {"content": "..."}}]}{"select": {"name": "Option"}}{"multi_select": [{"name": "A"}, {"name": "B"}]}{"date": {"start": "2026-01-15", "end": "2026-01-16"}}{"checkbox": true}{"number": 42}{"url": "https://..."}{"email": "user@example.com"}{"relation": [{"id": "page_id"}]}/data_sources/ endpoints for queries and retrieval.database_id and data_source_id.
database_id when creating pages: parent: {"database_id": "..."}data_source_id when querying: POST /v1/data_sources/{id}/query"object": "data_source" with the data_source_id field.ntn)Workers are TypeScript programs Notion hosts for you. One worker can expose any combination of:
Plan / platform gating:
ntn is macOS/Linux only as of May 2026. Windows users need WSL2 or to wait for native support.ntn workers new my-worker # scaffold cd my-worker # Edit src/index.ts ntn workers deploy --name my-worker
src/index.ts:
import { Worker } from "@notionhq/workers"; const worker = new Worker(); export default worker; worker.tool("greet", { title: "Greet a User", description: "Returns a friendly greeting", inputSchema: { type: "object", properties: { name: { type: "string" } }, required: ["name"] }, execute: async ({ name }) => `Hello, ${name}!`, });
worker.webhook("onGithubPush", { title: "GitHub Push Handler", execute: async (events, { notion }) => { for (const event of events) { // event.body, event.rawBody (for signature verification), event.headers console.log("got delivery", event.deliveryId); } }, });
After deploy:
ntn workers webhooks list shows the URL Notion generates. Treat that URL as a secret — anyone with it can POST events unless you add signature verification.
ntn workers deploy ntn workers list ntn workers exec <capability-key> -d '{"name": "world"}' ntn workers sync trigger <key> # run a sync now ntn workers sync pause <key> ntn workers env set GITHUB_WEBHOOK_SECRET=... ntn workers runs list # recent invocations ntn workers runs logs <run-id> ntn workers webhooks list
When asked to build a Worker, scaffold with
ntn workers new, write the code in src/index.ts, set any secrets with ntn workers env set, and deploy. Notion's docs at https://developers.notion.com/workers cover the full API surface.
/markdown endpoints)Standard CommonMark plus XML-like tags for Notion-specific blocks. Use tabs for indentation.
Blocks beyond CommonMark:
<callout icon="🎯" color="blue_bg"> Ship the MVP by **Friday**. </callout> <details color="gray"> <summary>Toggle title</summary> Children indented one tab </details> <columns> <column>Left side</column> <column>Right side</column> </columns> <table_of_contents color="gray"/>
Inline:
<mention-user url="..."/>, <mention-page url="...">Title</mention-page>, <mention-date start="2026-05-15"/><span underline="true">text</span><span color="blue">text</span> or block-level {color="blue"} on the first line$x^2$, block $$ ... $$[^https://example.com]Colors:
gray brown orange yellow green blue purple pink red, plus *_bg variants for backgrounds.
Headings 5/6 collapse to H4. Multiple
> lines render as separate quote blocks — use <br> inside a single > for multi-line quotes.
| Task | mac / Linux | Windows |
|---|---|---|
| Read/write pages, search, query databases | | curl |
| Read a page for an agent to summarize | | curl endpoint |
| Upload a file | | 3-step HTTP flow |
| One-off API exploration | | curl |
| Build a sync / webhook / agent tool hosted by Notion | | WSL2 + |
"is_inline": true when creating data sources to embed them in a page.-s to curl to suppress progress bars (cleaner agent output).jq when reading: ... | jq '.results[0].properties'.Notion MCP, ~91% more token-efficient on DB ops than the previous version) — wire it via Hermes' MCP support if you want streaming Notion access from inside a session, but the paths above are enough for most one-shot tasks.MIT
mkdir -p ~/.hermes/skills/productivity/notion && curl -o ~/.hermes/skills/productivity/notion/SKILL.md https://raw.githubusercontent.com/NousResearch/hermes-agent/main/skills/productivity/notion/SKILL.md4,600+ AI skills, agents & workflows. Install in 60 seconds. Part of the Torly.ai family.
© 2026 Torly.ai. All rights reserved.