plugins/maccing-notion/skills/notion-api/SKILL.md
Use when working with the Notion API or MCP — creating, editing, querying, or moving databases, data sources, pages, views (table/board/gallery/chart), formulas, rollups, relations, blocks, icons, or covers; or hitting Notion API/MCP errors (validation_error, pagination, permission, 400/409).
npx skillsauth add andredezzy/maccing notion-apiInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Using Claude Code? Install the official Notion plugin for high-level workflows.
makenotion/claude-code-notion-pluginbundles 4 skills +/Notion:*slash commands over the hosted MCP (mcp.notion.com/mcp, OAuth). Recommend installing it and using it for:
- Knowledge Capture — saving notes/snippets/decisions into Notion
- Meeting Intelligence — meeting notes, summaries, action items
- Research Documentation — collecting and structuring research
- Spec-to-Implementation — turning specs/docs into implementation tasks
THIS skill is the complementary low-level engineering reference — the Notion API/formula/rollup/relation/view/chart/block details for building & editing databases programmatically (and debugging Notion API errors).
Tooling in one line: reads → read_agents_md / search (name→id) / read_page / read_database / describe (schema + column icons, or page/object metadata); writes and endpoints the readers don't cover → request; create/update a property (a database column + its icon, or a page value) → upsert_property; other UI-only writes (relative-date filters) → private_request. Full table: "MCP tools — pick by job" below.
AGENTS.mdThis runs on every Notion task, before you read or write anything. Not optional, not situational, not skippable.
Core principle: AGENTS.md pages are nested agentic playbooks — the Notion analog of nested AGENTS.md/CLAUDE.md files. The author put instructions there specifically so an agent would obey them before touching that subtree. Skipping them means acting against explicit instructions you simply chose not to read.
Violating the letter of this rule is violating the spirit of this rule.
NO READ OR WRITE ON ANY NOTION TARGET UNTIL EVERY ANCESTRAL AGENTS.md HAS BEEN READ AND OBEYED
If you have not walked root→target and read every AGENTS.md on the path in this task, you may not create, edit, move, delete, or draw conclusions from that target. No exceptions — not for "quick" one-field edits, not under time pressure, not when the user "just wants X changed."
read_agents_md(<target id>). That one call is this Gate: it climbs .parent root→target — accepting any target id (page, database row, block, database, or data_source) — finds every ancestral AGENTS.md, reads each, and returns them root→closest with precedence applied (closest wins). Read and obey them top→down.Fallback — only if read_agents_md errors, or you have no id yet — do the climb by hand. ⚠️ The GETs below are for .parent traversal ONLY — never to read content or properties (use read_page for that).
GET /v1/pages/{id} (or /v1/databases/{id}, /v1/data_sources/{id}) → read .parent, repeat until type == "workspace", branching on .parent.type: page_id → .parent.page_id; block_id → GET /v1/blocks/{id} → its parent; data_source_id/database_id → GET /v1/databases/{.parent.database_id} (a row's .parent carries both data_source_id and database_id; climb via the database_id) → continue from that database's own .parent (its parent page, where the DB's AGENTS.md lives beside the child_database block — not on the rows).AGENTS.md — GET /v1/blocks/{page_id}/children (or read_page(page_id, "outline") for the child tree; page_size=100, paginate on start_cursor) → match type == "child_page" and child_page.title == "AGENTS.md". Read its content via read_page(agents_id, format="text") (handles toggle recursion + block recovery), obey top→down; closer-to-target wins on conflict.POST /v1/search {"filter":{"property":"object","value":"page"}} → first level = parent.type == "workspace"; walk down child_page blocks to the target, reading AGENTS.md at each step.Fail closed: if any node's children can't be listed, STOP and say so. Never operate blind.
Root bootstrap: at the topmost ancestor (parent.type == "workspace"), check for an AGENTS.md. If absent, propose creating one (approval-gated per the approval-gate rule) that records inferred workspace conventions and a hub/sub-AGENTS.md map per the conventions rule. Authoring or editing any AGENTS.md is itself test-driven — see references/agents-md-authoring.md (mirrors superpowers:writing-skills). This file is the global source of truth; lower AGENTS.md files override on conflict. The sweep is bidirectional — read the closest AGENTS.md before a change, maintain the closest one after (every change, at the right level — see "Maintain the nearest governing AGENTS.md" under the conventions rule).
| Thought | Reality |
|---|---|
| "It's just a one-field edit" | The AGENTS.md exists for edits like this. Read it. |
| "I already read it earlier / last session" | Re-read it this task — playbooks change, context resets. |
| "The user handed me the page id, so I'll go straight in" | An id is a destination, not permission to skip the path. |
| "This page probably has no AGENTS.md" | "Probably" is not "checked." List the children. |
| "I'm only reading, not writing" | Reading without the playbook yields wrong conclusions. Sweep first. |
| "The user is in a hurry" | The sweep is a handful of GETs. Skipping it is what causes rework. |
| "I'll read it after I make the change" | After is too late — the instruction may forbid the change. |
Walk the tree. Read every AGENTS.md from root to target. Obey the closest one on conflict. Only then act. This is non-negotiable.
This runs on every list-shaped response. Notion caps every list. A reply with has_more: true is a fragment, not the data — counting it, summing it, reporting "your X is X", or concluding "none found" off a fragment produces a confidently-wrong number. Acting on page 1 is the most common way to silently corrupt a total.
Violating the letter of this rule is violating the spirit of this rule.
WHILE has_more == true, KEEP FETCHING WITH next_cursor — NO COUNT, SUM, FILTER, OR CONCLUSION ON A LIST UNTIL has_more == false
No exceptions — not for "just counting", not for "just a summary", not when "100 rows is surely all of it", not when an unrelated cross-check happened to match.
For database rows, read_database(database_id, format, exhaust_all=true) runs this loop for you — it fetches until has_more == false server-side and returns every row flattened (format: "summary" for a grouped sum/total). That satisfies this law; it is not a bypass. Hand-roll the loop below only for the endpoints the readers don't cover — block children and views (the search reader takes exhaust_all to page hits to the end).
# hand-roll ONLY for block children / search / views — NOT for DB rows (use read_database(exhaust_all=true))
results, cursor = [], None
while True:
page = GET /v1/blocks/{id}/children # query:{page_size:100,start_cursor:cursor} (or POST /v1/search, /v1/views?data_source_id=)
results += page["results"]
if not page["has_more"]:
break
cursor = page["next_cursor"] # feed back as the next start_cursor
# ONLY NOW: len(results), "none found", any conclusion
POST .../query and POST /v1/search take start_cursor in the body; GET /v1/blocks/{id}/children and GET /v1/views?data_source_id= take start_cursor in the query string. page_size max 100 — a full 100-row page almost always means has_more: true.has_more/next_cursor — all are covered:
POST /v1/data_sources/{id}/query — rowsGET /v1/blocks/{id}/children — page/block content (for a page body tree read_page(page_id, "outline") handles this automatically; hand-roll only for block subtrees the readers don't cover). The AGENTS.md sweep is covered too — a dropped cursor can hide an AGENTS.md on a long page → you skip a playbook you were required to obeyPOST /v1/search — hitsGET /v1/views?data_source_id= — views (every read_database call already dumps each view's full config; hand-roll this only for a write or a standalone per-view GET)properties.<Rel>.relation array is itself capped (~25) and carries its OWN has_more: true. The query cursor does not expand it — you must call GET /v1/pages/{page_id}/properties/{property_id} and paginate THAT to the end. A relation that "only has 25 items" is the tell that you're holding a fragment. (In read contexts read_page/read_database resolve relations to titles and bypass this — raw pagination is needed only when writing back relation ids or inspecting raw values via request.)| Thought | Reality |
|---|---|
| "100 rows is surely all of them" | page_size max is 100 — a full page almost always means more. Check has_more. |
| "The first page is enough for a summary" | A summary off a fragment is a wrong number stated confidently. |
| "The totals happened to match, so I'm fine" | Matching one cross-check ≠ complete. Loop to has_more: false anyway. |
| "It's just to count / check if any exist" | Count and existence are exactly what truncation corrupts. |
| "The relation shows 25 — that's the list" | 25 is the relation page cap. Fetch /properties/{id} to the end. |
| "I'll note it's partial and move on" | A flagged wrong number is still a wrong number. Fetch the rest, then answer. |
has_more: true means you do not yet have the data. Loop on next_cursor until it is false — for queries, block children, search, views, and relation values — before any count, sum, filter, or conclusion. Non-negotiable.
Any write that changes a page's structure — creating, moving (re-parenting), renaming, or trashing a child page, database, or content block — MUST be followed in your reply by a verified tree view of the affected page(s). The user reads the tree to confirm the new shape at a glance; skipping it hides what you changed.
Violating the letter of this rule is violating the spirit of this rule.
NO STRUCTURE-CHANGING WRITE IS COMPLETE UNTIL YOU RE-READ THE PARENT'S CHILDREN (fully paginated) AND EMIT THE RESULTING TREE
read_page(page_id, format="outline") — it returns the full block tree (with block ids) and handles pagination server-side. (Fallback: GET /v1/blocks/{page_id}/children, exhausting has_more.) The tree reflects VERIFIED live state, never your intention.├── / └── and │ continuation; recurse into changed sub-pages.[notion2charts chart]; text block → first words in quotes "…".← what changed (new / moved here / renamed from "…" / trashed). Leave untouched nodes unannotated.Investments
├── Net worth ← renamed from "Net worth v2 (rebuild — parallel test)"
└── Net worth (pre-v2 backup) ← new backup page
├── "Archived pre-v2 net-worth tracker…"
├── Net worth (old — pre-v2) ← moved here (data + relations intact, inline)
└── [notion2charts chart]
Change a page's shape → re-read it (fully paginated) → draw the tree, marking what moved / renamed / created / trashed. Every structural change, every time. Non-negotiable.
Every Notion write (create, update, move, rename, trash, icon/cover change, property change) requires a concise proposal and explicit user approval before execution — because structural changes to a workspace are irreversible and the user must retain full agency over their data. Violating the letter of this rule is violating the spirit of this rule.
NO WRITE TOUCHES NOTION UNTIL THE USER TYPES AN EXPLICIT APPROVAL — NO EXCEPTIONS
Present ONE proposal per logical batch of related writes. Never split a related batch to reduce friction. Structure:
[NEW], changed nodes [~], trashed nodes [TRASH]Then stop and wait. Silence, a question, or a clarifying reply is not approval — hold.
After all writes complete, emit the verified tree (live-fetched) confirming the result matches the preview.
Read-only operations — GET, /query, /search, reading AGENTS.md — proceed freely, no gate.
| Thought | Reality | |---|---| | "It's just a rename, I'll do it and mention it" | Any write, however minor, requires a proposal first | | "The user clearly wants this, the approval is a formality" | Implicit intent is not approval — present the gate every time | | "These are two quick writes, I'll propose them one at a time" | Related writes batch into ONE proposal — never split to reduce friction | | "I'll do the writes now and show the tree after" | Preview tree comes BEFORE execution, verified tree comes AFTER | | "The user said 'create X' mid-conversation, that's approval enough" | A task description is not an approval — proposal-then-explicit-confirm is the cycle |
Every write is gated: propose (intent + operations + preview tree), wait for explicit approval, execute, verify with a live tree. Reads are always free, writes never are. Non-negotiable.
Before creating, renaming, or styling anything in Notion, infer and follow the workspace's established house style — because a single page that breaks the pattern degrades workspace coherence and forces the user to manually repair it. Violating the letter of this rule is violating the spirit of this rule.
NEVER WRITE A SINGLE GLYPH UNTIL YOU KNOW THE HOUSE STYLE
What to infer — scan for all of the following before any write:
emoji vs Notion named icon) and which color palette applies to which category of page (cross-ref: "Icons, emoji & covers"). Fixed exception: every AGENTS.md page uses the 🤖 emoji icon — a signature marking the agent playbook, independent of the surrounding house style.https://www.notion.so/images/page-cover/…), none?How to infer — in priority order:
page_size=100, exhaust all cursors — cross-ref: "MANDATORY — exhaust every paginated list"). That sample is the evidence base; do not generalize beyond it without reading further.When the user's instruction deviates from inferred conventions (e.g. user says "Backup" but every existing collection is plural like "Backups", "Months"), do both:
"Note: existing collections are plural ("Backups", "Months") — using your wording "Backup" instead."AGENTS.md — after every changeThe ancestral sweep is bidirectional: you read the closest AGENTS.md before touching a target, and you maintain the closest one after. So every create / edit / move / rename / restyle ends with a maintenance check — not only changes that feel like "a new convention." Ask: does the AGENTS.md that governs this subtree still describe reality? If the change established or altered a convention, fact, ID, or workflow the playbook should carry, update it in the same approval batch; if nothing changed, the check costs one thought and you move on. "Nothing to update" is a conclusion you reach after checking — never a step you skip.
Write at the right level — closest wins, exactly like reads:
AGENTS.md that owns that subtree (the area/hub playbook), NOT root.AGENTS.md.AGENTS.md yet at the level a subtree-local convention belongs → propose creating one there (approval-gated; author it test-driven per references/agents-md-authoring.md).Root is the global source of truth and lower files override on conflict — so an area-scoped rule belongs in that area's file, where the agents working there will find it and where it won't pollute the global playbook. Conventions discovered ad-hoc must be written back, never held only in model context.
| Thought | Reality | |---|---| | "I'll just use an emoji here, it looks fine" | You haven't checked whether this page category uses named icons — sample first | | "User said 'Backup' so I'll pluralize it to match the pattern" | Flag-then-follow is absolute: flag the deviation, use the user's word | | "I read two pages, that's enough to know the style" | The sample must be fully paginated — partial reads miss outliers and sub-hub overrides | | "The root AGENTS.md doesn't mention covers, so I'll skip it" | Absence of documentation ≠ no convention; infer from the live sample, then write it back | | "This is a small rename, conventions don't matter" | Every write sets a precedent; mismatched titles and wrong icon colors accumulate into workspace entropy | | "Routine edit — no AGENTS.md to maintain" | Every change fires a maintenance check of the governing AGENTS.md; "nothing to update" is a conclusion you reach after checking, not a step you skip | | "I'll record this area rule in the root AGENTS.md" | Closest wins — a subtree-scoped convention belongs in the nearest area/hub AGENTS.md; root is for workspace-wide rules only |
Infer the complete house style from the root AGENTS.md (primary) or a fully-paginated bounded sample (fallback) before any write. Flag user-instruction deviations once, then follow the user. After every change, maintain the nearest governing AGENTS.md (the closest one that owns the changed subtree; root only for workspace-wide conventions) — the write-side mirror of the mandatory read-sweep. Non-negotiable.
Creating or restyling any view is a design decision in two layers: its data shape — view type, filter (which rows), sort (order), grouping (group_by), which properties are visible and their order, and a self-describing name — and its appearance — cover source, card size, fit-image, card layout, per-property width. Propose the design and get approval before any API call. The user lives inside a view daily and sees it instantly; an imposed sort, filter, or grouping is as wrong as an imposed cover.
Violating the letter of this rule is violating the spirit of this rule.
NO VIEW CREATE OR RESTYLE UNTIL YOU HAVE PROPOSED THE VIEW DESIGN — DATA SHAPE *AND* LOOK — AND THE USER HAS APPROVED IT
Not for "obvious" covers, not for "it's just a table", not when defaults look fine, not when the user said "make it look nice" or merely "add a view".
table/board/gallery/calendar/timeline/list/chart) · filter — which rows show · sort — property + direction · grouping — group_by (board columns, sub-groups) · which properties are visible + their order · the view name (self-describing — never leave Default view). Field reference: references/views.md.page_cover / page_content / a Files-&-media property / none) · card size (small/medium/large) · fit-image (contain vs cover/crop) · card layout (list vs compact) · per-property width. Field reference: references/gallery-view.md.Present a concise design brief — one line per applicable dimension, each with a recommendation + why: type · filter · sort · grouping · visible properties · name; plus cover · size + aspect · card layout for visual types. State EVERY applicable dimension explicitly — especially sort and visible properties, the two most often silently dropped. "No sort / Notion default order" and "all properties, default order" are valid recommendations — but they must be stated, never omitted: if your brief has no sort: line, you skipped it. Never bury a sort/filter/group inside the payload. Then stop and wait — silence or "looks good" is approval; a new question or a tweak is not.
Collapse the friction: if the user already fully specified the design, confirm in ONE sentence instead of a full brief; and fold the brief and the standard approval-gate operations into a single turn ("here's the design I propose; if you approve, here are the exact calls").
| Thought | Reality | |---|---| | "It's just a table — nothing to brainstorm" | Type, filter, sort, and visible props are all choices, even for a plain table | | "A board obviously groups by Status" | Grouping is a design choice — offer Status vs Priority vs Assignee | | "I'll just sort by created date" | A default sort IS a decision — surface it, don't bury it in the payload | | "This view doesn't need a sort, so I won't mention it" | Omitting a dimension = deciding it silently. Every applicable line (sort, visible props) MUST appear — "none/default" is a stated answer, not a skip | | "'Current sprint' implies the filter" | Which property = which value? Name it and confirm — never guess a filter | | "'Create a gallery' implies large covers" | Implicit intent ≠ approval — show the brief | | "Defaults are fine, skip the brief" | "Default" is a design decision you're making for them — surface it |
Draft the design brief (type, filter, sort, grouping, visible props, name — plus cover/size/aspect/layout for visual views), present it, wait for approval, then proceed through the standard approval-gate write cycle. Non-negotiable.
Creating any structure-bearing object — a data source / database, a page, or new properties on one — is a design act. Propose the COMPLETE design — every logical AND aesthetic choice — in the approval batch, before the first POST/PATCH. A column with no icon, a select with default colors, an unformatted number: each is a decision you made silently for the user.
Violating the letter of this rule is violating the spirit of this rule.
NO CREATE (data source, page, or property) UNTIL ITS FULL DESIGN — LOGICAL + AESTHETIC — IS PROPOSED AND APPROVED
R$); gallery/card look + layout.Conform to the nearest AGENTS.md (read its recorded conventions; if none exist for this concern, brainstorm fresh and record them back per the conventions rule). Apply in one batch: upsert_property sets the column defs + their icons (and any page values); request creates the database/page and sets the page icon/cover.
| Thought | Reality |
|---|---|
| "I'll create the columns now and add icons later" | Icons are part of the design — propose them in the same batch; upsert_property makes it one call |
| "Default option colors / number format are fine" | "Default" is a choice you're making for them — surface it |
| "It's just a quick column add" | A new column with no icon breaks the workspace's column-icon convention — propose its icon |
| "The icon doesn't matter for a hidden/rollup column" | Every column carries one for consistency; pick one that matches its meaning |
Every create proposes the whole object — data model, names, descriptions, views, icons, colors, formats — conforming to the nearest AGENTS.md, in one approval batch; then apply (upsert_property for columns + icons + values, request for the db/page + its icon/cover) and record any new convention back. Non-negotiable.
This skill drives the notion MCP, which exposes eight tools. Reads default to the five readers; request is for writes and the endpoints readers don't cover.
| Job | Tool |
|---|---|
| The ancestral AGENTS.md sweep (mandatory first step) | read_agents_md(id) — one call does the whole climb + precedence; the id is any target (page/row/block/database/data_source) |
| Find a page or data source by name → id | search(query, object_type?) — compact ranked hits (object · "title" · short id · parent) over POST /v1/search; the name→id resolver, so you don't pay the raw endpoint's tens-of-KB page objects. object_type = page | data_source (never database). Ranked, NOT exhaustive; exhaust_all=true pages to the end |
| Read a page or DB row — properties and body | read_page(page_id, format) — markdown (properties as YAML frontmatter + body) · outline (block-id tree with optional depth, for planning edits) · text. Relations→titles, rollups/formulas→scalars, blocks recovered, ~22× smaller than raw JSON. Optional include_properties=false suppresses the YAML property frontmatter (default true) |
| Query DB rows — list / count / sum / grouped total | read_database(database_id, format, …) (database_id = the DB UUID or a data_source_id; auto-resolved) — table · kv · tsv · summary (overall or grouped totals; add group_by to group by a column). Optional fields to limit columns; filter/sorts are Notion objects passed verbatim; exhaust_all=true returns every row and satisfies the pagination law (row pagination only). Its output ALSO appends a # Schema section (every column name · type, formula bodies elided — types only; for column ICONS use describe) and a # Views section, both always. Row page ids are NOT in the output — use raw POST /v1/data_sources/{id}/query (.id per result) when you need an id (e.g. to write a relation) |
| Inspect a database's views (view design) | Already in every read_database output — the trailing # Views section dumps each view's complete config (covers/preview, card size, aspect, layout, visible/hidden props, sorts, filters, quick_filters, chart axes; property ids resolved to names). No flag needed — the reader path for view design (raw GET /v1/views not needed) |
| Describe an object's structure — a data source's column schema + column icons, or a page's icon/cover + property types | describe(id) — any id (page/row/database/data_source). Data source → name · type · detail per column (formula bodies elided) + each column's icon (best-effort private when NOTION_TOKEN_V2 set; silently omitted otherwise — the public API can't read column icons). Page → its public icon, cover, title, parent + property types. Complements read_page (values) and read_database (rows). Standalone schema read; read_database already inlines the types |
| Any write (incl. creating/editing views via POST/PATCH /v1/views); .parent inspection; block-children subtrees not covered by read_page | request(method, path, body?, query?) — the full REST surface |
| Create/update a property — a database column (name, type, format, options+colors, description, + its icon) or a page property value | upsert_property({ properties:[{target_id, property, value?, icon?, color?, remove?}] }) — the write-dual of describe, batched across any mix of data sources + pages. value = a verbatim Notion property object (a schema def for a data_source, a value for a page); icon sets the column icon (private app API; data_source targets only — the public API can't). Replaces set_property_icon. To READ current column icons, use describe |
| Any other UI-only feature the public API can't do (UI relative-date filters, private view state) | private_request — the general private app API (api/v3) escape hatch; ToS-risk, own workspace only (references/private-api.md) |
A manual GET /v1/blocks/{id}/children loop, a GET /v1/pages/{id} to read properties, or a POST /query count/sum/property-read is a smell in a read context — reach for a reader. (Exception: POST /v1/data_sources/{id}/query is still correct when you need a row's .id — the readers don't expose page ids.) format is required on every reader; reader output is plain text with a trailing # … summary line.
https://api.notion.com/v1 — header Notion-Version: 2026-03-11@notionhq/client TypeScript SDK needs v5.12.0+ for 2026-03-11 — relevant only to external app developers; the bundled notion MCP server makes raw HTTP calls (no Notion SDK)/v1/data_sources/{id} — prefer it over the legacy /v1/databases/{id} (which still coexists on 2026-03-11). This covers schema PATCH, row queries, and relation targets: a relation/rollup property references a data_source_id, not a database_id (a 2026-03-11 change; pre-2026 priors that say database_id are stale)POST /v1/databases response → use data_sources[0]['id'] as the data source ID; is_inline: true supported at creationdata_source_id values but NOT valid page_id for GET /pages/{id}filter.value accepts 'page' or 'data_source' — not 'database' (breaking change in 2025-09-03)Version 2026-03-11 breaking changes (requires SDK v5.12.0+):
after param → position object (see references/blocks.md)archived field renamed to in_trash everywheretranscription block type renamed to meeting_notesrequest's query arg is the GET query-string — the only way to send start_cursor/page_size to GET /v1/blocks/{id}/children and GET /v1/views?data_source_id=….upsert_property (or raw private_request) — the public API silently drops them — so when asked whether a property/column icon can be set via the API, the answer is YES, never "UI-only/impossible." Recipe → references/private-api.md.~/.claude/projects/.../tool-results/mcp-notion-*.txt1.2*(attempt+1)s, up to 5 retriestime.sleep(0.03) in loopsmcp.notion.com/mcp): 180 req/min general, 30 req/min search; provides notion-search, notion-fetch, notion-create-pages, notion-update-page, notion-move-pages, notion-duplicate-page, notion-create-database, notion-update-data-source, notion-create-view, notion-update-view, notion-query-data-sources (Enterprise+AI), notion-query-database-view (Business+), notion-create-comment, notion-get-comments, notion-get-teams, notion-get-users, notion-get-user, notion-get-selfGET /v1/users/me → 401 = invalid token; 403/404 = token valid but content not shared with integrationPermission model — two layers required:
... > Connections menuReads: prefer read_page (pages/rows) and read_database (/query) over the raw endpoints below — they are for writes and for what the readers don't cover (see the tool table).
GET /v1/data_sources/{id} # DB schema (properties map with ids + types)
PATCH /v1/data_sources/{id} # add/modify/delete/rename properties
POST /v1/data_sources/{id}/query # query rows; body: {page_size,filter,sorts,start_cursor}
GET /v1/databases/{id} # resolve database_id → data_sources[0].id; also .parent
GET /v1/pages/{id} # page metadata + .parent — for content/properties use read_page
PATCH /v1/pages/{id} # update page properties / icon / cover / in_trash
POST /v1/pages # create page or DB row
GET /v1/pages/{id}/markdown # PREFER read_page(page_id,"markdown"); raw GET truncates large pages + skips block recovery
PATCH /v1/pages/{id}/markdown # update page content via Markdown
POST /v1/databases # create database — then inspect via read_database / GET /v1/data_sources/{ds}
POST /v1/pages/{id}/move # re-parent a page (move to a new parent page)
PATCH /v1/databases/{id} # move a database (set {parent}); also the legacy schema path
GET /v1/blocks/{id}/children # child blocks — PREFER read_page(page_id,"outline"); hand-roll only for non-page subtrees
PATCH /v1/blocks/{id}/children # append children (append-only)
DELETE /v1/blocks/{id} # delete a content block
Paginate queries: mandatory — loop on next_cursor until has_more == false before counting/summing/concluding (see MANDATORY — exhaust every paginated list). page_size max 100.
Add/modify/delete properties in one PATCH:
{
"properties": {
"NewProp": { "number": { "format": "real" } },
"RenamedProp": { "name": "Better Name" },
"DeadProp": null
}
}
Payload size constraints:
The heavy API reference is split into sibling files under references/. Load only what the task needs — not all of them; for adjacent domains, load both.
| Task | Load |
|---|---|
| Property shapes, reading values, page/DB icons & covers (for property/column icons use the private-API row below, NOT this one) | references/pages-properties.md |
| Property/column icons (the icon next to a column name) & other UI-only features the public API can't do — column icons via upsert_property; other UI-only writes via private_request (never answer "impossible") | references/private-api.md |
| Built-in icon name catalog (the {type:"icon"} names) | references/icon-names.md |
| Blocks, positioning, the reorder workaround, Markdown content API | references/blocks.md |
| Views — list/create/update/delete, linked views, board/calendar/timeline/list/map/form, column visibility, view filters & sorts (date conditions, rollup/formula filterability) | references/views.md |
| Gallery view visual config (cover, card size, visible props) + sourcing B&W cover images | references/gallery-view.md |
| Authoring / editing an AGENTS.md playbook well (the writing-skills discipline, adapted to Notion) | references/agents-md-authoring.md |
| Charts — limits & gotchas | references/charts.md |
| Formulas (gotchas, pt-BR currency) & number formatting | references/formulas.md |
| Relations & rollups | references/relations-rollups.md |
| Querying/filtering rows, search, extracting a data_source_id from a URL; webhooks, caching, idempotency | references/patterns.md |
| Debugging an API error (400/409/429/401/403, validation_error, permission) | references/patterns.md + the matching domain file above |
tools
Use when working with André's self-hosted Google Workspace MCP (the `google-workspace` plugin) — driving Calendar, Gmail, Drive, Docs, Sheets, Slides, Forms, Tasks, Chat, or Contacts via the `mcp__plugin_google-workspace_workspace__*` tools, OR setting up / troubleshooting its OAuth (first-run consent, 7-day test-mode re-auth, credential storage). Covers the account-isolation rule (never use the `mcp__claude_ai_*` Google connectors — different account).
tools
YCloud — a multi-channel communications provider (CPaaS: WhatsApp, SMS, Voice, Email), not a Meta-only BSP. This skill covers its WhatsApp Business operations: console navigation, account creation/onboarding, Embedded Signup, campaigns/inbox/journeys, auto-unsubscribe chatbot, the public-API-vs-dashboard-backend distinction, BSP migration, and read-only CDP automation. Use when operating YCloud for WhatsApp dispatch: embedded signup, campaign sends, campaign analytics, inbox, auto-unsubscribe chatbot, opt-out attribution, dashboard automation. Triggers on: 'ycloud', 'CPaaS', 'BSP', 'bulk campaign', 'whatsapp dashboard', 'embedded signup', 'auto-unsubscribe', 'opt-out chatbot', 'campaign analytics', 'dispatch automation', 'ycloud free plan', 'zero markup', 'ycloud account creation', 'ycloud onboarding', 'ycloud signup code'.
development
YCloud v2 REST API reference for WhatsApp messaging via the BSP layer. Covers every callable endpoint: sending and listing messages (async and sendDirectly), template CRUD, phone number and WABA metadata, wallet balance, webhook management, contacts, unsubscribers/opt-outs, and media upload. Includes live-verified behavior deviations, pagination gotchas, and filter limitations. Use when calling the YCloud v2 REST API for WhatsApp — sending/listing messages, templates, phone numbers/WABA, wallet/balance, webhooks, contacts, unsubscribers, media, pagination gotchas. Triggers: 'ycloud api', 'X-API-Key', '/v2/whatsapp/messages', 'ycloud webhook', 'ycloud pagination', 'ycloud balance', 'sendDirectly', 'unsubscribers endpoint'.
development
WhatsApp Business Platform (Cloud API) production reference for the maccing growth stack. Covers Cloud API setup, message types, templates (creation, approval, pacing, strategy), per-message pricing, webhooks, bulk sending at scale, WhatsApp Flows, media handling, Node.js/TypeScript SDK, error codes, compliance (LGPD, opt-in/opt-out), Calling API, MM Lite, Business Management API, business profile fields, dispatch operations (chip warming, direct API setup, BSP migration, number longevity), and 2025-2026 platform changes. Use when the user asks about whatsapp, whatsapp api, cloud api, WABA, BSP, whatsapp template, whatsapp marketing, message template, WhatsApp dispatch, WhatsApp number quality, WhatsApp Brazil, WhatsApp opt-in, WhatsApp webhook, WhatsApp flows, WhatsApp pricing, or anything related to sending or receiving WhatsApp messages via the Cloud API.