Pages & Sites

Space MCP tools in the Pages & Sites scope (pages). Reach these with a wks_live_ key over space-mcp or space-api.

Tools in the pages scope, callable over space-mcp (tools/call) or space-api (REST) with a wks_live_* key that grants pages.

add_page_media

Scope: pages:write

Put a picture, GIF, video, audio (music, a podcast clip, a voice note) or a YouTube / Vimeo / Loom video on a page, in the section you name, and say how it plays. Give asset_id (a library file of this space — list_document_assets, upload_asset / add_asset_from_url first; the page follows the file when it changes) or url (a file's link, or the YouTube/Vimeo/Loom page link). Where: section = a section's id, its number (1 = first) or words from its heading (list_page_contents → sections), position start|end (inside it) or before|after it; no section = at the end of the page, before the footer. How it plays: play "click" (waits for the visitor — default), "auto" (as the page opens) or "in_view" (when the visitor scrolls to it, pausing when they scroll away); loop; sound (a video that plays by itself is silent unless sound: true); controls; start_at, volume, speed; a thumbnail (thumbnail_asset_id or thumbnail_url — a video's poster, an audio's cover, an embed's still; YouTube's is automatic). Browsers hold sound until the visitor has touched the page: a video then starts muted with its controls, audio on their first tap — the answer says so. Returns the media's id for set_page_media / remove_page_media.

ParameterTypeRequiredDescription
page_idstringyes
asset_idstringA library file of this space (picture, GIF, video or audio).
urlstringOr a link: a file's url, or a YouTube / Vimeo / Loom page link.
kindimage | video | audio | embedRead from the file when left out.
sectionstring,integerA section's id, its number (1 = first) or words from its heading.
positionstart | end | before | afterInside the section at its start/end, or right before/after it. Default end.
playclick | auto | in_view
loopboolean
soundbooleanVideo / embed: with sound. Audio always has sound.
controlsbooleanShow the player's controls (default true).
start_atnumber,stringWhere playing starts: seconds (90) or a time ("1:30").
volumenumberLoudness 0–100 (video/audio files; a YouTube/Vimeo/Loom player sets its own).
speednumberPlayback speed 0.25–2 (video/audio files).
thumbnail_asset_idstringA picture from the library shown before it plays.
thumbnail_urlstringOr a picture's link.
captionstringA line under it (audio: its title beside the player).
altstringWhat a picture shows, for people who can't see it.
widthfull | wide | narrowDefault wide (audio: narrow).

book_on_page

Scope: pages:write

Book someone into an OPEN slot on a booking page, exactly as if they booked it themselves: the booking lands on the calendar, a meeting link is made if the page uses one, the guest becomes a contact and gets the page's confirmation email. Find open slots with preview_booking_page, then pass its start_at (or date + start in the page's timezone). A page that asks questions (a form) needs its required answers in form_data.

ParameterTypeRequiredDescription
booking_page_idstringyes
start_atstringA slot's start_at from preview_booking_page (ISO 8601).
datestringOr: YYYY-MM-DD in the page's timezone (with start).
startstringHH:MM in the page's timezone (with date).
guest_namestringyes
guest_emailstringyes
guest_notesstring
form_dataobjectAnswers to the page's form, by field key (get_booking_page lists its questions).

cancel_booking

Scope: pages:write

Cancel a booked appointment (status → cancelled). Get the booking_id from list_bookings. The guest is emailed that it is cancelled (with a calendar file that removes it) unless notify_guest:false; add a note to say why. The linked calendar event is kept unless delete_calendar_event:true. Returns guest_told.

ParameterTypeRequiredDescription
booking_idstringyes
delete_calendar_eventbooleanAlso remove the linked calendar event (default false, matching the in-app cancel).
notify_guestbooleanEmail the guest (default true), with a calendar file that updates their calendar. false = say nothing.
notestringA line for the guest in that email — why, or what happens next.

create_blog_author

Scope: pages:write

Create a hand-typed byline the space owns — a guest writer, a client principal, a team name like "The Silverpoint Dental Team". Reuse it across posts via author_id. To credit a real Wakato member or business account instead, ask the owner to add them in the Blog UI: attaching a real person's identity to content is their call, not yours.

ParameterTypeRequiredDescription
namestringyesByline as it should read, e.g. "Dr. Evan Lane, DDS".
biostringShort bio shown under the byline on the post.
avatar_urlstringHosted headshot/logo URL.
socialsobjectOptional links, e.g. {"website":"https://…","linkedin":"https://…"}.

create_blog_collection

Scope: pages:write

Create a blog (collection) — the REAL blog system, not a static page. NEVER hand-write post cards or one page per post: pass index_page_id and the blog section is written into that page for you, so every post you publish afterwards appears there automatically. To make it part of a website, pass site_id from create_site/list_sites. A blog holds the post layout (template_html tokens: {{title}} {{excerpt}} {{body}} {{date}} {{category}} {{cover_image}} (a full <img> or nothing) {{author}} (the NAME only) plus {{author_avatar}} {{author_bio}} {{author_links}} — each empty when the author has none, so an author block collapses instead of breaking — or {{author_card}} for the whole byline as one unit; a neutral default is used if omitted). Idempotent by base_slug per space. Then add posts with create_blog_post.

