Skip to content
Notion Arena

Notion platform

Notion API limits, cited

These are the constraints that decide what a recipe can install and what it has to hand you as a click path. Each one links to the page in the Notion reference that states it. Nothing here is inferred from a forum post.

Block creation

LimitValueHow we handle it
Children per append request100The installer chunks every page body into groups of 100 and issues one PATCH per chunk.
Nesting depth per request2 levelsA column_list containing columns containing paragraphs is already at the ceiling. Deeper trees are split across requests, appended to the block id the previous request returned.
Block types availableSee referenceparagraph, heading_1-4, bulleted/numbered_list_item, to_do, toggle, quote, callout, divider, code, equation, embed, bookmark, column_list, synced_block, table, table_row, breadcrumb, table_of_contents, child_page, child_database.
button block typedoes not existButtons are the single most-requested thing a recipe cannot create. Every recipe that needs one ships the exact click path instead.
Insertion positionpositionSince 2026-03-11, <code>position</code> replaces the older <code>after</code> field for placing children.
Notifications on writemode: "silent"Bulk installs should send this, otherwise every appended block can notify watchers of the page.

Sources: Append block children and Block object.

The data-source split, and why installs are two-pass

Notion separated databases from data sources. A database is the container; a data source holds the schema and the rows. Every database created through the API arrives with exactly one default data source, whose id you do not choose and cannot predict.

A relation property targets a data source. So creating Deals with a relation to Companies requires the data_source_id of Companies, which only exists after Companies has been created and read back.

the ordering a correct install must followtext
pass 1 · POST /v1/databases      create every database, base properties only
                                 (relations, rollups and dependent formulas omitted)
pass 2 · GET  /v1/databases/{id} read each one back, take data_sources[0].id
pass 3 · PATCH /v1/data_sources/{id}
                                 now add the relation properties, using the ids
pass 4 · PATCH /v1/data_sources/{id}
                                 then rollups (they name a relation property)
pass 5 · PATCH /v1/data_sources/{id}
                                 then formulas that read either of the above
Why recipes declare refs instead of ids
In a recipe definition, a relation points at another recipe database by ref: a local name like companies. The installer marks those with an internal __relationRef field, strips them from pass 1, and resolves each one to a real data_source_id in pass 3. That is why a recipe can declare a bidirectional relation between two databases that do not exist yet.

Sources: Create a data source and Property object.

Properties the API will not write

Two property types exist in the response schema and are silently ignored on create or update:

Property typeReadableWritableNote
buttonReturned by the API on databases that have one. Cannot be created or configured programmatically: it is a page-level control, not a schema field.
verificationThe wiki page-verification badge. Response-only.
status groupsOn create, each option may carry a <code>group</code> of To-do, In progress or Complete. In responses, groups arrive separately with <code>option_ids</code>, so a round-trip is not symmetrical.
unique_idYou can set the <code>prefix</code>. You cannot set the number.
relationCurrent versions target a data source, not a database id. Older integrations pinned to an earlier Notion-Version still address databases directly.

Source: Property object.

Views

The Views API is recent, and it is the reason provisioning a workspace from code finally works end to end. Before it, you could create a database but not shape how anyone looked at it.

  • POST /v1/views creates table, board, list, calendar, timeline, gallery, form, chart, map and dashboard views.
  • Board groups, timeline date properties, calendar ranges, chart axes and form questions are all configurable at creation time.
  • Dashboard layouts are read-only. You can create a dashboard view, and you can create the widget views it would display, but you cannot position them. Notion composes the layout itself from the widget views attached to the same data source.

Our habit-os-native recipe creates a dashboard view plus each chart view as its own request, then tells you in the manual steps that the arrangement is Notion's to decide.

Source: Working with views.

The four gaps every recipe works around

These appear as manual[] entries in 11 of our 11 recipes. Each entry carries the reason, verbatim, so you can check it against the reference yourself.

Button blocks
Why it cannot be scripted: There is no button block type in the public API, and the button property is response-only. A recipe can build everything a button acts on, never the button.Click path: On the target page, type /button → add each action in order → Notion runs them top to bottom. Buttons cannot loop, cannot branch, and cannot read a rollup.
Database automations
Why it cannot be scripted: No endpoint exists. Automations are configured in the database UI and stored outside the public schema.Click path: Open the database → ••• → Automations → New automation. Each recipe lists the trigger, the conditions and the actions in the order to add them.
Recurring templates
Why it cannot be scripted: Database templates can be created through the API as page bodies, but the repeat schedule is a Business-and-above feature with no endpoint.Click path: Open the database → the arrow next to New → New template → build the body → ••• on the template → Repeat → pick the schedule.
Synced block placement
Why it cannot be scripted: The API can create a synced_block with children, but cannot create the second instance of it elsewhere in the workspace. An original and its duplicates are linked in the UI, not addressable by id.Click path: Create the original via the API, then in each destination page type /synced and choose the existing block. Recipes tell you exactly which pages need it.

Rate limits and quotas

ConstraintValueEffect on an install
Request rate~3 req/s averageA 26-request CRM install takes well under a minute even paced conservatively. Our installer does not parallelise writes to the same parent.
Children per request100Long documentation pages are chunked; the chunk count is visible in the plan before you run it.
Nesting per request2 levelsDeep trees need sequential appends against returned block ids.
Payload size500 KB per requestPage bodies of a few hundred blocks are fine. A simple_table whose serialized body exceeds 500 KB needs splitting across requests.
File upload sizeDepends on planWidget HTML is 15-90 KB, so uploads are never close to the limit on any plan.
Integration connectionsPer page or per workspaceAn internal integration must be explicitly connected to every page it writes to, or writes return restricted_resource.
Read the plan before you spend a token
POST /api/install with mode: "plan" requires no credentials and returns every request: method, path, body and a note. You can count them, diff them, and estimate the runtime before anything touches your workspace.

Notion-Version

Every request in this project sends Notion-Version: 2026-03-11. That version introduced the File Upload path for HTML blocks, heading_4, tab blocks, the position field, silent notification mode, and a typed database_type on database objects.

Why pinning matters

  • Notion's API is versioned by date, and behaviour changes between versions, notably the database/data-source split, which changes what a relation property points at.
  • A recipe that resolves data_sources[0].id is only correct on versions where data sources exist. Pinning is what makes that safe.
  • If you fork this project and change the version constant, re-run the plan for one recipe and diff it before installing anything.

Source: Notion API changelog.