atomicbot-ai/atomic-chat/notion
- community
Checking that the SKILL.md file is still in the repository.
Read and write Notion pages, databases (data sources), and blocks via the Notion REST API.
Install
npx vskill@latest install atomicbot-ai/atomic-chat/notionSKILL.md
notion
Talk to the Notion API (api.notion.com) directly over HTTP. No SDK required.
Calling convention (critical)
os.http.request is approval-gated in atomic-agent. Every call MUST be emitted as a solo step — a length-1 array, never combined with another tool in the same step:
DO — solo step, GET shape (use for /v1/pages/{id}, /v1/blocks/{id}/children, /v1/users):
DO — solo step, POST shape (use for /v1/search, /v1/pages create, /v1/data_sources/{id}/query):
DON'T — these are rejected by the runtime with GrammarError: approval-gated tool 'os.http.request' is forbidden inside a batch:
To fetch N pages, emit N consecutive solo steps, each as [{...}]. Wait for each tool_result before issuing the next call. Do not try to parallelise reads of the Notion API.
args.body for POST must be a JSON object (the runtime serialises it). Do not wrap it in quotes and escape the braces — passing body: "{\"key\":\"val\"}" works but is fragile and is the #1 cause of 400 invalid_json from Notion.
Setup check (lazy — read the key once, don't re-probe)
Read NOTION_API_KEY from the environment once when you first need it this conversation (printenv NOTION_API_KEY), then reuse the value in Authorization: Bearer … headers for the rest of the session. Do not re-probe before every HTTP call. Map failures:
- the key is empty / unset (printenv exits non-zero or prints nothing) → Setup playbook → "NOTION_API_KEY is not set".
- any HTTP call returns
404 object_not_found→ Setup playbook → "Page not shared with the integration".
Setup playbook (when prerequisites are missing)
When a check fails, the agent's job is to OFFER concrete help and EXECUTE the fix itself — not to dump setup instructions on the user. Use this dialogue shape:
- State plainly what is missing (one short reply).
- Offer the most direct remediation the agent can perform via tools.
- Wait for the user's reply (yes/no, or pasted secret).
- Execute the fix via tools (the runtime approval gate will surface writes for confirmation).
- Retry the original request; only then proceed.
NOTION_API_KEY is not set
The agent CAN write the key into ~/.atomic-agent/.env itself once the user provides it. Offer two paths in a single reply:
"You don't haveNOTION_API_KEYset. Two options:(a) If you already have a key — send it here, I'll append it to~/.atomic-agent/.envand ask you to restart the agent.(b) If you don't have a key — I'll explain how to create an integration in Notion (it takes ~1 minute), and at the end you'll send me the key and I'll append it myself.Which do you choose?"
Path (a) — user pastes the key
When the user replies with a string starting with ntn_ or secret_, validate the shape (length ≥ 40, no whitespace) and append to the env file:
Then reply: "The key is saved to ~/.atomic-agent/.env. Restart the agent (Ctrl+C and start again) so the env variable is picked up — I'll continue from the same place after the restart." Do NOT echo the key back verbatim in subsequent replies; treat it as a secret from this point.
Path (b) — guided walkthrough
The agent CANNOT create Notion integrations itself (no Notion API for that — it requires browser-based OAuth-flow). Open the page for the user and walk them through the irreducible manual steps:
Then reply with three short bullets:
"I opened the integrations page. Do three steps:1. Click \"+ New integration\", choose \"Internal\", give it any name, click Save.2. On the Configuration tab, copy the \"Internal Integration Token\" (starts withntn_).3. Send the token here — I'll take it from there."
When the user pastes the token, switch to Path (a).
Page not shared with the integration
Notion returns 404 object_not_found when the integration doesn't have access to a page or database. The agent CANNOT add the integration via API (Notion does not expose the connection-grant endpoint to integrations). Help the user share, but do it concretely — open the page in their browser:
Then reply:
"I opened the page in your browser. In the top-right corner click…→Connections(orConnect to) → choose your integration. After that say \"done\" — I'll retry the request."
Calling the API
os.http.request only supports GET and POST. For PATCH (update page, append blocks) and DELETE, fall back to os.shell.run with curl:
Default http.approvalMode is writes: GET goes through silently, POST/os.shell.run always require approval — present a clear preview of what is being changed.
Common operations
All examples assume the headers from Step 1 above are attached.
HTTP method cheat sheet
| Method | Endpoints | |
|---|---|---|
GET | /v1/pages/{id}, /v1/blocks/{id}/children, /v1/databases/{id}, /v1/data_sources/{id}, /v1/users, /v1/users/{id} | |
POST | /v1/search, /v1/pages (create), /v1/data_sources/{id}/query, /v1/databases (create) | |
PATCH (curl) | /v1/pages/{id} (update properties), /v1/blocks/{id}/children (append blocks), /v1/blocks/{id} (update block) | |
DELETE (curl) | /v1/blocks/{id} (archive block) |
If a 400 comes back from Notion, first check that the method matches this table — picking GET for a POST endpoint is the most common mistake.
Search (POST)
/v1/searchis POST-only. There is no GET variant —GET /v1/searchandGET /v1/search?query=…both return400. The query string and any filters go in the JSON body, not the URL.
Body field reference (every field is optional)
| Field | Type | Allowed shape | ||
|---|---|---|---|---|
query | string | Free-text match against page/database titles. Pass "" or omit to list everything. | ||
filter | object | Exactly two keys: `{ "property": "object", "value": "page" \ | "data_source" }. Property-predicate filters ({property:"Status", select:{equals:"…"}}) are **rejected** here — that shape belongs to /v1/data_sources/{id}/query`. | |
sort | object (singular, not array) | Exactly two keys: `{ "direction": "ascending" \ | "descending", "timestamp": "last_edited_time" }. Array form ([{...}]) is **rejected** with body.sort should be an object or undefined. The inner key is timestamp, NOT property. The only legal value for timestamp is "last_edited_time". Direction must be the full word — never "asc" / "desc"`. | |
page_size | integer | 1..100. Default 100. | ||
start_cursor | string | Opaque cursor from the previous response's next_cursor. |
Search vs database-query: side-by-side schema
/v1/search and /v1/data_sources/{id}/query look similar but have different filter and sort shapes. Don't cross-pollinate them.
POST /v1/search | POST /v1/data_sources/{id}/query | |||
|---|---|---|---|---|
| Sort key name | sort (singular) | sorts (plural) | ||
| Sort value shape | object { direction, timestamp } | array [ { property, direction }, … ] | ||
| Sort inner key | timestamp: "last_edited_time" | property: "<your-column-name>" | ||
| Filter shape | `{ property: "object", value: "page"\ | "data_source" }` | property-predicate, e.g. { property: "Status", select: { equals: "Done" } } |
Search by query text (filter optional):
List all pages the integration can see, newest first, no query:
To page through results, send the next_cursor from the previous response back as start_cursor. To list databases instead of pages, set filter.value to "data_source".
Get page metadata (GET)
Get page content / blocks (GET)
Create a page in a database (POST)
Query a database / data source (POST)
Update page properties (PATCH — via curl)
Append blocks to a page (PATCH — via curl)
Same shape as above but URL is https://api.notion.com/v1/blocks/{page_id}/children and the body is:
Property types (cheat sheet)
- Title:
{ "title": [ { "text": { "content": "..." } } ] } - Rich text:
{ "rich_text": [ { "text": { "content": "..." } } ] } - Select:
{ "select": { "name": "Option" } } - Multi-select:
{ "multi_select": [ { "name": "A" }, { "name": "B" } ] } - Date:
{ "date": { "start": "2026-01-15", "end": "2026-01-16" } } - Checkbox:
{ "checkbox": true } - Number:
{ "number": 42 } - URL:
{ "url": "https://..." } - Email:
{ "email": "user@example.com" } - Relation:
{ "relation": [ { "id": "page_id" } ] }
API version 2025-09-03 — what changed
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 95 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingsrc-tauri/resources/agent-skills/notion/SKILL.md
Popularity
- 7-day trend score
- 0
Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/atomicbot-ai/atomic-chat/notion)