ParameterTypeRequiredDescription
namestringBlog name, e.g. "Acme Journal" (default "Blog").
site_idstringAttach to a site so the blog is part of one website (recommended).
base_slugstringURL segment, default "blog" (derived from the name if omitted). Unique per space, fixed at creation.
template_htmlstringCustom post layout HTML with the six tokens. Omit for a neutral responsive default.
index_page_idstringThe page this blog shows on. The blog section is written into that page automatically — do not add cards yourself.
card_templatestringHTML for ONE index card, using {{url}} {{title}} {{excerpt}} {{cover_image}} {{author}} {{date}} {{category}}. Design it however the site needs — the same markup renders every post, forever. {{cover_image}} is a complete <img class="card-cover"> or NOTHING when the post has no cover, so never wrap it in your own <img>; for a background/overlay treatment use {{cover_image_css}} (the whole background-image declaration, or nothing) rather than url('{{cover_image_url}}'), which would emit url('') on a coverless post. Match the site's own card design — cards then render bare so the host page's grid styles them. Omit for a neutral built-in card.

create_blog_post

Scope: pages:write

Write a blog post into a collection. Provide body_html (HTML rendered into the blog template), plus optional excerpt, cover_image_url, and author. Defaults to a draft; pass status:'published' to publish immediately (live at /post/<id>). A published post appears on the blog's index page on its own — never create a page for a post. Requires create_blog_collection first.

ParameterTypeRequiredDescription
collection_idstringyesFrom create_blog_collection / list_blog_collections.
titlestringyes
body_htmlstringPost body as HTML.
excerptstring1–2 sentence summary; also the default meta description and card text.
cover_image_urlstringHosted cover image URL.
author_idstringA byline from list_blog_authors. Preferred over author — it carries the photo, bio and links.
authorstringFallback byline text when no author_id is used.
slugstringPreferred URL slug (a short suffix is appended to keep it unique).
statusdraft | publishedDefault draft.
categorystringShort label shown on index cards, e.g. "Compliance".
meta_titlestringSEO <title> override (≤70 chars). Defaults to title.
meta_descriptionstringSEO meta description override (≤160 chars). Defaults to excerpt.
og_image_urlstringSocial share image override. Defaults to cover_image_url.

create_booking_page

Scope: pages:write

Create a real Space booking page so visitors can schedule time. Returns { booking_page_id, slug, calendars }. Embed with &lt;div data-wakato-booking="&lt;booking_page_id>"> or link a CTA to /b/<slug>. Active immediately; open hours default to Mon–Fri 9–5 unless you pass availability. Every calendar of the space closes its slots unless you pass checks_calendars. Set meeting_location_type to "wakato_meet" so EVERY booking automatically creates a Wakato Meet video-call link.

