notion-cli
Skill0 starsChanged 41 days agoMITView on GitHub
Notion operations via the official `ntn` CLI - query data sources, create/update pages, read/write page content as Markdown, search, and filter. Use when any agent needs to interact with Notion programmatically. Covers authentication, CLI commands, property formats, filter syntax, pagination, and rate limits. For the user's specific workspace structure (DB IDs, relations, schemas), consult the user's current data catalog.
- Pipes a download into a shell
- Installs packages
- Sends data out
8 other projects use a version of this.
Adopted by
Most recently taken up by
- taixuann/development-spacetheir own version, first seen Nov 8, 2026
- pytholic/claude-skillstheir own version, first seen Nov 1, 2026
- alokn/claude-skillstheir own version, first seen Oct 11, 2026
- makenotion/skillstheir own version, first seen Sep 22, 2026
- hideki5123/agent-skill-settheir own version, first seen Jul 9, 2026
- magnusrodseth/dotfilestheir own version, first seen May 26, 2026
Used with
Projects with it also use playwright-cli.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
MicrosoftSkillplaywright-cli
Automate browser interactions, test web pages and work with Playwright tests.
Also in 4 projects that use this
Other versions8 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 8
- makenotion/skills★ 170same name
- magnusrodseth/dotfiles★ 3same name
- pytholic/claude-skills★ 3same name
- hideki5123/agent-skill-set★ 2same name
- taixuann/development-space★ 1same name
- ZenoxZX/notion-cli★ 1same name
- alokn/claude-skills★ 0same name
- CaesiumY/notion-cli-skill★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository34 other files it gives its agents.
- afkSkill
- apple-contactsSkill
- apple-devSkill
- apple-notesSkill
- apple-photosSkill
- bug-huntSkill
- catch-upSkill
- cherriSkill
- chrome-controlSkill
- claude-sessionsSkill
- coordinatorSkill
- discountsSkill
- forge-skillSkill
- groupmeSkill
- herdr-crewSkill
- iconsSkill
- imsgSkill
- language-selectionSkill
- laterSkill
- mac-controlSkill
- nix-mechanicsSkill
- posthogSkill
- quotaSkill
- redditSkill
- rustSkill
- scoutSkill
- semverSkill
- social-contentSkill
- spotifySkill
- travelSkill
- validate-ideaSkill
- verdictSkill
- web-reconSkill
- whatsappSkill
The file
We split this skill into the rules it gives an agent
7 rules. 0 are common, 2 are distinctive, and 5 are very specific to this project.
0
Common
Most of the 8 edited versions we read keep these.
2
Distinctive
This author's own choices. Worth a look.
5
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 36
There is deliberately NO local api-docs mirror: When this skill's references don't cover an endpoint or parameter, ntn api <path> --docs IS …
- Very specific: line 56
Pagination: default --limit 25 (max 100).
- Very specific: line 227
Trash: use in_trash, not archived
- Very specific: line 228
Empty strings: use null instead of "" to unset values
- Very specific: line 243
Add -v to any ntn command for full error chains.
- Distinctive: line 259
Run these steps with the user when the Last updated date at the top is more than 1 month old (or on request):
- Distinctive: line 265
Keep workspace-specific routing in the user's private data catalog.
“Common” is measured: kept by at least 6 in 10 of the 8 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Notion CLI / API Skill
Last updated: 2026-08-15. If today is more than 1 month past this date, this skill may be stale. Before proceeding, tell the user and offer to run the Self-Update steps. If they decline, continue normally and don't ask again this session.
Authentication
Auth is a Notion internal-integration secret, stored wherever your setup keeps secrets (e.g. a 1Password item). The ntn CLI takes it via NOTION_API_TOKEN (overrides any keychain login):
export NOTION_API_TOKEN=$(op read "op://<vault-id>/<item-id>/credential")Export once per shell, not per command (secret-manager rate limits). Keep concrete credential references in private authentication instructions.
- The integration only sees pages connected to it in Notion. A
403/object-not-found on a page the user clearly has means the connection is missing - the user adds it via the page's ••• menu → Connections. - Alternative: a personal access token from https://www.notion.so/developers/tokens also works in
NOTION_API_TOKEN(acts as the user's own user, no connections needed, max 1yr expiry) - not currently used.
The CLI: ntn
Official Notion CLI, installed via Homebrew on the user's Macs (brew install --cask notion-cli; upgrade: brew upgrade --cask notion-cli; elsewhere: curl -fsSL https://ntn.dev | bash). It is self-documenting - consult it before guessing syntax:
ntn <command> --help # help for any command
ntn api ls # enumerate all public API endpoints
ntn api <path> --docs # full official docs for an endpoint
ntn api <path> --spec # reduced OpenAPI fragmentBeyond the commands used below, the CLI also has whoami / auth (inspect credentials), files (file uploads, beta), doctor, and update.
There is deliberately NO local api-docs mirror. When this skill's
references don't cover an endpoint or parameter, ntn api <path> --docs IS
the documentation - live, current, and complete (e.g.
ntn api v1/data_sources/{data_source_id}/query --docs). Find the path with
ntn api ls, then pull its docs; never go hunting for cached copies.
Core Operations
1. Query a Data Source (Most Common)
ntn datasources query $DATA_SOURCE_ID \
--filter '{"property":"Status","status":{"equals":"Done"}}' \
-s "Created time desc" \
--limit 100 --json--filtertakes raw Notion filter JSON verbatim (--filter-file -for stdin)-s "<property> [asc|desc]", repeatable, order matters - takes exact property names only (case-sensitive). Timestamp sorts/filters (created_time/last_edited_time) aren't properties: usentn api v1/data_sources/$ID/querywith{"sorts": [{"timestamp": "last_edited_time", "direction": "descending"}]}/{"filter": {"timestamp": "created_time", ...}}- Requires a data source ID, not a database ID.
ntn datasources resolve <database-id>lists a database's data sources. - Pagination: default
--limit 25(max 100); when output reports more results, re-run with--start-cursor <cursor>until exhausted:
CURSOR=""
while :; do
RESPONSE=$(ntn datasources query $DATA_SOURCE_ID --limit 100 --json \
${CURSOR:+--start-cursor "$CURSOR"})
echo "$RESPONSE" | jq '.results[]'
[ "$(echo "$RESPONSE" | jq -r '.has_more')" = "true" ] || break
CURSOR=$(echo "$RESPONSE" | jq -r '.next_cursor')
done2. Read Page Content - as Markdown
ntn pages get $PAGE_ID # Markdown, properties as frontmatter
ntn pages get $PAGE_ID --json # raw; `markdown` is a TOP-LEVEL fieldProperties only, without the body: ntn api v1/pages/$PAGE_ID.
One call returns the whole page - no block pagination, no recursive children. If the response has "truncated": true, pass each id in unknown_block_ids back to the same endpoint to fetch the remaining subtrees; a 404 on one means it's permission-restricted, not truncated - skip it.
3. Create a Page
ntn pages create --parent data-source:$DATA_SOURCE_ID --content '# Title
Body markdown here.'--parent accepts page:<id>, database:<id>, or data-source:<id>; content via --content, stdin, or $EDITOR. To set properties beyond the title, use the API passthrough:
ntn api v1/pages -d '{
"parent": {"type": "data_source_id", "data_source_id": "'$DATA_SOURCE_ID'"},
"properties": {
"Title": {"title": [{"text": {"content": "New Task"}}]},
"Status": {"status": {"name": "In Progress"}},
"Due Date": {"date": {"start": "2026-07-12"}}
},
"markdown": "# Heading\n\nBody content."
}'(markdown is mutually exclusive with children.)
4. Edit Page Content
ntn pages edit $PAGE_ID --content '# Updated body' # full rewrite (also: stdin)Targeted search-and-replace via the markdown endpoint (array key is content_updates, NOT operations):
ntn api v1/pages/$PAGE_ID/markdown -X PATCH -d '{
"type": "update_content",
"update_content": {"content_updates": [{"old_str": "find this", "new_str": "replace with this"}]}
}'Full rewrite via raw HTTP (the reliable path for anything over a couple of KB - see gotcha 8). Note the payload key is new_str, NOT markdown:
curl -s -X PATCH "https://api.notion.com/v1/pages/$PAGE_ID/markdown" \
-H "Authorization: Bearer $NOTION_API_TOKEN" -H "Notion-Version: 2026-03-11" \
-H "Content-Type: application/json" \
-d '{"type": "replace_content", "replace_content": {"new_str": "# New body\n\n..."}}'To append, set old_str to a unique existing anchor (e.g. a heading) and include that anchor in new_str. Markdown tables render as native Notion tables. Avoid the block API (/v1/blocks/{id}/children) except for block types markdown can't express.
5. Update Page Properties
ntn api v1/pages/$PAGE_ID -X PATCH -d '{
"properties": {"Status": {"status": {"name": "Complete"}}}
}'6. Retrieve Data Source Schema
ntn api v1/data_sources/$DATA_SOURCE_IDPerformance tip: filter_properties[] returns only the properties you need - POST /v1/data_sources/{id}/query?filter_properties[]=title&filter_properties[]=status. Also works on the page create/update endpoints (trims the response, not the write).
7. Search
ntn api v1/search -d '{"query": "meeting notes", "filter": {"property": "object", "value": "page"}, "page_size": 20}'ntn api syntax notes
Method is inferred (GET default; POST when a body is present; -X wins). Besides -d '<json>' / -d @file.json, it supports httpie-style inline inputs: page_size==100 (query param), properties[foo]=bar (body string), archived:=true (typed JSON).
API Version Notes (2026-03-11)
ntn sends a current Notion-Version automatically (--notion-version to override). Still relevant when composing ntn api calls:
- Database query/schema endpoints live at
/v1/data_sources/- the old/v1/databases/*/queryendpoint is DEAD (returns 400). Usedata_source_ideverywhere. archivedwas replaced byin_trashin all requests/responses.- Block append
afterparam was replaced by apositionobject (after_block,start,end). - The
transcriptionblock type was renamed tomeeting_notes. - Page content should be read/written via the markdown endpoints (
ntn pages get/edit), not the block API.
Property Types Quick Reference
{
"Title": {"title": [{"text": {"content": "Page title"}}]},
"Text": {"rich_text": [{"text": {"content": "Rich text content"}}]},
"Status": {"status": {"name": "In Progress"}},
"Select": {"select": {"name": "Option A"}},
"Multi-select": {"multi_select": [{"name": "Tag1"}, {"name": "Tag2"}]},
"Date": {"date": {"start": "2026-03-25", "end": "2026-03-26"}},
"URL": {"url": "https://example.com"},
"Number": {"number": 42},
"Checkbox": {"checkbox": true},
"Relation": {"relation": [{"id": "related-page-id"}]}
}Filter Syntax
Filters passed to --filter / ntn api bodies are standard Notion filter JSON:
{"property": "Status", "status": {"equals": "Done"}}Compound:
{
"and": [
{"property": "Status", "status": {"equals": "Done"}},
{"property": "Priority", "select": {"equals": "High"}}
]
}For anything long or nested, pipe it in with --filter-file - instead of fighting shell quoting:
ntn datasources query $DATA_SOURCE_ID --filter-file - <<'EOF'
{
"and": [
{"or": [
{"property": "Title", "title": {"contains": "API"}},
{"property": "Description", "rich_text": {"contains": "API"}}
]},
{"property": "Status", "status": {"does_not_equal": "Archived"}},
{"timestamp": "created_time", "created_time": {"on_or_after": "2025-01-01"}}
]
}
EOFFull operator list: ntn api v1/data_sources/{data_source_id}/query --docs.
Important Gotchas
- Data source ID ≠ database ID -
ntn datasources resolve <database-id>when unsure - Pagination: exhaust all pages via
--start-cursor/start_cursor - Rate limits: 3 requests/second average (burst higher)
- Rich text: limited to 2000 characters per rich_text array
- Trash: use
in_trash, notarchived - Empty strings: use
nullinstead of""to unset values - The integration only sees connected pages - missing pages usually mean a missing connection, not a bad query
- The CLI hangs on markdown bodies - creates AND edits.
ntn api v1/pageshangs on creates with amarkdownbody (observed 2026-08-11: >90s twice on ~3.5KB), andntn pages edit --contenthangs on full rewrites (observed 2026-09-01: >10min on ~15KB, never returned). The same payloads over raw HTTP finish in seconds. Send any markdown write beyond a couple of KB straight to the No-CLI curl form - and re-read the page before retrying, since a hung call may still have applied and a create would otherwise duplicate. The full-body rewrite endpoint isPATCH /v1/pages/{id}/markdownwith{"type":"replace_content","replace_content":{"new_str":"<markdown>"}}(othertypes:insert_content,update_content,replace_content_range); returnsobject: page_markdown. ntnspawned from a script can block on stdin. Asubprocess.run(["ntn", "api", ..., "-d", body])(Python, or any child that inherits an open non-TTY stdin) can sit forever before sending the request (observed 2026-09-28: a one-field PATCH hung >9min; the same call returned in 1.5s with stdin closed). Always passstdin=subprocess.DEVNULL(shell:</dev/null) when callingntnfrom code.- ⚠️ DATA-LOSS: schema PATCHes on select/multi_select - updating a data-source property (even just its
description) requires including the type key, and sending it EMPTY ("multi_select": {}/"select": {}) REPLACES the option list with nothing, wiping every option and stripping that property's values from ALL pages in the database (happened 2026-07-19; recovery required rewriting 133 pages, original option colors unrecoverable). Before any data-source PATCH touching a select/multi_select property, GET the current schema and resend the existingoptionsarray verbatim inside the type key.statusproperties are protected (empty"status": {}leaves options intact), and empty type keys are harmless for scalar types (rich_text,url,number,date).
Error Handling
400: bad request (check JSON - or a dead pre-2026 endpoint)401: unauthorized (bad/revoked token - see Authentication)403: forbidden (page not connected to the integration)404: not found429: rate limited (slow down)500: server error (retry with exponential backoff)
Add -v to any ntn command for full error chains; ntn doctor checks the setup.
No-CLI Fallback (deployed environments)
Where ntn isn't installable (Modal, CI, Workers), hit the API directly - same bodies as above:
curl -s -X POST "https://api.notion.com/v1/data_sources/$DATA_SOURCE_ID/query" \
-H "Authorization: Bearer $NOTION_API_TOKEN" \
-H "Notion-Version: 2026-03-11" \
-H "Content-Type: application/json" \
-d '{"page_size": 100}'Self-Update
Run these steps with the user when the Last updated date at the top is more than 1 month old (or on request):
- Update the CLI:
ntn update, then skimntn --helpfor new/changed commands and reconcile this file. - Read the changelog at https://developers.notion.com/page/changelog for changes since the Last updated date. Apply any endpoint/param/behavior changes to this SKILL.md.
- Verify auth still works:
ntn api v1/users/mewithNOTION_API_TOKENexported - if it fails, fix the 1Password reference in the Authentication section. - Bump the Last updated date at the top of this file to today.
- Keep workspace-specific routing in the user's private data catalog.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

