Pages Tools
Core 0.2.0 uses one Page model for all content. See Content types and the upgrade guide for existing installations.
Page fields
Section titled “Page fields”| Field | Type | Description |
|---|---|---|
content_type |
string | Content type ID; defaults to page. |
fields |
object | Custom values defined by the content type. |
path |
string | Explicit public path, overriding the type URL pattern. |
title |
string | Page title. Used in nav and as default <title> tag. |
slug |
string | URL path. "" = homepage. No leading slash. |
content_mode |
"blocks" | "html" |
"blocks" is the default for new pages. See Blocks for the block-mode editing surface. |
html_content |
string | Full page body HTML — used when content_mode === "html". |
blocks |
Block[] |
Block tree — used when content_mode === "blocks". Edit via add_block, update_block, move_block, remove_block, duplicate_block. |
template |
string | Optional PageTemplate id. The renderer composes the template’s block tree with the page’s blocks at build time. |
status |
"published" | "draft" | "review" | "unlisted" |
Only published and unlisted pages are built into the static site. unlisted pages are reachable by URL but excluded from listings and the sitemap. |
publish_at |
string | ISO 8601. Schedule the page to go live automatically at this time. Cleared once it fires. |
unpublish_at |
string | ISO 8601. Schedule the page to come down automatically. |
kind |
"page" | "article" |
"article" enables Article JSON-LD schema. |
seo_title |
string | Custom <title> tag. Defaults to title + suffix. |
seo_description |
string | <meta name="description">. Describe the actual page accurately; there is no fixed length requirement. |
og_title |
string | Optional sharing title for OpenGraph/Twitter; empty uses the document title. Core 0.2.120 / MCP 0.45.90. |
og_description |
string | Optional sharing description; empty uses the SEO description and its site fallbacks. Core 0.2.120 / MCP 0.45.90. |
seo_image_alt |
string | Sharing-image description; empty uses the mapped public image alt when applicable. |
og_image |
string | Absolute CDN URL for Open Graph image. |
canonical_url |
string | Override the canonical URL (e.g. after a migration). |
noindex |
boolean | Set true to add <meta name="robots" content="noindex">. |
nofollow |
boolean | Restrict following links independently. Public noindex defaults to noindex,follow; explicit nofollow and private previews remain restricted. |
language |
string | Per-page language override (e.g. "en", "sv"). |
author |
string | Article author. Used in Article JSON-LD. |
date_published |
string | ISO 8601 date. Used in Article JSON-LD. |
json_ld |
string | Custom JSON-LD schema as a JSON string. For FAQ, recipe and event pages, use the structured data tools. |
Homepage
Section titled “Homepage”The homepage has slug: "" and is stored with id: "home". The AI agent uses this automatically when you say “the homepage” or “the start page”.
create_page
Section titled “create_page”Creates a new page. Required: title. Pass content_type and fields for articles or any other type. Defaults: status: "draft", content_mode: "blocks". The AI agent picks the block library to compose the body unless you ask for HTML mode explicitly.
The page id is derived from its address (/category/news/diy becomes category_news_diy). Pass id to choose it yourself, for example to keep parent or filter values stable during a migration: 1–100 letters, digits, dots, underscores or hyphens. An id that is already in use fails with 409; a supplied id is never replaced.
Starting from a marketing page layout
Section titled “Starting from a marketing page layout”get_composition_starter returns an editable block tree for five common
marketing pages, built only from core blocks. Nothing is saved by the read.
kind |
Contains |
|---|---|
feature_page |
Hero, benefits grid, how it works with a guide link, FAQ, call to action |
comparison_page |
Hero, dated summary with sources, comparison table, when to choose which, FAQ, call to action |
integration_page |
Hero, what you can do, setup steps with a code block, FAQ, call to action |
pricing_page |
Title, pricing table, plan comparison table, FAQ, call to action |
use_case_page |
Hero, problem, how the product helps, example, FAQ, call to action |
Pass the returned blocks to create_page or update_page. Every placeholder is
in square brackets: replace each one, and remove sections you have no real
content for, such as an example without a measurable outcome. The FAQ is an
ordinary Accordion, so it shows the questions on the page.
Importing structured answers
Section titled “Importing structured answers”Core 0.2.95 / MCP 0.45.74 add low-priority imports in Cloud and self-hosted installations running that release. Earlier versions lack this contract; ordinary API writes have editor authority.
Use create_page with write_mode: "import" for research or migration data that
a person will later confirm or correct. Affected fields must explicitly include
import in writable_by, directly or through their enclosing field. Defaults
also receive provenance. Normal creation defaults to write_mode: "editor".
The server assigns provenance; callers cannot impersonate an owner or downgrade
existing editor decisions.
For existing Pages, first call read_page_authority:
{ "page_id": "example", "paths": ["programs/@summer/legacy_anchor", "support/training"]}The response includes a revision, whether a working copy exists, and each
requested leaf’s source. attribution is explicit, inherited or
unattributed; effective_path identifies the source’s provenance path. No
values, actor identities or evidence URLs are returned. Ordinary Page reads omit
provenance: that does not mean the answers are unprotected.
Then pass the returned revision to import_page_fields:
{ "page_id": "example", "base_revision": "<revision from read_page_authority>", "import_run_id": "migration-2026-10", "changes": [ { "path": "programs/@summer/legacy_anchor", "value": "original-program" } ]}Paths name custom-field leaves, without a fields/ prefix. Keyed arrays use @
followed by an existing stable item ID; escape ~ as ~0 and / as ~1 inside
segments. Omitted fields and other items are preserved. This operation cannot
insert/delete items, change their IDs or replace compound objects/arrays. Scalar
lists such as multiselects are indivisible leaf values. Both tools accept up to
100 paths/changes and the same selected Site/version, and require editor access.
Installation credentials cannot use this import/readback surface.
Import saves immediately. It does not invoke publication, but the Site’s existing
automatic publishing policy still applies. A protected answer, including an
owner’s deliberate null, returns 409 with rejected paths; the whole import
is rejected without partial writes. An existing draft or changed Page/schema also
returns 409. Resolve the conflict and read again; never blindly retry using an
override. Unchanged values are not re-confirmed. Changes that alter the Page URL
are rejected: use the normal route-editing flow and redirects instead.
Earlier imports stamped as editor/API decisions are not reset by this feature. Review those records separately; do not erase provenance or reimport a production library just to make owner edits pass.
Only addressed leaves are validated against the current schema. Removing a field definition does not physically delete its stored values: imports preserve such retired values and their provenance, but cannot address or change them. Stop consuming obsolete mirrors when changing a schema; do not maintain them as a second source of truth.
Create a page titled "Tjänster" at slug "tjanster" with three service sections.Slug and address conflicts
Section titled “Slug and address conflicts”A requested address is never changed silently. Creation compares the new
page’s address with every page in the version — drafts, review and unlisted
pages included, and the address an unsaved page draft will take when it is
saved. The address comes from the content type’s URL pattern, or from an
explicit path.
-
Explicit
slugorpathalready in use: the call is refused with409and a stablecode—slug_in_use, orpath_in_usewhen you passedpathor the content type builds its URL from fields other than the slug.detailsnames the page that holds the address:{"error": "Slug \"tack\" is already in use: /tack belongs to page tack. Choose another slug, or edit the existing page.","code": "slug_in_use","details": { "page_id": "tack", "path": "/tack" }}Edit that page, or choose another slug. Nothing is created.
-
Slug derived from the title (no
slugpassed): the page is created with a numbered slug, such astack-2, and the response carries a warning:"warnings": [{"code": "slug_adjusted","requested": "tack","slug": "tack-2","path": "/tack-2","conflicting_page_id": "tack","message": "…"}]Scripts that link to the new page should use the returned
slugandurl, or pass an explicitslugto be told about the conflict instead.
clone_page follows the same rule: a slug you pass must be free, and a slug
derived from the new title is numbered with a warning. The portal’s New
page form names only a title, so it numbers the slug and the editor says
which page already uses the requested address. Two pages may share a slug when
their addresses differ, for example under different path prefixes or content
types.
set_page_mode
Section titled “set_page_mode”Switches a page between "html" and "blocks" modes. Snapshots a revision first. Typeroll does not convert HTML into blocks: switching to blocks starts an empty block tree and keeps the HTML in the page history. Build the content with blocks afterwards.
Switch the About page to blocks mode and rebuild it with blocks. → set_page_mode page_id=about to=blocksupdate_page
Section titled “update_page”Updates one or more fields on an existing page. Pass only the fields you want to change — others are preserved.
Update the Services page: change the seo_description to "..."Structured data tools
Section titled “Structured data tools”add_faq_schema, add_recipe_schema and add_event_schema add schema.org JSON-LD to a page without hand-writing json_ld.
They validate the input, build the JSON-LD and merge it into the page’s
json_ld field: an existing node of the same type is replaced, other
structured data on the page is kept in an @graph. A page whose existing
json_ld is not valid JSON-LD is refused rather than overwritten. Like other
content edits, the result is a draft; pass save: true or save it afterwards.
They need write permission and accept version.
| Tool | Required | Optional |
|---|---|---|
add_faq_schema |
page_id, items (question, answer) |
— |
add_recipe_schema |
page_id, name, ingredients, instructions |
description, image_url, author, prep_time, cook_time, total_time (ISO 8601 durations such as PT30M), recipe_yield |
add_event_schema |
page_id, name, start_date |
description, end_date, location_name, location_address, image_url, offers_url, offers_price, offers_currency (ISO 4217) |
Validation rules: FAQ items need a question and an answer; recipes need at least
one ingredient and one step, and URLs must be absolute; event dates are ISO 8601
(include the UTC offset), the end is not before the start, and a price needs a
currency. The response lists warnings when the data is valid but not eligible
for Google’s rich results, such as a recipe without an image or an event
without a location.
The FAQ page shows five questions. Add FAQ structured data for them. → add_faq_schema page_id=faq items=[…]Use FAQ markup only on a page that visibly shows those questions and answers.
The built-in AI chat has tools with the same names, inputs and rules. Over the
REST API, write the JSON-LD string to json_ld with PATCH /api/v1/sites/{siteId}/pages/{pageId}; for other types, use update_page with
json_ld in the same way. See SEO and indexing.
batch_update_pages
Section titled “batch_update_pages”Updates multiple pages in one call. More efficient than individual updates for bulk changes like SEO fixes.
Over the REST API this is POST /api/v1/sites/{siteId}/pages/batch-write, taking
[{ page_id, patch, save? }] up to 200 entries and answering
{ results: [{ page_id, ok, saved, error? }] }. Each entry applies
independently, so one failed page does not abort the rest, and save: true
commits that page’s working copy in the same call. Prefer it to a loop of
single-page writes: a per-page loop across a whole site is slower, has no
per-entry report, and leaves a partial result you have to reconstruct by
reading the data back.
Write meta descriptions for all pages.The AI agent uses batch_update_pages automatically for multi-page operations.
For block-based Pages, update blocks. Read the saved tree first, preserve existing
block IDs, and give new blocks unique IDs, including blocks nested in children
or slots. An ID must contain 1–128 letters, digits, underscores or hyphens.
The supplied tree replaces the Page’s entire block tree; it is not a list of
individual block patches. Use save: true on each batch entry to commit its
working copy before deploying, and check every entry’s result for errors.
list_pages
Section titled “list_pages”Returns all pages with title, slug, status and SEO fields — but not the HTML body (to keep responses small).
Add full: true to include html_content — use sparingly on large sites.
read_page
Section titled “read_page”Returns a single page including its full html_content. The AI agent uses this before editing a page.
batch_read_pages
Section titled “batch_read_pages”Reads multiple pages by ID in one call. Returns { pages_by_id: { id: page }, not_found: [] }.
Over the REST API this is POST /api/v1/sites/{siteId}/pages/batch-read, taking
{ page_ids: [...] } up to 200 ids and answering
{ pages: [{ page_id, found, page? }] }. Each page carries fields and
blocks, which list_pages omits by default.
It reads accepted content. A page with an uncommitted working copy is
returned as it was last saved, not as it currently reads in the editor — so use
read_page when you need to tell a draft from accepted content.
resolve_page_url
Section titled “resolve_page_url”Finds the Page behind a URL or path on the selected site and version, including
addresses created by Content type routes. Pass url and optionally version.
It never fetches the URL and refuses addresses on other sites. A #fragment
points to a place on the returned page, not to a separate page.
Which page is https://example.com/blog/moving-tips/ ?search_pages
Section titled “search_pages”Finds pages whose saved content contains a substring (contains) or matches a
regular expression (regex). It searches the same editorial text that
bulk_replace_text edits: block data in block-mode pages, html_content in
HTML-mode pages, and the content type’s text fields. Block ids and block types
are not searched. Each match reports field (blocks, html_content or
fields.<name>) and an excerpt around the first hit:
search_pages contains="<h1" ← pages with an H1search_pages contains="calendly" ← pages with a Calendly embedsearch_pages contains="/old-path" ← pages with a stale internal linkdelete_page
Section titled “delete_page”Permanently deletes a page. This also removes it from the next deploy.
Scheduled publishing
Section titled “Scheduled publishing”Set publish_at on a draft and it goes live on its own — no one has to be at a
keyboard:
Publish the spring campaign page on 1 April at 08:00.Take the winter offer down on 28 February.Saved schedules create a pending task for the chosen time. The task updates anything that’s due, clears the timer so a re-run can’t double-fire, and triggers one deploy per site no matter how many pages changed, regardless of content type.
The times are absolute (ISO 8601). A scheduled publish still only shows content that was saved — a page with unsaved drafts publishes its saved version.
HTML conventions (HTML-mode pages)
Section titled “HTML conventions (HTML-mode pages)”When a page is in content_mode: "html", the AI agent follows these conventions for the body content:
- CSS variables for colours and fonts —
var(--color-primary),var(--font-heading), never hardcoded hex - One
<h1>per page — the page title. Sections use<h2>. - No
<nav>,<header>,<footer>in page HTML — those live in partials - Inline
<style>blocks are allowed — scoped to the page’s classes <iframe>allowed for YouTube, Vimeo, Calendly, Google Maps- No
<script>tags — use site settings for analytics/chat widgets
In block-mode pages, none of these conventions apply directly — the block library handles structure and the renderer enforces semantics. See the Blocks reference.
Short breadcrumb labels (Core 0.2.28)
Section titled “Short breadcrumb labels (Core 0.2.28)”Set optional breadcrumb_label through create_page, update_page, the Pages
API (including batch writes), or Page settings → Breadcrumb label. A label
is at most 200 characters; null, empty or whitespace-only values use the normal
page title. It changes the breadcrumb label only, not title, SEO title, slug,
path, canonical or hierarchy. Parent labels are shared by their descendants.
For inferred Content-type trails, the published hub’s short label takes
precedence over the type’s plural label. A home page has no breadcrumb trail.
For example, a hub titled “Our complete guide to moving” may have
breadcrumb_label: "Moving tips". Its child keeps its full page title and route.
Republish after changing a parent label so affected descendants update too.
MCP 0.45.24 exposes the optional field; Cloud/self-hosted servers require Core
0.2.28.
Clone a page
Section titled “Clone a page”clone_page creates a draft, preserving body, Content type, template, parent,
custom fields, language and page CSS. For an explicit path such as
/offers/summer/, slug: "autumn" produces /offers/autumn/ under the site’s
trailing-slash policy. Otherwise routing follows the Content type and parent.
An occupied explicit path returns a conflict; choose another slug.
Canonical overrides and custom JSON-LD are not copied. Pass version to keep
both the source read and clone creation in the selected site version.