ParameterTypeRequiredDescription
namestringyes
descriptionstring
booking_typeone_on_one | group
duration_minutesnumber
timezonestring
meeting_location_typewakato_meet | in_person | phone_call | custom_linkWhere meetings happen. "wakato_meet" auto-creates a Wakato Meet video link on every booking.
meeting_location_valuestringAddress (in_person), phone/instructions (phone_call), or URL (custom_link). Leave empty for wakato_meet.
form_idstringAttach a Space form (from list_forms/create_form) so guests answer extra questions when booking. Must belong to this space.
availabilityobject[]Weekly open hours, replacing the current ones: [{ day: 1, start: "09:00", end: "17:00" }] — day 0 = Sunday … 6 = Saturday; several ranges on one day are several rows.
date_overridesobject[]Days that differ from the weekly hours (replaces the list): { date: "2026-12-24", available: false } closes a day; { date, available: true, ranges: [{ start, end }] } opens it with its own hours.
max_attendeesnumberGroup pages: how many people can book the same slot.
slot_interval_minutesnumber,nullHow often slots start (e.g. 15 = every quarter hour); null = every duration_minutes.
checks_calendarsstring[]Which calendars' busy events close slots on this page — keys from list_calendars ('space' = events made in this space, 'conn:<id>' = a Google/Outlook account, 'phone:<id>' = a member's phone). The others are ignored. [] = only this page's own bookings count.
copy_bookings_tostring,nullAlso put every booking on this Google/Outlook calendar of the space (a 'conn:<id>' key from list_calendars), so it shows as taken there too. null = stop.
brandingobjectPartial branding patch, MERGED onto existing branding (null value clears a key). Keys: mode ('light'|'dark'), primary_color, background_color, card_color, text_color, muted_color, border_color, on_primary_color, input_background, subtle_color, disabled_color, font_family, radius, button_text, photo_url (https cover photo shown on the public page's brand panel), logo_url. Setting only photo_url never clobbers existing colors.

create_page

Scope: pages:write

Create a new AI-designed web page. EVERY page is part of exactly one artifact — decide FIRST: (a) part of a browseable WEBSITE → create_site kind:"website" and pass site_id; (b) a step of a LANDING flow/funnel → create_site kind:"landing" and pass site_id (steps link FORWARD only, no nav); (c) a single standalone LANDING PAGE → no site_id (pass standalone:true if it's your 2nd+ within the hour). Pass layout_html (a complete HTML document) to build it in one call. Any <form> must use the binding &lt;form data-wakato-form-id="&lt;id>"> and any calendar must use &lt;div data-wakato-booking="&lt;id>"> (or a /b/<slug> link) referencing a real Space form/booking page — create those first with create_form/create_booking_page. Storefront sections: embed &lt;div data-wakato-products>&lt;/div> (live grid of every product listed on this space, optional data-limit="N"). For CATEGORY sections (Men/Women/Kids…) add data-tags="men" — comma-separated product tags, the grid keeps only products carrying ALL of them, and it stays live as tagged products are added/removed (tag products via create_product/update_product tags). &lt;div data-wakato-product="&lt;product_id>">&lt;/div> renders one product's card. Leave every container EMPTY; the platform fills live price/photos/stock/rating with working Buy buttons at render time (restyle every card with set_product_card_template; a Buy button you design yourself works too — any element with data-wakato-checkout="<product_id>", or Wakato.checkout(id) from your own script, opens the checkout over the page; to design the product page AND the checkout form yourself, bound live to the listing, read get_storefront_sdk_reference), and each card links to the product's live DETAIL page at /product/<product_id> (renders automatically — design it with set_product_page_template, never hand-build product pages). Every LIVE product of the store connected to this space is on its storefront automatically — nothing to link.Blog section: &lt;div data-wakato-blog-index data-base="&lt;the blog's base_slug>">&lt;/div> — leave it EMPTY; it fills with cards for every published post, forever. data-base MUST equal the collection's base_slug (list_blog_collections) or the section renders empty. Easier: create the page, then set index_page_id on the blog (create_blog_collection / update_blog_collection) and the section is written for you. NEVER hand-write post cards and NEVER create one page per post — both go stale the moment a post is added. Link sibling pages with /pg/&lt;their slug> hrefs. Idempotent by title: a same-title page is filled if empty, otherwise returned untouched. ENFORCED: a 2nd+ standalone page within the hour is rejected unless standalone:true — scattering one website/funnel across loose pages is never allowed.

DESIGN BRIEF — an unstyled or default-looking page is a FAILED build. BEFORE the first page of a conversation, call get_page_guide: it carries the space's brand record and how to work with the person — ask before deciding the look, show a direction, build a DRAFT, check it with preview_page, publish only when they say so — plus what usually converts for each kind of page (get_page_guide {kind}). The person's words, their brand and your instructions come first; never give two businesses the same page. Commit to a small system before writing HTML: one background family, one ink color, ONE accent kept for actions, AA-readable contrast; two Google Fonts via <link> (a display face with character, a plain body face); a type scale and a spacing rhythm, content in a max-width container with ≥16px side gutters. RESPONSIVE IS MANDATORY: include the viewport meta, grids/flex collapse to one column ≤768px, nothing overflows horizontally (images max-width:100%, fixed aspect via object-fit:cover), tap targets ≥44px. IMAGES: reference ONLY real URLs you know exist (space assets, generate_image results) — NEVER invent file paths; with no image available, design with gradients/patterns/typography instead of leaving broken tiles or icon placeholders. Whenever a url came with a library asset id, CARRY THE ID: <img src="<url>" data-wakato-asset="<asset_id>"> (a CSS background puts data-wakato-asset-bg="<asset_id>" on the element). That binds the media to the file, so replacing the file later updates every page using it and the owner can manage it from the page editor's media shelf. Video/audio/YouTube: add_page_media. Popups open by data-wakato-popup-open="<id>". LIVE DATA: to show a space table's rows (pricing, menu, directory, schedule), embed <div data-wakato-table="<table id>" data-columns="key1,key2" data-limit="10"></div> and let it fill itself — NEVER paste rows in as text, which freezes them the moment you write them. Only a table whose owner set web_published renders, and only its web_columns (list_tables tells you both); if the table the user means is not published, say so rather than pasting its rows in. Put a short fallback line inside the container for the case where it resolves to nothing. To show a document's text (terms, a policy, a guide, a changelog), embed <div data-wakato-document="<document id>"></div> — again never pasting the text in, and again only a document whose owner published it to the web renders (list_documents tells you which). Same for products (data-wakato-products) and blog posts (data-wakato-blog-index). Content gated behind scroll-triggered JS must still be visible with JS disabled (headless QA screenshots scroll once, nothing more).

ParameterTypeRequiredDescription
titlestringyes
descriptionstring
layout_htmlstringComplete HTML document for the page body.
site_idstringThe artifact this page belongs to (from create_site/list_sites). REQUIRED for every website page and every landing-flow step; omit ONLY for a single standalone landing page.
standalonebooleanSet true ONLY for an intentional separate single landing page unrelated to other recent pages. Required for a 2nd+ standalone page within an hour (otherwise rejected to prevent scattering one website/funnel as loose pages).
meta_titlestringSearch/social <title> override (≤70 chars). Defaults to the page title.
meta_descriptionstringMeta + og:description (≤160 chars). Falls back to description; when neither is set the tag is omitted, leaving any <meta name="description"> in your own layout_html to stand.
og_image_urlstringSocial share image for this page.

create_site

Scope: pages:write

Create a web artifact and DECLARE what it is. kind:"website" = a browseable multi-page site (visitors roam freely via shared navigation). kind:"landing" = a landing page flow: forward-only steps toward ONE goal — opt-in → sales → thank-you etc. — with navigation disabled (when someone asks for a 'funnel', this is it; a single conversion page needs NO site — just create_page standalone). REQUIRED before any multi-page build: create the artifact FIRST, then pass its id as site_id on every create_page call (do NOT scatter pages standalone). Idempotent by title. The first page attached becomes the home page / step 1.

ParameterTypeRequiredDescription
titlestringyesBrand/offer name, e.g. "Acme Bike Co." or "Spring Sale Funnel".
kindwebsite | landingyeswebsite = browseable with shared nav · landing = forward-only step flow, nav disabled (a funnel when 2+ steps).
descriptionstring
nav_layouttop | sidebar | noneNav style for websites (default top). Ignored for kind:landing — landing flows never get a nav.
logo_urlstringOptional brand logo URL.

delete_booking_page

Scope: pages:write

PERMANENTLY delete a booking page (its booking history is deleted with it). Refused while upcoming confirmed bookings exist — cancel those first with cancel_booking. Prefer update_booking_page status:"paused" to just stop new bookings.

ParameterTypeRequiredDescription
booking_page_idstringyes

delete_page

Scope: pages:write

PERMANENTLY delete one page (a published page goes offline). To remove a WHOLE website or landing flow use delete_site instead — it removes the artifact and all its pages in one call. If the deleted page belonged to a site, run validate_site afterwards and fix any nav item that pointed at it.

ParameterTypeRequiredDescription
page_idstringyes

delete_site

Scope: pages:write

PERMANENTLY delete a web artifact — the WHOLE website or landing flow: by default every page/step is deleted with it (published pages go offline). This is the correct way to remove a website or funnel; deleting its pages one by one leaves the shell behind. Pass keep_pages: true only if the pages should survive as standalone single landing pages (they lose the shared nav/flow).

ParameterTypeRequiredDescription
site_idstringyes
keep_pagesbooleantrue = detach the site's pages as standalone instead of deleting them (default false).

edit_page_html

Scope: pages:write

Write or change a page in pieces, for a page too long to send whole in one call. append: add a piece before </body> (or a before marker, or at the end while the page is still open) — so build a long page head, sections, script, then </body></html>, each piece under ~12 KB (a longer one is cut off). find + replace: change one exact piece (replace_all for every match; replace "" removes it). Checked like update_page; a live page changes live.

ParameterTypeRequiredDescription
page_idstringyes
appendstringA piece of HTML to add.
beforestringWith append: put the piece right before this exact text on the page (default: before </body>, or at the end when the page has none yet).
findstringAn exact piece of the page's current HTML to change.
replacestringWhat to put in its place ("" removes it).
replace_allbooleanWith find: change every match, not just one.

get_booking_page

Scope: pages:read

Get a Space booking page including its availability and timezone.

ParameterTypeRequiredDescription
booking_page_idstringyes

get_page

Scope: pages:read

Get a single page including its layout_html (the full HTML document).

ParameterTypeRequiredDescription
page_idstringyes

get_page_analytics

Scope: pages:read

How a page is doing with visitors — the same numbers as the page's Analytics tab: visits, people (and how many came back), time on page, how many left right away, how far they scrolled, forms sent, bookings, who is on it right now, visits by day and busiest hours, the change against the period before, and where they came from (sites, campaigns), where they are (countries, cities, languages) and what they used (devices by name — iPhone, Android phone, Mac…, systems, browsers). Give page_id for one page in full, site_id for a site with a row per page, or nothing for every published page ranked by visits. range: today | 7d | 30d (default) | 90d | all, or from/to dates. Counts only visits a real browser opened.

ParameterTypeRequiredDescription
page_idstringOne page, in full (list_pages).
site_idstringA site: its totals and a row per page (list_sites).
rangetoday | 7d | 30d | 90d | allDefault 30d.
fromstringISO date — a custom window (with to).
tostringISO date.

get_page_guide

Scope: pages:read

READ THIS BEFORE BUILDING A PAGE OR A WEBSITE. Wakato's guide to pages people actually want: who decides (the person, then their brand, then your instructions), how to work with them (ask before deciding the look, show a direction, build a DRAFT, look at it with preview_page, publish only when they say so), design and copy rules, and this space's BRAND RECORD (colours, fonts, logo, contact details) so you start from it. Call again with kind for what usually converts on that kind of page — local service, restaurant, appointment business, shop, product, software, opt-in, sales page, funnel, event, portfolio, link in bio. Free.

ParameterTypeRequiredDescription
kindstringThe kind of page you are building, for its playbook: local_service, restaurant, appointments, shop, product, software, lead_magnet, sales_page, funnel, event, portfolio, link_in_bio. Omit for the whole guide (and the brand).

list_blog_authors

Scope: pages:read

List the bylines this space can use. Each is a 'member' (a Wakato person), a 'business', or 'manual' (hand-typed). Pass an id as author_id on create_blog_post so the post carries a real byline — photo, bio and links included — instead of only a name string. Member and business bylines are owned by that account and cannot be edited here.

ParameterTypeRequiredDescription
include_removedbooleanAlso list bylines that were removed. Removed ones cannot be used on new posts.

list_blog_collections

Scope: pages:read

List the space's blogs (collections). Each blog groups posts and may be attached to a site (site_id). Call before create_blog_collection to reuse an existing blog.

list_blog_posts

Scope: pages:read

List blog posts in the space. Optional collection_id, status (draft|published), limit.

ParameterTypeRequiredDescription
collection_idstring
statusdraft | published
limitnumber

list_booking_pages

Scope: pages:read

List Space booking pages (id, name, slug, status). Reuse an existing one before creating a new one.

list_bookings

Scope: pages:read

List actual booked appointments (guest name/email, start/end, status, attendee_status = whether the guest accepted/declined the calendar invite) across the space or one booking page. Use upcoming_only:true for the forward schedule, search to find a specific guest, status:"cancelled" for cancellations. Answers in the guest's attached form (if any) are in form_data.

ParameterTypeRequiredDescription
booking_page_idstringRestrict to one booking page (omit for all pages in the space).
statusconfirmed | cancelled
upcoming_onlybooleanOnly bookings that start from now, soonest first.
fromstringISO datetime lower bound on start_at.
tostringISO datetime upper bound on start_at.
searchstringMatch on guest name or email.
limitnumberMax rows (default 50, max 200).

list_page_contents

Scope: pages:read

What is ON a page, without the html: every picture/video by FILE (url, asset_id when bound, how many places), every video/audio/embed with how it plays (playable: id, play, loop, sound, controls, thumbnail), the page's sections (for add_page_media), the storefront grids (tags, limit) and pinned product cards, the bound tables/documents and whether the blog index is on it. Read this before swap_page_media / set_page_products — they change what's on the page, never its layout.

ParameterTypeRequiredDescription
page_idstringyes

list_pages

Scope: pages:read

List pages in the space.

list_sites

Scope: pages:read

List the space's web artifacts (sites). Each is EITHER kind:"website" (browseable multi-page site, shared brand + platform-injected navigation) OR kind:"landing" (a landing page flow — forward-only ordered steps toward one goal, no nav; 2+ steps = a funnel). Returns id, title, slug, kind, status, root_page_id. Call this before create_site to avoid duplicates.

place_page_media

Scope: pages:write

Move a picture, video, audio or embedded video to another section or spot on the page (section = id, number or heading words from list_page_contents → sections; position start|end|before|after) and/or change its size (width: full | wide | narrow). Name it by id, url or asset_id.

ParameterTypeRequiredDescription
page_idstringyes
mediastringyes
sectionstring,integer
positionstart | end | before | after
widthfull | wide | narrow

present_page

Scope: pages:read

Show a Space Page live in your chat's panel (Apps & Pages tab). Call it when you start building or changing a page, or when the user asks to open/see one. Whiteboards and documents cannot be presented. UI-only signal — succeeds even if the panel is closed, and stores nothing.

ParameterTypeRequiredDescription
page_idstringyes

preview_booking_page

Scope: pages:read

TEST a booking page the way a guest would: returns its full config, the attached form's questions (if any), and REAL bookable time slots computed with the same engine as the public /b/<slug> widget — plus a ready verdict and issues list. Each slot's label is complete — weekday, date, and time already rendered in the requested timezone (e.g. "Tuesday, July 14 at 10:30 AM"; quote verbatim — never pair a time with your own weekday/date or do timezone math) — plus an absolute start_at/end_at (UTC ISO). Use this right after create/update_booking_page to confirm it actually works. Pass a specific date (YYYY-MM-DD) to probe one day, or omit it to scan the next days (default 14) for the first open day.

ParameterTypeRequiredDescription
booking_page_idstringyes
datestringOptional day to test, YYYY-MM-DD. Omit to scan forward for the next open day.
daysnumberDays to scan when no date is given (default 14, max 60).
timezonestringIANA timezone to render slot labels in (e.g. America/Los_Angeles). Defaults to the page's own timezone.

preview_page

Scope: pages:run

SEE a page before anyone does: renders its DRAFT (or the live page) as visitors get it, saves desktop 1440px + mobile 390px screenshots to Space Assets, and runs a vision QA critique (billed to the space wallet) of real visual problems — overlap, clipping, contrast, broken images, unstyled or empty sections, mobile overflow, no clear headline or button up top. THE QUALITY LOOP: build the draft → preview_page → fix every severity=error → preview again until looks_good → show the person → publish_page only when they say so. An unpublished page is drawn as its draft, a published one as it is live. Pair with validate_site.

ParameterTypeRequiredDescription
page_idstringyes
viewportsstring[]Widths to capture (default both).
critiquebooleanRun the vision critique (default true). Set false for screenshots only.

publish_blog_post

Scope: pages:run

Publish a blog post (live at /post/<id>), or pass publish:false to move it back to draft. Requires pages:run.

ParameterTypeRequiredDescription
post_idstringyes
publishbooleanfalse to unpublish (default true).

publish_page

Scope: pages:run

Publish a page so it's live at /pg/<published_slug>. Pass publish:false to unpublish. Requires pages:run. The result includes an automatic link lint (link_issues): severity=error entries are DEAD links/embeds visitors will hit — fix and republish, never leave them. Publish only when the person says so — check the draft with preview_page first, and look once more at the live page afterwards.

ParameterTypeRequiredDescription
page_idstringyes
published_slugstringOptional custom slug; defaults to the page's slug.
publishbooleanfalse to unpublish (default true).

remove_page_media

Scope: pages:write

Take a picture, video, audio or embedded video off a page — by its id (list_page_contents → playable), its url or its library file id. Media added with add_page_media goes whole (with its caption); a picture the page carries by url is removed wherever it appears. The file stays in the library.

ParameterTypeRequiredDescription
page_idstringyes
mediastringyesIts id, url or asset_id.

reorder_site_pages

Scope: pages:write

Set the display order of a site's pages (the order they appear in the site's page list and default navigation). Pass the site's page ids in the desired order; omitted pages sort after the listed ones.

ParameterTypeRequiredDescription
site_idstringyes
page_idsstring[]yesPage ids in the desired order (first = home position).

reschedule_booking

Scope: pages:write

Move a confirmed booking to a new slot. Pass new_date (YYYY-MM-DD) + new_start (HH:MM) in the booking page's OWN timezone — find open slots with preview_booking_page first. The new slot is validated against real availability (the booking never conflicts with itself) and the linked calendar event moves with it. The guest is emailed the new time (with a calendar file that moves it) unless notify_guest:false. Returns guest_told.

ParameterTypeRequiredDescription
booking_idstringyes
new_datestringyesYYYY-MM-DD in the page's timezone.
new_startstringyesHH:MM in the page's timezone.
new_endstringHH:MM — derived from the page's duration when omitted.
skip_availability_checkbooleanForce the move even if the slot isn't in the page's open availability.
notify_guestbooleanEmail the guest (default true), with a calendar file that updates their calendar. false = say nothing.
notestringA line for the guest in that email — why, or what happens next.

set_page_media

Scope: pages:write

Change one picture, video, audio or YouTube/Vimeo/Loom video on a page: how it plays (play: click | auto | in_view = when scrolled to; loop; sound; controls), where it starts (start_at), its volume and speed (video/audio files), its thumbnail (thumbnail_asset_id / thumbnail_url, "" to take it away), its caption, its description (alt, pictures), or its SOURCE — a new file (asset_id) or link (url) of the same kind: a new audio for an audio, a new YouTube/Vimeo/Loom link for an embed. Name it by its id (list_page_contents → playable), its url or its library file id. Works on media added with add_page_media and on what the page already had. Layout untouched — place_page_media moves or resizes.

ParameterTypeRequiredDescription
page_idstringyes
mediastringyesIts id, url or asset_id (list_page_contents).
playclick | auto | in_view
loopboolean
soundboolean
controlsboolean
start_atnumber,stringWhere playing starts: seconds (90) or a time ("1:30").
volumenumberLoudness 0–100 (video/audio files; a YouTube/Vimeo/Loom player sets its own).
speednumberPlayback speed 0.25–2 (video/audio files).
thumbnail_asset_idstring
thumbnail_urlstringA picture's link; "" takes the thumbnail away.
captionstring
altstringWhat a picture shows, for people who can't see it.
asset_idstringA new source: a library file of the same kind.
urlstringOr a new link: a file's url, or a new YouTube / Vimeo / Loom link.

set_page_media_rules

Scope: pages:write

The page's own media rule: one_at_a_time = starting a video or audio with sound pauses any other one playing with sound (silent background loops keep playing; a YouTube/Vimeo/Loom player is another site's and isn't paused).

ParameterTypeRequiredDescription
page_idstringyes
one_at_a_timebooleanyes

set_page_products

Scope: pages:write

Change which products a page shows, layout untouched: add = pin products beside the page's pinned cards (a card like the last one, right after it), remove = take products off THIS page (a pinned card is removed; a product that shows through a storefront grid is hidden in that grid — it stays on sale everywhere else, and add puts it back), grid = narrow a storefront grid ({index, tags: ["men"] = only products carrying all of them, [] = every product; limit: 6 or null}). Ids from list_products. On a live page the change is public at once.

ParameterTypeRequiredDescription
page_idstringyes
addstring[]Product ids to pin, or to put back in a grid that hides them.
removestring[]Product ids to take off this page.
gridobjectTags and/or limit for one storefront grid (index 0 = the first on the page).

swap_page_media

Scope: pages:write

Replace one picture (or video) on a page with a library file — everywhere it appears, layout untouched, and bound to the file from then on (data-wakato-asset). Name the old one by from_url or from_asset_id as list_page_contents shows it; the new one must be a file of this space (list_document_assets, or upload_asset / add_asset_from_url first). A picture is swapped for a picture, a video for a video. On a live page the change is public at once.

ParameterTypeRequiredDescription
page_idstringyes
from_urlstringThe url the page carries for the picture to replace (from list_page_contents).
from_asset_idstringOr the library file it is bound to.
to_asset_idstringyesThe library file to show instead.

update_blog_collection

Scope: pages:write

Edit a blog (collection): rename it, replace template_html to re-theme the post layout (keep the six tokens), or attach/detach a site_id / index_page_id (pass null to detach). Setting index_page_id writes the blog section into that page, so posts start appearing there automatically. The base_slug is fixed at creation.

ParameterTypeRequiredDescription
collection_idstringyes
namestring
template_htmlstringReplacement post layout HTML with the six tokens.
site_idstring,nullAttach to a site, or null to detach.
index_page_idstring,nullThe page this blog shows on (the blog section is written into it), or null to clear.
card_templatestring,nullHTML for ONE index card, using {{url}} {{title}} {{excerpt}} {{cover_image}} {{author}} {{date}} {{category}}. Design it however the site needs — the same markup renders every post, forever. {{cover_image}} is a complete <img class="card-cover"> or NOTHING when the post has no cover, so never wrap it in your own <img>; for a background/overlay treatment use {{cover_image_css}} (the whole background-image declaration, or nothing) rather than url('{{cover_image_url}}'), which would emit url('') on a coverless post. Match the site's own card design — cards then render bare so the host page's grid styles them. Omit for a neutral built-in card.

update_blog_post

Scope: pages:write

Edit a blog post's title, excerpt, cover_image_url, body_html, author, or its SEO overrides (meta_title / meta_description / og_image_url — pass null to clear one and fall back to the derived value).

ParameterTypeRequiredDescription
post_idstringyes
titlestring
excerptstring
cover_image_urlstring
body_htmlstring
author_idstring,nullA byline from list_blog_authors, or null to clear it.
authorstring
categorystring,nullShort label shown on index cards, e.g. "Compliance".
meta_titlestring,nullSEO <title> override (≤70 chars), or null to fall back to title.
meta_descriptionstring,nullSEO meta description override (≤160 chars), or null to fall back to excerpt.
og_image_urlstring,nullSocial share image override, or null to fall back to cover_image_url.

update_booking_page

Scope: pages:write

Edit an existing booking page — everything its settings screen has: name, length, open hours (availability), days off / special days (date_overrides), one-on-one vs group, notice, how far ahead, status, location, form, confirmation email, branding, which calendars close its slots (checks_calendars) and where bookings are also copied (copy_bookings_to). Pass booking_page_id + only the fields to change. Set meeting_location_type to "wakato_meet" so every booking auto-creates a Wakato Meet video-call link.

ParameterTypeRequiredDescription
booking_page_idstringyes
namestring
descriptionstring
duration_minutesnumber
timezonestring
statusdraft | active | paused
buffer_minutesnumber
min_notice_hoursnumber
max_advance_daysnumber
meeting_location_typewakato_meet | in_person | phone_call | custom_link
meeting_location_valuestring
form_idstringAttach a Space form (id) so guests answer extra questions when booking, or null to detach. Must belong to this space.
availabilityobject[]Weekly open hours, replacing the current ones: [{ day: 1, start: "09:00", end: "17:00" }] — day 0 = Sunday … 6 = Saturday; several ranges on one day are several rows.
date_overridesobject[]Days that differ from the weekly hours (replaces the list): { date: "2026-12-24", available: false } closes a day; { date, available: true, ranges: [{ start, end }] } opens it with its own hours.
max_attendeesnumberGroup pages: how many people can book the same slot.
slot_interval_minutesnumber,nullHow often slots start (e.g. 15 = every quarter hour); null = every duration_minutes.
checks_calendarsstring[]Which calendars' busy events close slots on this page — keys from list_calendars ('space' = events made in this space, 'conn:<id>' = a Google/Outlook account, 'phone:<id>' = a member's phone). The others are ignored. [] = only this page's own bookings count.
copy_bookings_tostring,nullAlso put every booking on this Google/Outlook calendar of the space (a 'conn:<id>' key from list_calendars), so it shows as taken there too. null = stop.
booking_typeone_on_one | group
confirmation_messagestringText shown on-page after booking AND inside the default confirmation email.
confirmation_email_enabledbooleanSend the guest a confirmation email with the .ics invite (default true). false = no email.
confirmation_email_subjectstringCustom confirmation email subject. Merge tags: {{guest_name}} {{guest_email}} {{page_name}} {{space_name}} {{meeting_time}} {{meeting_link}} {{meeting_location}}.
confirmation_email_bodystringCustom confirmation email body text — replaces the default greeting/intro; the meeting time, join link, and .ics invite are always included below it. Merge tags: {{guest_name}} {{guest_email}} {{page_name}} {{space_name}} {{meeting_time}} {{meeting_link}} {{meeting_location}}.
brandingobjectPartial branding patch, MERGED onto existing branding (null value clears a key). Keys: mode ('light'|'dark'), primary_color, background_color, card_color, text_color, muted_color, border_color, on_primary_color, input_background, subtle_color, disabled_color, font_family, radius, button_text, photo_url (https cover photo shown on the public page's brand panel), logo_url. Setting only photo_url never clobbers existing colors.

update_page

Scope: pages:write

Update a page's title/description and/or rewrite its layout_html. To change only what's ON the page — a picture, which products show — use swap_page_media / set_page_products instead: they never touch the layout, and they are all an agent held at "Change what's on it" has. Embedded form/booking/product references are re-validated against this space. Storefront containers (data-wakato-products with optional data-tags/data-limit / data-wakato-product="<id>") and the blog index (data-wakato-blog-index) are supported the same as create_page. If the page is already published the change goes LIVE immediately — run preview_page afterwards.

DESIGN BRIEF — an unstyled or default-looking page is a FAILED build. BEFORE the first page of a conversation, call get_page_guide: it carries the space's brand record and how to work with the person — ask before deciding the look, show a direction, build a DRAFT, check it with preview_page, publish only when they say so — plus what usually converts for each kind of page (get_page_guide {kind}). The person's words, their brand and your instructions come first; never give two businesses the same page. Commit to a small system before writing HTML: one background family, one ink color, ONE accent kept for actions, AA-readable contrast; two Google Fonts via <link> (a display face with character, a plain body face); a type scale and a spacing rhythm, content in a max-width container with ≥16px side gutters. RESPONSIVE IS MANDATORY: include the viewport meta, grids/flex collapse to one column ≤768px, nothing overflows horizontally (images max-width:100%, fixed aspect via object-fit:cover), tap targets ≥44px. IMAGES: reference ONLY real URLs you know exist (space assets, generate_image results) — NEVER invent file paths; with no image available, design with gradients/patterns/typography instead of leaving broken tiles or icon placeholders. Whenever a url came with a library asset id, CARRY THE ID: <img src="<url>" data-wakato-asset="<asset_id>"> (a CSS background puts data-wakato-asset-bg="<asset_id>" on the element). That binds the media to the file, so replacing the file later updates every page using it and the owner can manage it from the page editor's media shelf. Video/audio/YouTube: add_page_media. Popups open by data-wakato-popup-open="<id>". LIVE DATA: to show a space table's rows (pricing, menu, directory, schedule), embed <div data-wakato-table="<table id>" data-columns="key1,key2" data-limit="10"></div> and let it fill itself — NEVER paste rows in as text, which freezes them the moment you write them. Only a table whose owner set web_published renders, and only its web_columns (list_tables tells you both); if the table the user means is not published, say so rather than pasting its rows in. Put a short fallback line inside the container for the case where it resolves to nothing. To show a document's text (terms, a policy, a guide, a changelog), embed <div data-wakato-document="<document id>"></div> — again never pasting the text in, and again only a document whose owner published it to the web renders (list_documents tells you which). Same for products (data-wakato-products) and blog posts (data-wakato-blog-index). Content gated behind scroll-triggered JS must still be visible with JS disabled (headless QA screenshots scroll once, nothing more).

ParameterTypeRequiredDescription
page_idstringyes
titlestring
descriptionstring
layout_htmlstringReplacement HTML document.
meta_titlestringSearch/social <title> override (≤70 chars). Defaults to the page title.
meta_descriptionstringMeta + og:description (≤160 chars). Falls back to description; when neither is set the tag is omitted, leaving any <meta name="description"> in your own layout_html to stand.
og_image_urlstringSocial share image for this page.

update_site

Scope: pages:write

Update a web artifact after creation: title, description, nav_layout (top/sidebar/none), logo_url, root_page_id (the home page / step 1), and the managed nav_config. nav_config replaces the whole nav when provided: { enabled, items: [{label?, page_id? | url?}], cta: {label, page_id? | url?}, style: {sticky?, theme? (auto/light/dark), transparent_on_hero?, logo_position? (left/center), accent_color?} }. Page items must be pages of THIS site; item labels default to the live page title. WEBSITES ONLY for nav: landing pages are forward-only and any attempt to enable a nav on kind:landing is rejected — if visitors should browse freely, build a website instead.

ParameterTypeRequiredDescription
site_idstringyes
titlestring
descriptionstring
nav_layouttop | sidebar | none
logo_urlstringBrand logo URL. Empty string removes the logo.
root_page_idstringPage id to use as the site's home page.
nav_configobjectManaged navigation. items = ordered nav entries ({page_id} for site pages, {url} for external/public links like /f/<slug>); cta = optional highlighted button; style = bar appearance.

validate_site

Scope: pages:read

Readiness lint for a site BEFORE (or after) publishing: reports dead links (/pg/, /f/, /b/, /pay/, /post/ that resolve to nothing), nav items pointing at deleted or draft pages, form/booking/product embeds that don't exist in this space, and unpublished pages. Run this after building a multi-page site and FIX every error it reports — a site with dead links is not done. Returns { ok, pages_total, pages_published, issues: [{severity, code, message, page_id?, detail?}] }.

ParameterTypeRequiredDescription
site_idstringyes

On this page