Applies several editing operations to a project as ONE atomic change — the preferred way to build or restructure a sitemap. To restructure existing pages use nodes.move — never delete and recreate a page to relocate it (its content would be lost). Operations run in order and each sees the effect of the previous ones. Pick `operation` first and read that operation's `data` fields: each names its own parent. An operation that creates something can also take a `ref`: a local name later operations point at via the matching `*_ref` field instead of an id — leave it out whenever nothing later refers to what was created. Set `dry_run: true` to validate and preview without changing anything. Up to 100 operations per call, which is the API's own cap. Put as much of the job in ONE call as fits rather than splitting it up: a batch costs the same one write whether it carries 3 operations or 100, so splitting buys nothing and costs a round trip. A one-operation batch is right only when one change is genuinely all there is to make — if you are about to delete several pages, retitle several nodes, or clear starter pages, that is ONE call with several operations, not one call each. Split only what exceeds the cap or ~15-20 KB of JSON arguments (past that some clients truncate the call, which fails as unparseable JSON), into sequential batches.
ORDER MATTERS: emptying a container deletes it. Deleting the last page of a section removes the section, so a later operation naming it (as parent_id or as its own id) fails with "Not found" and takes the whole atomic batch with it. Deleting a page that still has siblings is safe anywhere. The rule that always works: create first, delete last.
POSITION pages by naming a neighbour, not by counting: nodes.create and nodes.move take `after_id`/`before_id` (`after_ref`/`before_ref` for a page created in this batch), which decide the parent too. An absolute `index` goes stale inside a batch as earlier operations shift siblings, so prefer an anchor and chain `after_ref` down a list of new pages. Never create a page and then move it into place.
Required `data` fields: every operation except the *.create ones and settings.update needs `id` (or `id_ref`) naming what it acts on. On top of that — sections.create {tab_id|tab_ref}; nodes.create and nodes.move {parent_id|parent_ref, or an after_id/before_id/after_ref/before_ref anchor instead}; blocks.create and blocks.move {node_id|node_ref}; sections.move_up and sections.move_down take only `id` and shift by ONE place, so moving a section N places is N of them in the same batch. Everything else takes only the fields being changed, e.g. blocks.create {node_ref, title, content, wireframes: ["header"]}. Content fields (block/symbol content, node notes.note) accept markdown or plain text and are read back as markdown.
A successful result may include `warnings[]` — notes about a change that WAS applied. They are not errors: nothing was rejected, and repeating the call would duplicate the change, so pass the note on or fix it with a follow-up edit instead of retrying. One code today, URL_OUTSIDE_PARENT_PATH: a page url outside its parent page's path — allowed and often deliberate (standalone SEO landings), so raise it rather than silently "correcting" it. `dry_run` reports them too, so a preview shows what a commit would flag.
The operations listed in the schema are the whole editing surface. If a request needs something none of them covers, say that it cannot be done here — never substitute a different operation for it, and never report it as done.
Paid features follow the PROJECT OWNER's plan — read `meta.ownerPlan` (get_project, or create_project's `outline`) before writing, because ONE gated operation fails the whole atomic batch. When it is "free" (or null): these operations are unavailable outright — tabs.create, symbols.create, symbols.update, symbols.delete, colors.create, colors.update, colors.delete, tags.create, tags.update, tags.delete, estimates.update_settings, estimates.add_line_item, estimates.update_line_item, estimates.delete_line_item, estimates.set_value; only these wireframes are allowed — wide, text, header, double, text_double, divider_dashed, triple, text_triple, footer, logos, text_quarter; nodes take only variant "default" (frame, ghost, stack are Pro+); node notes take only the plain `note` (the SEO keys are Pro+); and settings.update cannot set image_mode or frame. Each fails with PLAN_REQUIRED naming the trigger and the free alternatives.
apply_changes