Website content
Sites, their page trees, and the AI that drafts and fills them.
List sites
Returns the workspace's sites, each with page, folder and per-status counts, newest first. sitemap_last_edited is the later of the site's own sitemap_updated and the last change to any of its pages; sort=-sitemap_last_edited puts the site most recently worked on first. A "site" is a sitemap: the page tree plus the instructions that content is generated from.
query Parameters
sitemap_activesort^-?[a-z0-9_]+$Headers
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
List sites › Responses
Success
has_moreWhether more rows exist past this page.
next_cursorPass back as cursor for the next page. Null on the last page.
Create a site
Creates an empty site. Pages are added afterwards — by hand, by importing an existing sitemap, or by having a structure proposed from a description of the business.
Headers
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Create a site › Request Body
sitemap_namesitemap_domainsitemap_descriptionsitemap_configsitemap_tpl_groupCreate a site › Responses
Created
sitemap_idsitemap_createdsitemap_updatedsitemap_usersitemap_namesitemap_domainsitemap_descriptionsitemap_configsitemap_metadatasitemap_tpl_groupsitemap_approval_modeHow the site is reviewed: the workspace's default approval flow, the one sitemap_approval_flow names, or not at all. Set through PUT /sites/{id}/approval-flow.
sitemap_approval_flowThe approval flow the site chose; only read when sitemap_approval_mode is flow.
sitemap_last_editedThe later of sitemap_updated and the last change to any of the site's pages. Only sent by the list.
Get a site
Returns the site and every page under it in one response. Pages carry node_parent and node_order, so the tree is rebuilt from a flat list rather than requested level by level. The pages come without node_content: a body is the largest thing a page carries and a site has no bounded number of pages, so a tree that carried every body would grow past what one response can hold. Read the body of one page from GET /sites/{id}/pages/{page_id}, or pass include=content to fetch the site with every body inline — for an export, where the whole thing is the point.
path Parameters
idquery Parameters
includeHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Get a site › Responses
Success
sitemap_idsitemap_createdsitemap_updatedsitemap_usersitemap_namesitemap_domainsitemap_descriptionsitemap_configsitemap_metadatasitemap_tpl_groupsitemap_approval_modeHow the site is reviewed: the workspace's default approval flow, the one sitemap_approval_flow names, or not at all. Set through PUT /sites/{id}/approval-flow.
sitemap_approval_flowThe approval flow the site chose; only read when sitemap_approval_mode is flow.
sitemap_last_editedThe later of sitemap_updated and the last change to any of the site's pages. Only sent by the list.
Delete a site
Soft delete. The site and its pages stop being listed; nothing is removed from a connected CMS.
path Parameters
idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Delete a site › Responses
Success. No content.
Update a site
Applies the sitemap_-prefixed fields present in the body. This is the site itself; its pages are edited through the page endpoints.
path Parameters
idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Update a site › Request Body optional
sitemap_namesitemap_domainsitemap_descriptionsitemap_configsitemap_tpl_groupUpdate a site › Responses
Success
sitemap_idsitemap_createdsitemap_updatedsitemap_usersitemap_namesitemap_domainsitemap_descriptionsitemap_configsitemap_metadatasitemap_tpl_groupsitemap_approval_modeHow the site is reviewed: the workspace's default approval flow, the one sitemap_approval_flow names, or not at all. Set through PUT /sites/{id}/approval-flow.
sitemap_approval_flowThe approval flow the site chose; only read when sitemap_approval_mode is flow.
sitemap_last_editedThe later of sitemap_updated and the last change to any of the site's pages. Only sent by the list.
Set the review flow
Chooses how the site's pages are reviewed: default follows the workspace's default approval flow, flow names one in flow_id, and none turns review off for this site. Requires content:workflow:edit rather than content:edit, because deciding what review a site gets is the same decision as defining the flows. Pages already approved or published land on the new flow's final step; pages mid-review re-enter at its start. The answer names the flow now governing the site in governing_flow — with default that is whichever flow holds the role, and with none it is null.
path Parameters
idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Set the review flow › Request Body
modeflow_idSet the review flow › Responses
Success
Approve all pages
Marks every generated page on the site as approved in one step, for a site reviewed in bulk rather than page by page.
path Parameters
idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Approve all pages › Responses
Success
List site comments
Every comment thread on the site, one row each, newest activity first — the review status of a whole site without fetching a page at a time. Threads only: replies are counted in cmt_reply_count and their authors listed in cmt_participants, and the full conversation is read from the page it is on. Each row names its page in cmt_page and its anchor in cmt_anchor_label, resolved against the page as it stands; cmt_anchor_unattached marks a thread whose field has since been moved or removed. Filter with status=open (the default) or status=resolved.
path Parameters
idquery Parameters
cursor^[A-Za-z0-9_-]+$limitoffsetsort^-?[a-z0-9_]+$statusHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
List site comments › Responses
Success
has_moreWhether more rows exist past this page.
next_cursorPass back as cursor for the next page. Null on the last page.
Set the writing agent
Sets the agent used when generating this site's content. Requires content:generate rather than content:edit — choosing who writes is part of generating, not part of editing the site.
A single page overrides this by naming an agent in its own node_instructions_params.contentAgentId.
path Parameters
idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Set the writing agent › Responses
Success
Generate all pages
Queues content generation for each empty page that has instructions of its own or inherited from a parent. Returns once the work is queued; generation runs in the background and pages move to draft as they complete.
A page that names its own agent in node_instructions_params.contentAgentId is written by that one, so a single run can span pages written by different agents. agentId covers the rest.
path Parameters
idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Generate all pages › Responses
Success
Generate all briefs
Starts background brief generation, one job per page. Returns as soon as the jobs are queued, not when they finish — poll the site's briefsJob for progress.
path Parameters
idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Generate all briefs › Request Body optional
nodeIdsoptionssiteBriefGenerate all briefs › Responses
Success
Generate the site brief
Drafts the site-level instructions every page inherits, reading the live site where one exists.
path Parameters
idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Generate the site brief › Responses
Success
Generate the structure
Proposes a page tree from a description of the business, for a site that does not exist yet. Returns the proposal for review — it is not saved until you import it.
path Parameters
idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Generate the structure › Request Body
descriptionsizeGenerate the structure › Responses
Success
Get generation defaults
The site's instructions for the two passes that follow the copywriter, beside the platform defaults they replace when set: review (what the review agent writes about each page) and editor (what the editor agent is told to enforce and report). Set through the site's sitemap_config: reviewInstructions and editorInstructions, beside the agents themselves in reviewAgentId and editorAgentId. A page appends its own notes in node_instructions_params.reviewInstructions / editorInstructions.
path Parameters
idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Get generation defaults › Responses
Success
Import pages
Builds the page tree from URLs or page titles. Accepts a urls array, sitemap XML, a url to fetch the XML from, or a plain one-per-line list — the parser is deliberately tolerant, because this input is usually pasted by a person. A list with nothing URL-shaped in it is read as page titles: each line becomes a page whose slug is made from the title, and a line indented under the one before it becomes that page's child.
Every listed path becomes a page, whether or not other listed paths sit beneath it: a section landing page is still a page. A segment that appears only as an ancestor becomes a folder, since nothing was said about it. A type of page or folder, from a column or the sidecar, decides either way, and on a path that already exists it changes the node in place, except that a page holding content is never turned into a folder.
A comma, tab or semicolon separated file is read as a table when its first row names the columns: url (or path, slug) locates the page, name (or title) names it, and instructions and type are applied the same way the instructions sidecar is. A heading is matched on any word it carries, so V1 URL (slug) is a URL column; a table with a name column and no URL column gets its slugs from the names. Without a recognized header row the first URL-looking cell of each line is taken and the rest of the line is discarded. A page that arrives without a name is named after its slug, dashes to spaces and words capitalized. On a dry run the response reports which format was read and which columns went unused.
It also accepts a site export: post the exported document as the body, or its nodes array on its own. A URL path cannot carry capitalization, sibling order, or two pages under the same folder sharing a slug, so an export is the input that survives a round trip — the tree, page names, order and type are read from the file. Pages are matched on the ids in the file, then on their slug path, and updated where they match; nothing is deleted, so a page the file no longer mentions stays. Content in the file is written as a draft, which withdraws any sign-off it had, exactly as editing the page would.
path Parameters
idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Import pages › Request Body optional
xmlurlurlskeysdryRuninstructionsImport pages › Responses
Success
Add a page
Adds a page or folder to the tree. Omit node_order and it is appended to the bottom of its sibling group, which is almost always what you want — passing 0 collides with every existing sibling.
node_slug is the page's own URL segment and is derived from node_name when omitted. Two pages under one parent cannot share it, so a slug already in use is numbered off it (about, about-2) and the response carries the one the page was given.
path Parameters
idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Add a page › Request Body
node_namenode_parentnode_typenode_slugnode_instructionsnode_instructions_paramsnode_children_instructionsnode_componentnode_orderAdd a page › Responses
Created
node_idnode_creatednode_updatednode_sitemapnode_parentnode_ordernode_typeA page holds content and may have pages under it; a folder only holds pages. A page with content cannot become a folder.
node_namenode_slugnode_instructionsnode_instructions_paramsnode_children_instructionsnode_componentnode_contentnode_content_summarynode_content_statusnode_state_itemnode_threadsnode_metadataThe page's SEO meta data. Filled by generation from the draft copy and editable; kept out of node_content so it is never pushed to a connected CMS.
Reviewer material written alongside the copy: the review agent's account of the page when the site names one (review), and the editor agent's pass when the site names an editor (editor). Kept out of node_content so it never reaches a CMS. Left out of the site tree; read it from one page, or include=review.
node_assigneeWho the page currently waits on. Set freely via PUT /sites/{id}/pages/{page_id}/assignee and carried unchanged through workflow moves unless the move says otherwise.
node_approved_atWhen the page was last signed off. A page carrying this and standing at draft has been edited since — the copy that was approved is still kept, and GET /sites/{id}/pages/{page_id}/approved-content answers with it.
node_approved_byWho signed it off. Null on pages approved before this was recorded.
node_open_commentsUnresolved comment threads on this page. Only sent when a site is fetched whole.
Get a page
One page: what it is for, what has been written into it, and how far through review it is.
path Parameters
idpage_idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Get a page › Responses
Success
node_idnode_creatednode_updatednode_sitemapnode_parentnode_ordernode_typeA page holds content and may have pages under it; a folder only holds pages. A page with content cannot become a folder.
node_namenode_slugnode_instructionsnode_instructions_paramsnode_children_instructionsnode_componentnode_contentnode_content_summarynode_content_statusnode_state_itemnode_threadsnode_metadataThe page's SEO meta data. Filled by generation from the draft copy and editable; kept out of node_content so it is never pushed to a connected CMS.
Reviewer material written alongside the copy: the review agent's account of the page when the site names one (review), and the editor agent's pass when the site names an editor (editor). Kept out of node_content so it never reaches a CMS. Left out of the site tree; read it from one page, or include=review.
node_assigneeWho the page currently waits on. Set freely via PUT /sites/{id}/pages/{page_id}/assignee and carried unchanged through workflow moves unless the move says otherwise.
node_approved_atWhen the page was last signed off. A page carrying this and standing at draft has been edited since — the copy that was approved is still kept, and GET /sites/{id}/pages/{page_id}/approved-content answers with it.
node_approved_byWho signed it off. Null on pages approved before this was recorded.
node_open_commentsUnresolved comment threads on this page. Only sent when a site is fetched whole.
Delete a page
Removes the page and everything filed under it.
path Parameters
idpage_idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Delete a page › Responses
Success. No content.
Update a page
Applies the node_-prefixed fields present in the body. Writing content directly is allowed — generation is one way to fill a page, not the only one. Writing node_content into a page that had been approved withdraws that sign-off: the page returns to draft and re-enters the site's review at the first step, since nobody has read the text it now holds. The approved copy is kept — POST /sites/{id}/pages/{page_id}/restore-approved puts it back.
A node_slug that another page under the same parent already answers to is refused with 409: the address was named rather than derived, so numbering it off would publish the page somewhere other than where it was asked for.
node_type moves a node between page and folder; pages under it stay either way. A page that holds content is refused as a folder with 409. Discard the content first.
path Parameters
idpage_idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Update a page › Request Body optional
node_namenode_slugnode_typenode_instructionsnode_instructions_paramsnode_children_instructionsnode_componentnode_ordernode_contentnode_content_summarynode_metadatanode_reviewUpdate a page › Responses
Success
node_idnode_creatednode_updatednode_sitemapnode_parentnode_ordernode_typeA page holds content and may have pages under it; a folder only holds pages. A page with content cannot become a folder.
node_namenode_slugnode_instructionsnode_instructions_paramsnode_children_instructionsnode_componentnode_contentnode_content_summarynode_content_statusnode_state_itemnode_threadsnode_metadataThe page's SEO meta data. Filled by generation from the draft copy and editable; kept out of node_content so it is never pushed to a connected CMS.
Reviewer material written alongside the copy: the review agent's account of the page when the site names one (review), and the editor agent's pass when the site names an editor (editor). Kept out of node_content so it never reaches a CMS. Left out of the site tree; read it from one page, or include=review.
node_assigneeWho the page currently waits on. Set freely via PUT /sites/{id}/pages/{page_id}/assignee and carried unchanged through workflow moves unless the move says otherwise.
node_approved_atWhen the page was last signed off. A page carrying this and standing at draft has been edited since — the copy that was approved is still kept, and GET /sites/{id}/pages/{page_id}/approved-content answers with it.
node_approved_byWho signed it off. Null on pages approved before this was recorded.
node_open_commentsUnresolved comment threads on this page. Only sent when a site is fetched whole.
Write image alt text
Looks at one image and writes alt text for it in the context of this page: the site, the page, the field it sits in (fieldPath) and the copy beside it. The image is either a file of the workspace (file_id, which the caller must be able to read) or a public web address (url, fetched once and never stored). Returns the text for review; nothing is saved until the page is. currentContent lets an editor pass unsaved copy and instruction steers this one run.
path Parameters
idpage_idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Write image alt text › Request Body optional
agentIdfile_idurlfieldPathcurrentContentinstructionWrite image alt text › Responses
Success
Get the approved copy
The content as it stood when the page was last signed off, which is not what node_content holds once anyone has edited it. Editing an approved page returns it to draft and keeps this, so it answers both "what changed since approval" and "what does the live website still show". 404 while nothing about the page has ever been approved — node_approved_at on the page says which it is.
path Parameters
idpage_idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Get the approved copy › Responses
Success
Assign a page
Hands the page to a member of the workspace, or takes it from them with assignee_id: null. Assignment says who the page waits on and nothing more — it does not move the page, and workflow moves do not change it. The new assignee is notified.
Requires either content:edit or content:review.
path Parameters
idpage_idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Assign a page › Responses
Success
node_idnode_creatednode_updatednode_sitemapnode_parentnode_ordernode_typeA page holds content and may have pages under it; a folder only holds pages. A page with content cannot become a folder.
node_namenode_slugnode_instructionsnode_instructions_paramsnode_children_instructionsnode_componentnode_contentnode_content_summarynode_content_statusnode_state_itemnode_threadsnode_metadataThe page's SEO meta data. Filled by generation from the draft copy and editable; kept out of node_content so it is never pushed to a connected CMS.
Reviewer material written alongside the copy: the review agent's account of the page when the site names one (review), and the editor agent's pass when the site names an editor (editor). Kept out of node_content so it never reaches a CMS. Left out of the site tree; read it from one page, or include=review.
node_assigneeWho the page currently waits on. Set freely via PUT /sites/{id}/pages/{page_id}/assignee and carried unchanged through workflow moves unless the move says otherwise.
node_approved_atWhen the page was last signed off. A page carrying this and standing at draft has been edited since — the copy that was approved is still kept, and GET /sites/{id}/pages/{page_id}/approved-content answers with it.
node_approved_byWho signed it off. Null on pages approved before this was recorded.
node_open_commentsUnresolved comment threads on this page. Only sent when a site is fetched whole.
List page comments
Returns every comment on the page in one response, threads and replies together, oldest first — group them by cmt_parent rather than requesting a thread at a time. A thread names what it is about through cmt_field_path, an array of field keys and array indices addressing one part of the record (["body", 0, "headline"]); a thread with no field path is about the page as a whole. Filter with status=open or status=resolved; resolution belongs to the thread, so replies are filtered with the thread they are in.
path Parameters
idpage_idquery Parameters
cmt_parentcursor^[A-Za-z0-9_-]+$limitoffsetsort^-?[a-z0-9_]+$statusHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
List page comments › Responses
Success
has_moreWhether more rows exist past this page.
next_cursorPass back as cursor for the next page. Null on the last page.
Post a comment
Starts a thread, or replies to one when cmt_parent is given. A reply inherits the anchor and the resolved state of the thread it joins, so it takes no cmt_field_path of its own. A path is stored as sent except that array indices are normalized to numbers, so ["body", "0"] and ["body", 0] are one anchor rather than two. Anyone named in cmt_mentions is notified, as is everyone already in the thread.
path Parameters
idpage_idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Post a comment › Request Body
cmt_bodycmt_field_pathcmt_parentPost a comment › Responses
Created
cmt_idcmt_createdcmt_updatedcmt_entity_typecmt_entitycmt_field_pathField keys and array indices addressing one part of the record, from its root. Null means the record as a whole.
cmt_parentcmt_usercmt_bodyUsers named in the body, as the directory spells them.
cmt_resolved_atcmt_resolved_bycmt_authorWho wrote it — enough to attribute and draw an avatar without a second request.
cmt_resolved_by_nameDisplay name of whoever resolved the thread. Null while it is open.
Delete a comment
Soft delete, by the author or by anyone holding content:comment:delete. Deleting the first comment of a thread removes its replies with it.
path Parameters
comment_ididpage_idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Delete a comment › Responses
Success. No content.
Edit a comment
Rewrites the text of your own comment. Moderating someone else's means deleting it, not rewriting what they said.
path Parameters
comment_ididpage_idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Edit a comment › Responses
Success
cmt_idcmt_createdcmt_updatedcmt_entity_typecmt_entitycmt_field_pathField keys and array indices addressing one part of the record, from its root. Null means the record as a whole.
cmt_parentcmt_usercmt_bodyUsers named in the body, as the directory spells them.
cmt_resolved_atcmt_resolved_bycmt_authorWho wrote it — enough to attribute and draw an avatar without a second request.
cmt_resolved_by_nameDisplay name of whoever resolved the thread. Null while it is open.
Reopen a thread
Puts a resolved thread back on the open list.
path Parameters
comment_ididpage_idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Reopen a thread › Responses
Success
cmt_idcmt_createdcmt_updatedcmt_entity_typecmt_entitycmt_field_pathField keys and array indices addressing one part of the record, from its root. Null means the record as a whole.
cmt_parentcmt_usercmt_bodyUsers named in the body, as the directory spells them.
cmt_resolved_atcmt_resolved_bycmt_authorWho wrote it — enough to attribute and draw an avatar without a second request.
cmt_resolved_by_nameDisplay name of whoever resolved the thread. Null while it is open.
Resolve a thread
Marks the thread settled and drops it out of the open list. Nothing is deleted — a resolved thread is the record of the review, and reopening restores it.
path Parameters
comment_ididpage_idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Resolve a thread › Responses
Success
cmt_idcmt_createdcmt_updatedcmt_entity_typecmt_entitycmt_field_pathField keys and array indices addressing one part of the record, from its root. Null means the record as a whole.
cmt_parentcmt_usercmt_bodyUsers named in the body, as the directory spells them.
cmt_resolved_atcmt_resolved_bycmt_authorWho wrote it — enough to attribute and draw an avatar without a second request.
cmt_resolved_by_nameDisplay name of whoever resolved the thread. Null while it is open.
Generate one page
Queues this page to be written from its brief and the site's knowledge, into the shape its content schema declares. Returns once the work is queued, not when the copy is ready: the page sits in generating and moves to draft when it lands.
The writer is the agent this page names in node_instructions_params.contentAgentId, falling back to agentId and then to the site's agent.
path Parameters
idpage_idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Generate one page › Responses
Success
Generate one field
Rewrites one field of a page's structured content, optionally against an instruction, leaving the rest untouched. The page must already have structured content to regenerate a field from.
path Parameters
idpage_idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Generate one field › Request Body
fieldPathagentIdproofreadreviewcurrentContentinstructionGenerate one field › Responses
Success
Generate a branch
The same as generating the whole site, scoped to one branch — every empty page beneath this node, however deeply nested. The node may be a folder or a page that has pages under it.
path Parameters
idpage_idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Generate a branch › Responses
Success
Generate page meta data
Writes an SEO meta title (at most 60 characters) and meta description (at most 150) from the page's draft copy. The content template's metaTitleInstructions and metaDescriptionInstructions replace the platform defaults (see GET /content-schemas/meta-defaults); the page-level notes are appended. Returns the pair for review; nothing is saved until the page is. The page must hold draft content to write from. currentContent lets an editor pass unsaved copy; instructions overrides the saved page-level meta instructions for this one run.
path Parameters
idpage_idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Generate page meta data › Request Body optional
currentContentinstructionsGenerate page meta data › Responses
Success
Cancel page generation
Frees a page stuck in generating, back to draft where it still holds copy and empty where it never had any, so it can be edited again. The queued job is not recalled: copy that lands later is still saved. Answers 409 when the page is not generating.
Requires either content:edit or content:generate.
path Parameters
idpage_idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Cancel page generation › Responses
Success
node_idnode_creatednode_updatednode_sitemapnode_parentnode_ordernode_typeA page holds content and may have pages under it; a folder only holds pages. A page with content cannot become a folder.
node_namenode_slugnode_instructionsnode_instructions_paramsnode_children_instructionsnode_componentnode_contentnode_content_summarynode_content_statusnode_state_itemnode_threadsnode_metadataThe page's SEO meta data. Filled by generation from the draft copy and editable; kept out of node_content so it is never pushed to a connected CMS.
Reviewer material written alongside the copy: the review agent's account of the page when the site names one (review), and the editor agent's pass when the site names an editor (editor). Kept out of node_content so it never reaches a CMS. Left out of the site tree; read it from one page, or include=review.
node_assigneeWho the page currently waits on. Set freely via PUT /sites/{id}/pages/{page_id}/assignee and carried unchanged through workflow moves unless the move says otherwise.
node_approved_atWhen the page was last signed off. A page carrying this and standing at draft has been edited since — the copy that was approved is still kept, and GET /sites/{id}/pages/{page_id}/approved-content answers with it.
node_approved_byWho signed it off. Null on pages approved before this was recorded.
node_open_commentsUnresolved comment threads on this page. Only sent when a site is fetched whole.
List page history
What has happened to a page, newest first: every workflow move, who made it, and the note they left, and every write into its copy with what made it in history_ref.source. Page content itself is omitted — this answers how a page got where it is, not what it said at each point, which GET /sites/{id}/pages/{page_id}/versions does.
path Parameters
idpage_idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
List page history › Responses
Success
Proofread a page
Runs the site's editor agent over the page's saved copy, in the background, without regenerating it. The editor reads the brief the page was written to and its own guideline knowledge, corrects wording that breaks them, and lists each change in editor.changes with the rule it serves. The structure is never touched: fields, blocks, images and links come back exactly as saved, only words change. Answers 202 with the page marked node_metadata.proofreading; the copy is not touched, so the page keeps its status and stays editable. The record lands under node_review.editor when the editor is done, or the failure under node_metadata.lastProofreadError, and either clears the mark; the corrected copy waits on the record as proposed and reaches node_content only when a caller applies it, marking each change applied. A page already being proofread is answered as it stands; one that is generating is refused with 409. The editor is the agent this page names in node_instructions_params.editorAgentId, then agentId, then the site's; a page that opted out of the automatic pass is still proofread here, since the request is the opt-in. 400 when no editor is set. instructions stands in for the page's editor notes for this one run.
path Parameters
idpage_idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Proofread a page › Responses
Success
node_idnode_creatednode_updatednode_sitemapnode_parentnode_ordernode_typeA page holds content and may have pages under it; a folder only holds pages. A page with content cannot become a folder.
node_namenode_slugnode_instructionsnode_instructions_paramsnode_children_instructionsnode_componentnode_contentnode_content_summarynode_content_statusnode_state_itemnode_threadsnode_metadataThe page's SEO meta data. Filled by generation from the draft copy and editable; kept out of node_content so it is never pushed to a connected CMS.
Reviewer material written alongside the copy: the review agent's account of the page when the site names one (review), and the editor agent's pass when the site names an editor (editor). Kept out of node_content so it never reaches a CMS. Left out of the site tree; read it from one page, or include=review.
node_assigneeWho the page currently waits on. Set freely via PUT /sites/{id}/pages/{page_id}/assignee and carried unchanged through workflow moves unless the move says otherwise.
node_approved_atWhen the page was last signed off. A page carrying this and standing at draft has been edited since — the copy that was approved is still kept, and GET /sites/{id}/pages/{page_id}/approved-content answers with it.
node_approved_byWho signed it off. Null on pages approved before this was recorded.
node_open_commentsUnresolved comment threads on this page. Only sent when a site is fetched whole.
Upload a reference doc
Takes one document as a multipart upload (files field) and stores it as reference material for the page. Put the returned file_id into an entry of the page's node_instructions_params.referenceUrls ({fileId, name, note?}) and generation reads the document the way it reads a reference link. PDF, Word, PowerPoint, Excel, CSV, plain text and Markdown are accepted.
path Parameters
idpage_idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Upload a reference doc › Responses
Created
file_idfile_namefile_typefile_sizeDelete a reference doc
Removes a reference document that was uploaded for this page, bytes included. Only files stored as this page's reference material can be addressed here; the matching node_instructions_params.referenceUrls entry is the caller's to clean up.
path Parameters
file_ididpage_idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Delete a reference doc › Responses
Success. No content.
Reorder a page
Moves a page to a position under a parent, carrying everything nested under it. node_parent must name a page of the same site outside the moved page's own subtree; anything else is refused. Both affected sibling groups are renumbered contiguously from 0, so a tree that arrived with gapped or duplicated ordering comes back consistent.
A move already rewrites the page's address, so a slug the destination has spoken for is numbered off rather than refused. Read node_slug off the response for what the page ended up at.
path Parameters
idpage_idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Reorder a page › Responses
Success
Restore approved copy
Discards the edits made since the last sign-off and puts that copy back, returning the page to approved. Nothing unreviewed reaches a website this way — the content written is exactly what was approved — which is why it takes content:edit rather than a review permission. Where the site is under review this ends a review in progress: the page leaves whichever step it had reached and lands back on the final one.
path Parameters
idpage_idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Restore approved copy › Responses
Success
node_idnode_creatednode_updatednode_sitemapnode_parentnode_ordernode_typeA page holds content and may have pages under it; a folder only holds pages. A page with content cannot become a folder.
node_namenode_slugnode_instructionsnode_instructions_paramsnode_children_instructionsnode_componentnode_contentnode_content_summarynode_content_statusnode_state_itemnode_threadsnode_metadataThe page's SEO meta data. Filled by generation from the draft copy and editable; kept out of node_content so it is never pushed to a connected CMS.
Reviewer material written alongside the copy: the review agent's account of the page when the site names one (review), and the editor agent's pass when the site names an editor (editor). Kept out of node_content so it never reaches a CMS. Left out of the site tree; read it from one page, or include=review.
node_assigneeWho the page currently waits on. Set freely via PUT /sites/{id}/pages/{page_id}/assignee and carried unchanged through workflow moves unless the move says otherwise.
node_approved_atWhen the page was last signed off. A page carrying this and standing at draft has been edited since — the copy that was approved is still kept, and GET /sites/{id}/pages/{page_id}/approved-content answers with it.
node_approved_byWho signed it off. Null on pages approved before this was recorded.
node_open_commentsUnresolved comment threads on this page. Only sent when a site is fetched whole.
Write a page review
Writes the page's review from its saved copy, in the background: where each fact, figure and claim came from, the search terms and questions the page covers, and the trademarks and acronyms it uses, following the site's review instructions (see GET /sites/{id}/generation-defaults) with the page's notes appended. The writer is the review agent this page names in node_instructions_params.reviewAgentId, then agentId, then the site's; any of those may be the word copywriter, meaning the agent that writes the page reviews it too, which is the one that can trace a fact to its source. Generation writes the review on its own when the site names a review agent; this is for a page written before that, or edited since. Answers 202 with the page marked node_metadata.reviewing; the copy is not touched, so the page keeps its status and stays editable. The review lands under node_review.review when the agent is done, or the failure under node_metadata.lastReviewError, and either clears the mark. A page already being reviewed is answered as it stands. 400 when no review agent is set. instructions stands in for the page's saved review notes for this one run.
path Parameters
idpage_idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Write a page review › Request Body optional
agentIdinstructionsWrite a page review › Responses
Success
node_idnode_creatednode_updatednode_sitemapnode_parentnode_ordernode_typeA page holds content and may have pages under it; a folder only holds pages. A page with content cannot become a folder.
node_namenode_slugnode_instructionsnode_instructions_paramsnode_children_instructionsnode_componentnode_contentnode_content_summarynode_content_statusnode_state_itemnode_threadsnode_metadataThe page's SEO meta data. Filled by generation from the draft copy and editable; kept out of node_content so it is never pushed to a connected CMS.
Reviewer material written alongside the copy: the review agent's account of the page when the site names one (review), and the editor agent's pass when the site names an editor (editor). Kept out of node_content so it never reaches a CMS. Left out of the site tree; read it from one page, or include=review.
node_assigneeWho the page currently waits on. Set freely via PUT /sites/{id}/pages/{page_id}/assignee and carried unchanged through workflow moves unless the move says otherwise.
node_approved_atWhen the page was last signed off. A page carrying this and standing at draft has been edited since — the copy that was approved is still kept, and GET /sites/{id}/pages/{page_id}/approved-content answers with it.
node_approved_byWho signed it off. Null on pages approved before this was recorded.
node_open_commentsUnresolved comment threads on this page. Only sent when a site is fetched whole.
Set the page status
Sets the status by hand: empty, draft, approved or pushed, from any status but generating, which belongs to the worker until the copy lands or the run is cancelled. A page needs no copy to be approved; marking one approved as it stands is how a page that wants no new text is signed off. empty discards whatever content the page holds. Approval is what marks content ready to leave the platform, so it is the gate a CMS push checks. Where the site has an approval workflow, both ends of the sign-off are that workflow's moves to make and this endpoint answers 409 for either: approving (or publishing) a page, and withdrawing an approval by sending it back to draft or empty.
path Parameters
idpage_idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Set the page status › Request Body
statusSet the page status › Responses
Success
List page versions
Every version of the page's copy on record, newest first, each reduced to what changed against the one before it: the name, the slug, the content and the metadata, with the status and review step where the write moved them. history_ref.source says what wrote it: a save, a generation, a proofread, a CMS pull or merge, an import, a restore, a discard. The state a page held before its first recorded write is kept once, unattributed, as the oldest entry, so the earliest diff is against what the page really held. At most 100 entries come back.
path Parameters
idpage_idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
List page versions › Responses
Success
history_idhistory_timestamphistory_userhistory_opThe kind of change — insert, update or delete.
One entry per field that changed, keyed by column name.
history_refWhat the writer noted about the change rather than the columns: the action that made it, or the reason it was made.
Revert page to version
Puts the content and metadata of a version back on the page, and records the restore as a version of its own so it can be undone in turn. The name and slug a version carried are shown but not written back. Restoring is an edit: the page returns to draft, an approval it held is withdrawn and, under review, it re-enters at the flow's first step. The approved copy is untouched. 409 while the page is being generated.
path Parameters
history_ididpage_idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Revert page to version › Responses
Success
restoredColumns written back, by name.
Columns the version carried that were deliberately not written.
Generate pages
The same as generating the whole site, scoped to the pages named in page_ids — a hand-picked set rather than a branch. Pages already holding content, and pages with no instructions of their own or inherited, sit the run out rather than failing it; queued reports how many were taken.
path Parameters
idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Generate pages › Request Body
page_idsagentIdproofreadreviewGenerate pages › Responses
Success
Move pages
Puts every page named in page_ids under node_parent (top level when null), after whatever is already there and in the order they hold in the tree. A page whose ancestor is also named travels with it and keeps its place, so a selected branch arrives intact. node_parent must be a page of the same site outside every moved subtree; anything else is refused.
A move already rewrites a page's address, so a slug the destination has spoken for is numbered off rather than refused; renamedCount says how many were. Pages the site does not hold are reported back rather than failing the rest.
path Parameters
idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Move pages › Responses
Success
Proofread pages
Runs the editor agent over the pages named in page_ids, in the background, the way generation runs: each page sits in generating while the editor works and returns to draft with its copy corrected and the editor's record on its review. Corrections are applied outright rather than proposed, so only draft pages holding copy are taken; empty, generating, approved and published pages sit the run out, as do folders. queued reports how many were taken. The editor is each page's own, then agentId, then the site's; a page that opted out of the automatic pass is still proofread, since the request is the opt-in. 400 when none of the pages has an editor.
path Parameters
idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Proofread pages › Responses
Success
Review pages
Writes the review of each page named in page_ids, in the background, one task per page. Unlike generation or proofreading the copy is not touched, so a page keeps its status and stays editable; node_metadata.reviewing marks it until the review lands under node_review.review. Pages with copy (draft, approved or published) are taken; empty and generating pages, folders and pages already being reviewed sit the run out. The reviewer is each page's own, then agentId, then the site's, with copywriter meaning whoever writes that page. queued reports how many were taken; 400 when none of the pages has a review agent. Each review costs a full writer-sized call.
path Parameters
idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Review pages › Responses
Success
Set page statuses
Sets the status of every page named in page_ids under the rules of setting one page's status. A page already holding it counts as done. One that is generating, or whose move the site's approval workflow reserves, is reported back with the reason rather than failing the rest, as is a page the site does not hold.
path Parameters
idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Set page statuses › Request Body
page_idsstatusSet page statuses › Responses
Success
Move pages
Puts pages on a step of the site's approval workflow — any step, in either direction, from wherever they stand. The step's own gate decides who may set it (403 when it does not name the caller); pages the caller cannot write are reported per page rather than refusing the rest. Reaching the final step is what marks a page approved; leaving it withdraws that. The note is kept on each page's history and sent to whoever the page waits on — its assignee, or whoever can act on it next while unassigned. Passing assignee_id hands the pages over in the same move (null clears); leaving it out keeps every page with whoever holds it.
path Parameters
idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Move pages › Request Body
page_idstarget_state_idnoteassignee_idMove pages › Responses
Success
Update page settings
Writes generation settings onto every page named in page_ids at once: content instructions, content template, audience profiles, tone and copywriter agent. Only the fields present in the body are touched, and null (or an empty list) clears a field, returning the page to what it inherits from its folder or the site. Everything else a page carries, its content and review state included, is left alone. Pages the site does not hold are reported back rather than failing the rest.
path Parameters
idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Update page settings › Request Body
page_idsnode_instructionstemplate_idaudience_profile_idstone_idcontent_agent_ideditor_agent_ideditor_skippedreview_agent_idUpdate page settings › Responses
Success
Reset stuck pages
Frees any page left in generating for more than 15 minutes, back to draft where it still holds copy and empty where it never had any. A safety valve for the case where the background job exhausted its retries and the page would otherwise never come back.
path Parameters
idHeaders
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Reset stuck pages › Responses
Success
Discover pages
Given a URL, finds its pages: robots.txt, then the well-known sitemap paths (expanding sitemap indexes), then a homepage navigation crawl as a fallback. Runs server-side because a browser cannot fetch another origin. Returns the URLs it found without writing anything.
Headers
X-Tenant-IdThe workspace this request acts in. Every row the API returns is isolated to it at the database layer by row-level security.
Discover pages › Responses
Success
