Structure tools change the shape of your site rather than its words: which page types and search indexes exist, what the navigation tree looks like, which labels group your content, what lives at which URL, what your funnel stages are called, which phrases turn into links automatically, and the company’s own site-wide settings. Thirty-four tools cover that surface. Every write lands in a draft state and waits to be published.
All thirty-four tools at a glance
| Tool | Reads or writes | Also in the assistant? |
|---|---|---|
list_page_types | Read | Yes |
get_page_type | Read | No |
create_page_type | Write — confirm-gated | No |
update_page_type | Write | No |
list_search_indexes | Read | No |
create_search_index | Write | No |
update_search_index | Write | No |
list_menus | Read | Yes |
get_menu | Read | Yes |
create_menu | Write | Yes |
add_menu_item | Write — one item or subtree | Yes |
set_menu_item_fields | Write — one item | Yes |
move_menu_item | Write — one item | Yes |
reorder_menu_items | Write — one parent’s children | Yes |
remove_menu_item | Write — one item | Yes |
update_menu_fields | Write — the menu record only | Yes |
list_labels | Read | No |
get_label | Read | No |
create_label | Write | No |
update_label | Write | No |
list_label_users | Read | No |
list_paths | Read | No |
resolve_path | Read | No |
create_path_alias | Write | No |
delete_path_alias | Delete — confirm-gated | No |
list_broken_links | Read | No |
list_journey_stages | Read | No |
update_journey_stage | Write — name and description only | No |
list_autolinks | Read | No |
create_autolink | Write | No |
update_autolink | Write | No |
reorder_autolinks | Write | No |
update_company_settings | Write | No |
upload_company_image | Write | No |
Page types
Four tools. A page type is the template-plus-schema that decides what fields a page has and where its URLs live, so the two reads are things a model should call before it creates or edits a page, not after.
list_page_types
GET /api/ai/context/page_types · Returns { pageTypes: [{ pageTypeId, slug, name, type, editorFormattingOptions, formattingEnforced }] } · Also available in the Leed Assistant
Takes no parameters. type is one of documentation, api or posts, and is absent for a custom type. The tool’s own description instructs the model to call this before choosing a pageTypeId, and to ask you rather than guess when more than one page type plausibly matches — so “put this in the blog” resolves to the posts-kind entry and “add it to the docs” to the documentation-kind one.
get_page_type
GET /api/pagetypes/:pageTypeId · Returns { pageType } · MCP only
| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
pageTypeId | string | Yes | — | From list_page_types |
Returns the full page type, including its field schema, its required-field configuration and the same editorFormattingOptions and formattingEnforced that list_page_types reports. This is the call that tells a model that documentation pages must carry a summary before they can be published, so it is worth making before a bulk authoring run rather than discovering the requirement one rejected publish at a time. Read it before update_page_type too.
create_page_type
POST /api/pagetypes · Returns { pageType } · MCP only · Confirm-gated, local mode
Creates a posts or documentation page type; type is required, and API page types are created only by the OpenAPI import. Fields you leave out get the same per-kind defaults the Settings screen uses. A documentation page type gets a new left navigation menu of its own, unless documentationConfiguration.menus.left names an existing menu to bind instead — one that is not another type’s navigation or a company site menu, has no folder carrying a link and no page nested under a page. Creating a documentation page type also creates a search index for it. The first call returns a preview; nothing is created until the same call is repeated with confirm: true. There is no delete tool, so only a person in the CMS can remove a page type again.
update_page_type
PUT /api/pagetypes/:pageTypeId · Returns { pageType } · MCP only
Send only the fields to change. Nested objects — documentationConfiguration, requiredFields, editorFormattingOptions, ogCard and paging — are merged field by field, so naming one field leaves its siblings alone; send null to remove a setting. Arrays such as aliases and labels are replaced whole. The rules both write tools share:
- The kind (
type) is fixed at creation and cannot change. slugmust be unique across page types (409), and a slug-locked page type’s slug cannot change (403).- A setting that does not apply to the page type’s kind is rejected with a
400rather than ignored. documentationConfiguration.openAPI,tabGroupsandhighlighterare company-level only and are dropped if sent here — change them withupdate_company_settings.lockedandnavMenuLockedcan be set only by an Administrator or Content Publisher, and a custom theme or font needs Starter or above (402).
Read the company defaults a documentation page type inherits on Configuring a Page Type.
Search indexes
Three tools. A search index decides which page types a site search covers. A documentation or API page type usually has one already, because creating the page type creates it.
list_search_indexes
GET /api/searchindexes · Returns { searchIndexes, isDirty } · MCP only
Takes no parameters. Each index carries its id, name and pageTypeIds.
create_search_index
POST /api/searchindexes · Returns { searchIndex } · MCP only
| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
name | string | Yes | — | Unique among the company’s live indexes; a duplicate is a 400 |
pageTypeIds | string[] | No | [] | Each documentation or API type named that has no index yet is bound to this one |
The id is generated by the server. Requires pagetype:write.
update_search_index
PUT /api/searchindexes/:searchIndexId · Returns { searchIndex } · MCP only
Takes searchIndexId plus name and/or pageTypeIds. pageTypeIds replaces the list rather than appending, so read it with list_search_indexes and send the full set. There is no delete tool for search indexes.
Menus
Nine tools: two reads, a create, and six writes that each change one thing. After a menu is created it changes only through those six — there is no whole-tree replace.
list_menus
GET /api/menu · Returns { menus } · Also available in the Leed Assistant
Takes no parameters. Each row carries menuId, name, isDirty and an isDocsMenu flag saying whether that menu is bound to a documentation page type as its left navigation — which matters, because the docs-menu rules below apply only to those.
get_menu
GET /api/menu/:menuId · Returns { menu, isDocsMenu } · Also available in the Leed Assistant
| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
menuId | string | Yes | — | From list_menus |
Returns the menu’s draft state — the tree as it currently stands, including unpublished edits, with every item’s menuItemId — and menu.revision, an opaque token. Every menu write takes it as baseRevision, and each write returns the new revision so calls chain without a re-read. A stale revision is rejected with 409 concurrent_update rather than overwriting a change somebody else made in the meantime.
create_menu
POST /api/menu · Returns { menu } · Also available in the Leed Assistant
| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
name | string | Yes | — | Must not be blank — see the warning about renaming |
items | array of menu items | No | [] | The starting tree, in the item shape below |
description | string | No | — | Internal description only |
expanded | boolean | No | — | Whether the menu renders expanded by default |
Needs menu:write. The menu is a draft until it is bound to a page type or the company navigation and published.
Editing a menu one item at a time
Each of these posts a single op to POST /api/menu/:menuId/items/ops (or, for update_menu_fields, PATCH /api/menu/:menuId). All take menuId and baseRevision. The five item tools return { menuId, revision, isDocsMenu, applied } rather than the tree; update_menu_fields returns { menu, isDocsMenu }.
| Tool | Arguments | What it does |
|---|---|---|
add_menu_item | parentItemId (omit or null for top level), index, item | Adds one item, which may carry a whole subtree in submenu. A minted id comes back in applied[0].menuItemId |
set_menu_item_fields | menuItemId, fields | Changes the named fields of one item; null clears an optional one |
move_menu_item | menuItemId, parentItemId (required; null is the top level), index | Moves an item or a whole folder, at any depth, in one call |
reorder_menu_items | parentItemId, order | Reorders one parent’s children; order must list exactly its current child ids |
remove_menu_item | menuItemId, children | Removes one item. On an item with children, children must be promote or cascade |
update_menu_fields | name, description, expanded | Changes the menu record’s own fields, never its items |
A menu item is a node, and a node with children is a folder. The shape is the same either way:
| Field | Type | Meaning | Docs-menu constraint |
|---|---|---|---|
menuItemId | string, optional | Stable id for the node | Minted for you when omitted |
name | string, required | The visible label in the nav | For a folder, this is what gets slugified into the URL segment |
title | string, default "" | The hover tooltip | Never appears in a URL |
href | string, default "" | pageid:<pageId> for a page; "" for a folder | Blanked automatically on folders — see below |
target | string, default "" | Link target; "" for a normal in-site link | — |
submenu | array, optional | Child items | A page item may not have children |
icon + iconStyle | string, optional | Font Awesome icon on the item | Folders carry no icon |
expanded | boolean, optional | Folder open by default | — |
disabled | boolean, optional | Renders the item grayed out | — |
description | string, optional | Internal note | — |
Unknown keys are refused.
These rules are enforced by the route rather than by convention:
- A folder can never be a link. On a docs-bound menu, the
hrefof any item that has children is blanked. Sending a folder with anhrefis not an error — the href is simply discarded. - A page item may not have children. Sending one is a
400with the message “A docs page item cannot have children — place pages inside folders instead”. - A documentation page may appear in exactly one docs left-nav menu, once. A duplicate inside the tree, or a page already carried by another docs menu, is a
409. - No change may leave a page of a docs or API set out of its menu. Removing a page’s entry, or cascading a folder that holds pages, is refused — move the page, use
children: promote, or delete the page itself. menuItemIds are unique at every depth. A tree carrying a duplicate is a400naming it, and a menu that already holds duplicates cannot be saved until they are removed.
There is no order field on a menu item. Order is array order, top to bottom, at every level.
Adding a folder holding one page is a single add_menu_item call, with the page inside the folder’s submenu. menuId and baseRevision travel as their own arguments alongside the item:
{
"menuId": "docs-left-nav",
"baseRevision": "<revision from get_menu>",
"parentItemId": null,
"index": 0,
"item": {
"name": "Getting Started",
"expanded": true,
"submenu": [
{
"name": "Install the CLI",
"href": "pageid:9f1c0f5e-6b1a-4b2e-9a77-0c2d5a1f8e10",
"icon": "fa-solid fa-download"
}
]
}
}Publishing a menu
There is no per-menu publish tool. A menu edit appears in list_pending_changes as a menus item, and goes live through publish_pending_changes — confirm-gated, and documented on MCP Tools: Publishing, Distribution and Users — or from the CMS. If you want the same tree edited by hand instead, Building and Editing a Menu is the screen, and Left Navigation Menu covers the docs-specific rules from the editor’s side.
Labels
Five tools. A label does three jobs at once in Leed — a taxonomy, a series, and an RBAC boundary — so read Labels and Series before reshaping them programmatically.
list_labels
GET /api/labels · Returns { labels } · MCP only
Takes no parameters. Publish state is per label: each one carries its own needsPublish, along with locked.
get_label
GET /api/labels/:labelId · Returns { label } · MCP only
| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
labelId | string | Yes | — | From list_labels |
update_label
PUT /api/labels/:labelId · Returns { label } · MCP only
| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
labelId | string | Yes | — | Which label to edit |
name | string | No | unchanged | Display name |
description | string | No | unchanged | — |
visible | boolean | No | unchanged | Whether the label is shown on the public site |
isSeries | boolean | No | unchanged | Turns the label into a multi-part series |
series | multipart or announcement | No | unchanged | Only meaningful when isSeries is true |
slug | string | No | unchanged | The label’s URL segment |
Only the fields you send change. A locked label is read-only and any update is a 403; a slug another label already uses is a 409; and series requires isSeries: true, in the call or already stored. Updating needs Content Publisher or above, or a Publish override on the label.
create_label
POST /api/labels · Returns { label } · MCP only
Takes the same fields as update_label, with name required and not blank. A slug must be unique (409), and series requires isSeries: true in the same call (400). Needs Content Publisher or above (label:write). There is no delete_label on the MCP surface.
list_label_users
GET /api/labels/:labelId/users · Returns { users: [{ userId, role }] } · MCP only
| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
labelId | string | Yes | — | Which label to inspect |
URL paths
Five tools that answer “what lives where”: three reads — list_paths, resolve_path and the broken-link report — plus alias create and delete.
list_paths
GET /api/paths · Returns { paths } · MCP only
| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
q | string | No | — | Free-text filter over the path text |
t | string | No | all types | Comma-separated list of row types, e.g. alias or current,next |
Each row carries id, pageId, path, type, title, createdAt and a cmsManaged flag. The type is the field that matters:
| Type | Meaning | Created by |
|---|---|---|
current | The live URL of a published page | Publishing a page |
next | A placeholder reserving a URL for a page that is not published yet | Creating or moving an unpublished page, or a staged folder move |
alias | A custom path that redirects to a page | Adding an alias in the CMS, or a page whose URL changed |
A page that has moved typically owns several rows: one current at its new URL and one alias per old URL. URL Paths and Slugs is the full model.
resolve_path
GET /api/paths/lookup · Returns { pageId } (or { pageId: null }) · MCP only
| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
url | string | Yes | — | A relative path or an absolute URL on your own domain |
The reverse of list_paths, and the tool a model should reach for whenever it has a URL and needs a page. Anything that is not on your domain returns null rather than an error. Use it before writing a cross-link: a body should carry pageid:<id>, never a literal path, so the link survives the page moving.
create_path_alias
POST /api/paths · Returns { path } · MCP only
Creates an alias row — a custom path that redirects to a page. The server normalizes the path before storing it, so use the path in the response, which is what a browser will match. A path another row already holds is a 400 (Path already taken). current and next rows cannot be created here. Requires path:write.
delete_path_alias
DELETE /api/paths · MCP only · Confirm-gated, local mode
Deletes one row by the numeric id that list_paths reports. Only an alias row, or the next row of a deleted page, can be deleted; any other row is a 409. Deleting an alias breaks any live inbound URL pointing at it. Requires path:delete.
list_broken_links
GET /api/brokenlinks · Returns { brokenLinks: { checkedAt, links } } · MCP only
The last stored report of broken external links, keyed by pageId, with each link’s final status, message, latency and redirect chain. It is not a fresh check and there is no way to request one, so read checkedAt before telling anyone a link is broken now. A workspace that has never been checked gets a 404. Requires company:read.
Journey stages
Journey stages are the funnel bands Engage scores contacts into. Two tools, and the write is deliberately narrow.
list_journey_stages
GET /api/journeys · Returns { journeys } · MCP only
Takes no parameters.
update_journey_stage
PUT /api/journeys/:journeyId · Returns { journeys } — the whole list, re-saved · MCP only
| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
journeyId | string | Yes | — | Which stage to edit |
name | string | No | unchanged | Display name of the stage |
description | string | No | unchanged | — |
The patch is merged into the matching stage; an unknown journeyId is a 404. The set of stages is fixed — there is no tool to add one, remove one or reorder them, over MCP or in the assistant. Renaming a stage does not re-score anybody. Journey Stages covers what the stages mean and why the Engage journey is not editable.
Autolinks
An autolink turns a phrase into a link to a page everywhere that phrase appears, at build time. Four tools; the order they are applied in decides which of two overlapping phrases wins on a page.
list_autolinks
GET /api/autolinks · Returns { autolinks, orderNeedsPublish } · MCP only
Optional filters: needsPublish, pageId and unlinked. Rows come back in application order, each carrying its phrase, target page, needsPublish and lastBuildPageCount / lastBuildAt — how many pages the phrase linked on the last public build. null means no statistics since the last edit; 0 means the phrase matched nothing or its target was not in that build.
create_autolink
POST /api/autolinks · Returns { autolink } · MCP only
| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
text | string | Yes | — | The phrase to match, whole words, case-insensitive. Need not be unique |
pageId | string | No | — | The page to link it to. Without one, the autolink is stored but never published |
position | integer | No | appended | Index in the application order, clamped rather than rejected |
The id is generated server-side. Requires autolinks:write.
update_autolink
PUT /api/autolinks/:autolinkId · Returns { autolink } · MCP only
Changes text or pageId. An autolink is retarget-only: pageId can point at a different page but can never be cleared, and sending null is a 400. Naming a server-owned field such as lastBuildPageCount is a 403, and any change to the phrase or target clears that autolink’s build statistics.
reorder_autolinks
POST /api/autolinks/reorder · Returns { order } · MCP only
Takes order, a list of autolink ids. It sets position only, never membership: an autolink missing from the list keeps its place at the end, and an unknown id is ignored.
Autolinking is a Starter-and-up feature at build time, and phrase ordering and the per-page opt-out both change what actually gets linked — Autolinks covers both.
Company settings
Two tools for the company-wide settings on Settings → General. Both need Content Publisher or above.
update_company_settings
PUT /api/company · Returns { company } · MCP only
A JSON merge patch: omitted fields keep their stored value, an explicit null clears a leaf, and nested objects merge field by field. Arrays, documentationConfiguration.tabGroups and documentationConfiguration.openAPI are replaced whole. documentationConfiguration here holds the company-wide documentation defaults every documentation and API page type inherits and may override — except openAPI, tabGroups and highlighter, which are company-wide only. A field the schema does not name is dropped rather than rejected, so check the response for what was stored. A change to a published setting sets needsPublish: true.
upload_company_image
POST /api/assets/favicon · Returns { target, path } · MCP only
Uploads one image, sent as base64, into one slot: favicon, logo, docLogoLight or docLogoDark. Only that slot changes. The file must decode to at most 100 KB (413 otherwise). A slot that points somewhere new marks the company as needing publication.
If the tool name you came here for is not on this page, the Operator MCP Tool Index lists every tool alphabetically with the page that documents it.