MCP Tools — Content & Storefront

These tools let you manage every layer of your site's content from an MCP client: websites and pages, media, navigation, taxonomies, reusable block templates, custom post types, branding profiles, and your storefront (products, orders, customers, discounts, and reviews). Most write tools run through an approval workflow — when your API key is configured to require approval, mutations return a pending: true response with an approval.url you or a reviewer must click before the change goes live.

For authentication setup and scope reference, see MCP Overview.


Approval responses#

When a write tool is staged for approval instead of applied immediately, the response shape is:

{
  "pending": true,
  "pendingId": 42,
  "summary": "Create post \"About Us\" on website 7",
  "status": "pending",
  "approval": {
    "url": "https://app.simplerdevelopment.com/approve/abc123",
    "expiresAt": "2026-06-05T08:00:00.000Z"
  }
}

Tools that always mint an approval URL (even on direct-apply keys) also return approval on success — you can share that link with a reviewer to preview the result.


Websites#

sites_list#

List all websites owned by your account.

  • Auth: sites:read
  • Inputs: none

Response

[
  {
    "id": 7,
    "clientId": 3,
    "name": "Acme Corp",
    "domain": "acme.com",
    "active": true,
    "publicAccess": true,
    "brandingProfileId": 2,
    "createdAt": "2025-01-10T12:00:00.000Z"
  }
]

Tool call example

{ "name": "sites_list", "arguments": {} }

sites_update#

Update metadata on a website (name, domain, description, active flag, public-access gating, branding profile). DNS/Vercel provisioning is not triggered.

  • Auth: sites:write
InputTypeRequiredNotes
idnumberyesWebsite id
namestringno
domainstring | nullno
descriptionstring | nullno
activebooleanno
publicAccessbooleanno
brandingProfileIdnumber | nullno

Response — the updated website row, plus approval if staged.

Tool call example

{
  "name": "sites_update",
  "arguments": { "id": 7, "name": "Acme Corp v2", "active": true }
}

sites_get_custom_code#

Get the site-wide custom CSS and JS. Cascade order is: site → CPT → per-post.

  • Auth: sites:read
InputTypeRequired
idnumberyes

Response

{ "customCss": "body { font-size: 16px; }", "customJs": "" }

sites_update_custom_code#

Stage changes to site-wide custom CSS/JS. Changes are written to draft fields and do not go live until you call sites_publish_custom_code.

  • Auth: sites:write
InputTypeRequiredNotes
idnumberyes
customCssstringnoEmpty string = stage a clear
customJsstringnoEmpty string = stage a clear

Response

{
  "draftCustomCss": "body { color: red; }",
  "draftCustomJs": "",
  "liveCustomCss": "body { color: black; }",
  "liveCustomJs": "",
  "draftUpdatedAt": "2026-06-04T10:00:00.000Z",
  "note": "Wrote to draft fields. Call sites_publish_custom_code to make changes live."
}

sites_publish_custom_code#

Promote the draft site-wide CSS/JS to live. Copies draft_custom_csscustom_css and draft_custom_jscustom_js, then clears the draft fields.

  • Auth: sites:write
InputTypeRequired
idnumberyes

Response

{ "customCss": "body { color: red; }", "customJs": "" }

Posts & Pages#

posts_list#

List content posts for a website. Returns a slim projection (no content blob) by default.

  • Auth: sites:read
InputTypeRequiredNotes
websiteIdnumberno
postTypestringnoblog, page, etc.
publishedOnlybooleanno
limitnumbernoDefault 50
includeContentbooleannoDefault false — each block-rich post can be multi-MB

Response

[
  {
    "id": 101,
    "websiteId": 7,
    "title": "About Us",
    "slug": "about-us",
    "postType": "page",
    "published": true,
    "publishedAt": "2026-01-15T09:00:00.000Z",
    "excerpt": null,
    "createdAt": "2026-01-10T08:00:00.000Z"
  }
]

posts_get#

Fetch a single post by id. Prefer this over posts_list when you need one post in full.

  • Auth: sites:read
InputTypeRequiredNotes
idnumberyes
includeContentbooleannoDefault false

Response — the post row (plus content / customCss / customJs / SEO fields if includeContent: true).

Tool call example

{ "name": "posts_get", "arguments": { "id": 101, "includeContent": true } }

posts_create#

Create a blog post or page. Mints an approval URL you can share with a reviewer.

  • Auth: sites:write
InputTypeRequiredNotes
websiteIdnumberyes
titlestringyes
slugstringyes
blocksarraynoPreferred — structured block array
contentstringnoPlain text/HTML fallback; wrapped in a single text block
excerptstringno
postTypestringnoDefault blog
publishedbooleanno
customCssstringnoPer-post CSS injected at render time
customJsstringnoPer-post JS injected at render time
includeContentbooleannoEcho full body in response; default false

Response

{
  "id": 202,
  "websiteId": 7,
  "title": "New Landing Page",
  "slug": "new-landing",
  "postType": "page",
  "published": false,
  "approval": {
    "url": "https://app.simplerdevelopment.com/approve/xyz789",
    "expiresAt": "2026-06-05T10:00:00.000Z"
  }
}

posts_update#

Update a post. Supports full SEO fields. Mints an approval URL.

  • Auth: sites:write
InputTypeRequiredNotes
idnumberyes
titlestringno
blocksarrayno
contentstringno
excerptstringno
publishedbooleanno
customCssstring | nullnoPass null to clear
customJsstring | nullnoPass null to clear
seoTitlestring | nullno
seoDescriptionstring | nullno
ogImagestring | nullno
canonicalUrlstring | nullno
noIndexbooleanno
includeContentbooleannoDefault false

Response — updated slim post row + approval.


posts_fork#

Duplicate a published post into a new draft tied to the original via parentPostId. Edit the fork, share its approval URL for review, and publish it when approved without taking the live page down.

  • Auth: sites:write
InputTypeRequiredNotes
idnumberyesSource post id
titleSuffixstringnoDefault (fork)

Response

{
  "id": 203,
  "title": "About Us (fork)",
  "slug": "about-us-fork-1abc2",
  "parentPostId": 101,
  "published": false,
  "approval": { "url": "https://app.simplerdevelopment.com/approve/forklink" }
}

posts_delete#

Permanently delete a post. Revisions cascade.

  • Auth: sites:write
InputTypeRequired
idnumberyes

Response{ "success": true, "id": 101 } or a pending approval envelope.


posts_upload_html#

Upload a single HTML/XHTML file (base64-encoded) as a draft page post wrapping an html-embed block. Nav/header tags are stripped, referenced assets are imported to media, and the cleaned file is stored in S3. Max 1 MB decoded. Restricted to staff-role API keys.

  • Auth: sites:write
InputTypeRequiredNotes
websiteIdnumberyes
filenamestringyesMust end in .html, .htm, or .xhtml
contentBase64stringyesBase64-encoded HTML; decoded ≤ 1 MB
sourceUrlstringnoUsed to resolve relative asset refs

Response

{
  "id": 204,
  "title": "my-page",
  "slug": "my-page",
  "postType": "page",
  "published": false,
  "importedAssets": 3,
  "skippedAssets": 1,
  "url": "https://media.example.com/media/uuid.html"
}

posts_upload_html_zip#

Upload a zip archive (base64-encoded) containing index.html + supporting assets as a draft page. Every file is uploaded to S3; relative refs resolve through a shared media-proxy prefix. Limits: 50 MB uncompressed, 200 files, 10 MB per file. Restricted to staff-role API keys.

  • Auth: sites:write
InputTypeRequiredNotes
websiteIdnumberyes
filenamestringyesMust end in .zip
contentBase64stringyesBase64-encoded zip; decoded ≤ 50 MB

Response — same shape as posts_upload_html plus bundleFileCount (number of files extracted from the zip) and bundlePrefix (the shared S3 key prefix for all extracted files).


posts_list_revisions#

Revision history for a post (autosaves, manual saves, publishes).

  • Auth: sites:read
InputTypeRequiredNotes
postIdnumberyes
limitnumbernoDefault 25, max 100

Response — array of revision rows ordered newest-first.


Taxonomies (Categories & Tags)#

taxonomies_list#

List categories and tags for a website.

  • Auth: sites:read
InputTypeRequired
websiteIdnumberyes

Response

{
  "categories": [{ "id": 5, "name": "News", "slug": "news", "color": "#2563eb" }],
  "tags": [{ "id": 12, "name": "announcement", "slug": "announcement" }]
}

taxonomies_create_category#

Create a category on a website. May be staged for approval.

  • Auth: sites:write
InputTypeRequiredNotes
websiteIdnumberyes
namestringyes
slugstringnoDerived from name if omitted
descriptionstringno
colorstringnoHex color, e.g. #2563eb

taxonomies_create_tag#

Create a tag on a website.

  • Auth: sites:write
InputTypeRequiredNotes
websiteIdnumberyes
namestringyes
slugstringnoDerived from name if omitted

posts_set_taxonomies#

Replace the categories and/or tags assigned to a post. Call taxonomies_list first to look up ids. Omitted arrays are left unchanged.

  • Auth: sites:write
InputTypeRequired
postIdnumberyes
categoryIdsnumber[]no
tagIdsnumber[]no

Response

{ "postId": 101, "categoryIds": [5], "tagIds": [12, 14] }

Media#

media_list#

List uploaded media assets for your account.

  • Auth: media:read
InputTypeRequiredNotes
limitnumbernoDefault 50

Response — array of media rows (id, filename, mimeType, fileSize, url, alt, caption, createdAt).


media_upload_from_url#

Fetch a public URL and store the file in your media library. Max 25 MB. SSRF-guarded — internal/private URLs are rejected.

  • Auth: media:write
InputTypeRequiredNotes
urlstringyesPublic http(s) URL
filenamestringnoOverrides the filename derived from the URL path
altstringno
captionstringno
websiteIdnumbernoScope to a specific site
brandingProfileIdnumberno

Response — the new media row including url (the internal URL to use in posts, decks, emails).

Tool call example

{
  "name": "media_upload_from_url",
  "arguments": {
    "url": "https://example.com/hero.jpg",
    "alt": "Hero image",
    "websiteId": 7
  }
}

media_upload_presign#

Mint a short-lived S3 PUT URL for a direct local-file upload. After your curl --upload-file succeeds, call media_register to create the media row.

Allowed MIME types: image/png, image/jpeg, image/gif, image/webp, image/avif, image/svg+xml, application/pdf, video/mp4, video/webm, video/quicktime, audio/mpeg, audio/ogg, audio/wav. Max 25 MB.

  • Auth: media:write
InputTypeRequiredNotes
filenamestringyes
mimeTypestringyesMust be in the allow-list
fileSizenumberyesExact byte count, max 25 MB

Response

{
  "mediaKey": "media/uuid.jpg",
  "storedFilename": "uuid.jpg",
  "uploadUrl": "https://s3.amazonaws.com/bucket/media/uuid.jpg?X-Amz-...",
  "requiredHeaders": { "Content-Type": "image/jpeg", "Content-Length": "204800" },
  "expiresAt": "2026-06-04T09:05:00.000Z"
}

Typical two-step flow

# Step 1 — presign
RESULT=$(mcp call media_upload_presign '{"filename":"hero.jpg","mimeType":"image/jpeg","fileSize":204800}')
UPLOAD_URL=$(echo $RESULT | jq -r '.uploadUrl')
MEDIA_KEY=$(echo $RESULT | jq -r '.mediaKey')

# Step 2 — upload directly to S3
curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: image/jpeg" \
  --upload-file ./hero.jpg

# Step 3 — register
mcp call media_register "{\"mediaKey\":\"$MEDIA_KEY\",\"originalFilename\":\"hero.jpg\",\"mimeType\":\"image/jpeg\"}"

media_register#

Finalize a presigned-upload flow: HEAD the S3 object, verify the size cap, and insert a media row. Pairs with media_upload_presign.

  • Auth: media:write
InputTypeRequiredNotes
mediaKeystringyesMust start with media/
originalFilenamestringyes
mimeTypestringyes
altstringno
captionstringno
websiteIdnumberno
brandingProfileIdnumberno

Response — the new media row.


media_delete#

Permanently delete a media asset from the library (does not remove the S3 object).

  • Auth: media:write
InputTypeRequired
idnumberyes

Response{ "success": true, "id": 55 }


Nav changes use a draft overlay: nav_create, nav_update, and nav_delete all write to a draft JSON field and leave the live nav untouched until you call nav_publish or nav_publish_all.

List nav items for a website, sorted by sortOrder. Hierarchical via parentId.

  • Auth: sites:read
InputTypeRequired
websiteIdnumberyes

Response — array of nav rows including draft overlay if pending.


Stage a new nav item (draft only — hidden from the live nav until nav_publish).

  • Auth: sites:write
InputTypeRequiredNotes
websiteIdnumberyes
labelstringyes
hrefstringyes
parentIdnumbernoFor nested items
sortOrdernumberno
openInNewTabbooleanno
isButtonbooleannoRenders as a CTA button
descriptionstringno
iconstringnoMaterial Icon name

Stage changes to a nav item into its draft overlay. Live columns are untouched until nav_publish.

  • Auth: sites:write
InputTypeRequired
idnumberyes
labelstringno
hrefstringno
parentIdnumber | nullno
sortOrdernumberno
openInNewTabbooleanno
isButtonbooleanno
descriptionstring | nullno
iconstring | nullno

Stage a tombstone on a nav item (draft.pendingDelete). The row and live nav are unchanged until nav_publish runs.

  • Auth: sites:write
InputTypeRequired
idnumberyes

Promote a single nav item's draft to live. If pendingDelete: row is removed. If pendingCreate: item becomes visible. Otherwise: draft fields are applied to live columns.

  • Auth: sites:write
InputTypeRequired
idnumberyes

Promote every nav row with a non-null draft on a website in one call. Same per-row semantics as nav_publish.

  • Auth: sites:write
InputTypeRequired
websiteIdnumberyes

Response

{ "websiteId": 7, "count": 3, "items": [{ "id": 10, "published": true }, { "id": 11, "deleted": true }] }

Block Templates#

Block templates are reusable block trees you can insert into any post. They follow the same draft overlay pattern as navigation: create/update/delete stage into a draft jsonb field; block_templates_publish makes them live.

scope values:

  • block — copy-on-insert; post gets its own detached copy.
  • section — copy-on-insert at section level.
  • global — posts embed a live reference; when you update and publish the template, all embedders sync automatically (version is bumped).

block_templates_list#

List reusable block templates. Returns global (platform-curated) + your client's own templates.

  • Auth: sites:read
InputTypeRequiredNotes
category"custom" | "section" | "global"no
scope"block" | "section" | "global"no

Response — slim projection (no blocks JSON blob). Call block_templates_get for the full tree.


block_templates_get#

Fetch a full template including its blocks JSON.

  • Auth: sites:read
InputTypeRequired
idnumberyes

block_templates_create#

Create a new reusable block template (starts as a draft — hidden from the picker until published).

  • Auth: sites:write
InputTypeRequiredNotes
namestringyes
slugstringyesLowercase alphanumeric + hyphens; globally unique
blocksarrayyesAt least one block
descriptionstringno
categorystringnoDefault custom
scope"block" | "section" | "global"noDefault block
thumbnailstringnoURL
tagsstring[]no
lockedFieldsstring[]noField paths that can't be edited when the template is reused

Response — new template row + approval URL.


block_templates_update#

Stage changes to a block template into its draft overlay. Live columns and version are untouched until block_templates_publish.

  • Auth: sites:write
InputTypeRequired
idnumberyes
namestringno
descriptionstring | nullno
categorystringno
scope"block" | "section" | "global"no
blocksarrayno
thumbnailstring | nullno
tagsstring[]no
lockedFieldsstring[]no

block_templates_delete#

Stage a tombstone on a block template. Blocked if any posts currently embed it as a global template — remove or convert those usages first.

  • Auth: sites:write
InputTypeRequired
idnumberyes

block_templates_publish#

Promote a block template's draft to live. If pendingDelete: row is removed. If pendingCreate: template appears in picker. Otherwise: draft fields applied to live columns; version is bumped when blocks changed (triggers sync on global embedders).

  • Auth: sites:write
InputTypeRequired
idnumberyes

block_templates_fork#

Duplicate a published block template into a new draft tied to the original via parent_template_id. Use when you want to build a variant without modifying the source. Returns the new template id + an approval URL.

  • Auth: sites:write
InputTypeRequiredNotes
idnumberyesSource template id
nameSuffixstringnoDefault (fork)
slugSuffixstringnoAppended before the unique fork tag in the new slug

Custom Domains#

website_domains_list#

List custom domains attached to a website.

  • Auth: sites:read
InputTypeRequired
websiteIdnumberyes

Response

[{ "id": 3, "websiteId": 7, "domain": "acme.com", "isPrimary": true, "status": "pending" }]

website_domains_add#

Attach a custom domain to a website. Starts in pending status until DNS verification. Does not provision DNS records — you must configure them externally.

  • Auth: sites:write
InputTypeRequiredNotes
websiteIdnumberyes
domainstringyes
isPrimarybooleannoUnsets the existing primary if true

website_domains_remove#

Detach a custom domain. Does not affect external DNS.

  • Auth: sites:write
InputTypeRequired
idnumberyes

Environment Variables#

website_env_vars_list#

List env vars for a website environment. Values are included — treat output as secrets.

  • Auth: sites:read
InputTypeRequiredNotes
websiteIdnumberyes
environmentstringnoDefault production

Response — array of { id, key, value, syncedToVercel } rows, sorted by key.


website_env_vars_set#

Upsert an env var (creates or overwrites). Sets syncedToVercel: false — actual Vercel sync happens via the portal UI.

  • Auth: sites:write
InputTypeRequiredNotes
websiteIdnumberyes
environmentstringnoDefault production
keystringyes
valuestringyes

website_env_vars_delete#

Remove an env var by id.

  • Auth: sites:write
InputTypeRequired
idnumberyes

Custom Post Types#

Custom post types (CPTs) extend the CMS beyond blog and page. You can define custom field schemas, a block-tree template that wraps every post, and per-type CSS/JS. Reads use sites:read; writes use sites:write. Only site-owned CPTs are editable; built-in/global types (managed by admins) are read-only.

post_types_list#

List CPTs available on a website (site-specific + global built-ins).

  • Auth: sites:read
InputTypeRequiredNotes
websiteIdnumberyes
siteOnlybooleannotrue = exclude global built-ins

post_types_get#

Fetch a single CPT including its template and custom code.

  • Auth: sites:read
InputTypeRequired
websiteIdnumberyes
typeIdnumberyes

post_types_create#

Create a new CPT scoped to a website. Slug must be unique within the site and must not collide with global types.

  • Auth: sites:write
InputTypeRequiredNotes
websiteIdnumberyes
namestringyes
slugstringyesLowercase alphanumeric + hyphens
descriptionstring | nullno
iconstringnoMaterial Icon name; default article

Response — the new post type row.


post_types_update#

Update the name, slug, description, icon, or active flag of a site-owned CPT.

  • Auth: sites:write
InputTypeRequired
websiteIdnumberyes
typeIdnumberyes
namestringno
slugstringno
descriptionstring | nullno
iconstringno
activebooleanno

post_types_delete#

Permanently delete a site-owned CPT. Posts of that type cascade to deletion.

  • Auth: sites:write
InputTypeRequired
websiteIdnumberyes
typeIdnumberyes

post_types_get_template#

Get the block-tree template that wraps every post of this CPT. The template always contains exactly one { type: "post-content" } placeholder — at render time the post's own blocks are substituted in. Returns { template, defaulted: true } if no template has been saved yet.

  • Auth: sites:read
InputTypeRequired
websiteIdnumberyes
typeIdnumberyes

post_types_update_template#

Replace the block-tree template for a CPT. The server enforces "exactly one post-content placeholder" — extras are dropped (first wins), and a placeholder is prepended if absent. Pass template: null to reset to the default starter.

  • Auth: sites:write
InputTypeRequiredNotes
websiteIdnumberyes
typeIdnumberyes
templateobject | nullno{ blocks: [...], version?: string }

post_types_get_code#

Get the type-wide custom CSS and JS that cascades to every post of this CPT.

  • Auth: sites:read
InputTypeRequired
websiteIdnumberyes
typeIdnumberyes

Response{ "customCss": "...", "customJs": "..." }


post_types_update_code#

Update the type-wide custom CSS/JS. Pass an empty string to clear a field; omit to leave unchanged.

  • Auth: sites:write
InputTypeRequired
websiteIdnumberyes
typeIdnumberyes
customCssstringno
customJsstringno

post_types_fields_list#

List custom field definitions for a CPT, ordered by order. Children of repeater/group fields have parentId set.

  • Auth: sites:read
InputTypeRequired
websiteIdnumberyes
typeIdnumberyes

post_types_fields_create#

Add a custom field to a CPT.

  • Auth: sites:write
InputTypeRequiredNotes
websiteIdnumberyes
typeIdnumberyes
namestringyes
slugstringyes
fieldTypestringyestext, textarea, number, date, select, checkbox, url, email, image, user_select, repeater, group
parentIdnumber | nullnoFor children of repeater/group fields
optionsstring[]noRequired for select type
requiredbooleanno
defaultValuestring | nullno
helpTextstring | nullno
ordernumberno

post_types_fields_update#

Update a custom field. Reparenting (parentId) requires the new parent to be a repeater or group on the same CPT.

  • Auth: sites:write
InputTypeRequired
websiteIdnumberyes
typeIdnumberyes
fieldIdnumberyes
(any field from fields_create)no

post_types_fields_delete#

Delete a custom field. Stored values cascade. For repeater/group parents, child fields cascade too.

  • Auth: sites:write
InputTypeRequired
websiteIdnumberyes
typeIdnumberyes
fieldIdnumberyes

Branding#

Branding profiles hold your color palette, fonts, logos, and brand voice copy. Reads use branding:read; writes use branding:write.

branding_list_profiles#

List all branding profiles for your account.

  • Auth: branding:read
  • Inputs: none

Response — array of profile rows (id, name, isDefault, colors, fonts, logo URLs).


branding_get_profile#

Fetch a full branding profile (colors, fonts, logos, button style). Omit profileId to get the default profile.

  • Auth: branding:read
InputTypeRequired
profileIdnumberno

branding_get_messaging#

Fetch brand voice and copy context: tagline, value proposition, elevator pitch, tone, voice samples, differentiators.

  • Auth: branding:read
InputTypeRequired
profileIdnumberno

Response

{
  "tagline": "Build better, ship faster.",
  "valueProposition": "...",
  "elevatorPitch": "...",
  "toneOfVoice": "confident, approachable",
  "keyDifferentiators": ["no-code", "AI-first"],
  "targetAudience": "SMB agencies"
}

branding_audit#

Run the rule-based consistency audit on a branding profile. Returns WCAG contrast issues, missing-field warnings, and structural problems.

  • Auth: branding:read
InputTypeRequired
profileIdnumberyes

branding_check_contrast#

Compute the WCAG contrast ratio between two CSS colors. Returns the ratio plus AA/AAA pass/fail.

  • Auth: branding:read
InputTypeRequiredNotes
foregroundstringyesHex, rgb, or rgba
backgroundstringyesHex, rgb, or rgba

Response

{ "ratio": 4.56, "aa": true, "aaa": false }

branding_create_profile#

Create a new branding profile.

  • Auth: branding:write
InputTypeRequiredNotes
namestringyes
isDefaultbooleannotrue = unsets any existing default
primaryColorstringnoDefault #2563eb
secondaryColorstringnoDefault #1e40af
accentColorstringnoDefault #f59e0b
backgroundColorstringnoDefault #ffffff
textColorstringnoDefault #111827
headingFontstringno
bodyFontstringno
logoUrlstringno
logoTextstringno
logoSquareUrlstringno
logoRectUrlstringno
logoIconUrlstringno
logoAltstringno

branding_update_profile#

Update any combination of colors, fonts, logos, or the isDefault flag on an existing profile.

  • Auth: branding:write
InputTypeRequiredNotes
profileIdnumberyes
namestringno
isDefaultbooleanno
primaryColor / secondaryColor / accentColor / backgroundColor / textColorstringno
headingFont / bodyFontstring | nullno
logoUrl / logoText / logoSquareUrl / logoRectUrl / logoIconUrl / logoAltstring | nullno
borderRadiusstringno
linkColor / linkHoverColorstring | nullno

branding_delete_profile#

Permanently delete a branding profile. Sites that referenced it fall back to the client default.

  • Auth: branding:write
InputTypeRequired
profileIdnumberyes

branding_update_messaging#

Update brand voice / copy context (tagline, elevator pitch, value prop, tone, audience, differentiators). Creates the row if it does not exist yet. Pass profileId to scope messaging to a specific profile; omit for the client-level default.

  • Auth: branding:write
InputTypeRequired
profileIdnumberno
companyNamestringno
taglinestringno
missionStatementstringno
visionStatementstringno
valuePropositionstringno
elevatorPitchstringno
boilerplatestringno
toneOfVoicestringno
brandPersonalitystringno
writingStylestringno
keyDifferentiatorsstring[]no
targetAudiencestringno
industrystringno

Storefront#

Storefront tools are website-scoped — all reads require store:read, all writes require store:write.

Products#

store_products_list

List products for a website. Filter by status, category, featured flag, or search term.

  • Auth: store:read
InputTypeRequiredNotes
websiteIdnumberyes
status"draft" | "active" | "archived"no
categoryIdnumberno
featuredbooleanno
searchstringnoCase-insensitive match on name or SKU
limitnumbernoDefault 100, max 500

store_products_get

Fetch a product including all its images and variants.

  • Auth: store:read
InputTypeRequired
idnumberyes

Response

{
  "product": { "id": 1, "name": "T-Shirt", "price": 2999, "status": "active" },
  "images": [{ "id": 10, "url": "...", "order": 0 }],
  "variants": [{ "id": 20, "name": "Large / Red", "price": 2999 }]
}

store_products_create

Create a new product. Price is in cents. Starts in draft status — use store_products_update to activate.

  • Auth: store:write
InputTypeRequiredNotes
websiteIdnumberyes
namestringyes
pricenumberyesCents
slugstringnoDerived from name if omitted
descriptionstringno
shortDescriptionstringno
compareAtPricenumbernoCents
skustringno
categoryIdnumberno
trackInventorybooleannoDefault true
quantitynumbernoDefault 0
weightnumberno
weightUnit"g" | "kg" | "oz" | "lb"no
tagsstring[]no
featuredbooleanno
status"draft" | "active" | "archived"noDefault draft

store_products_update

Update any mutable field on a product. For images and variants, use their dedicated tools.

  • Auth: store:write
InputTypeRequiredNotes
idnumberyes
namestringno
slugstringno
descriptionstring | nullno
shortDescriptionstring | nullno
pricenumbernoCents
compareAtPricenumber | nullno
skustring | nullno
categoryIdnumber | nullno
trackInventorybooleanno
quantitynumbernoAbsolute quantity — for delta use store_products_adjust_inventory
tagsstring[]no
featuredbooleanno
status"draft" | "active" | "archived"no

store_products_delete

Permanently delete a product. Images and variants cascade. Order items retain historical data via productName/variantName.

  • Auth: store:write
InputTypeRequired
idnumberyes

store_products_adjust_inventory

Adjust product quantity by a positive or negative delta. Use store_products_update to SET an absolute quantity.

  • Auth: store:write
InputTypeRequiredNotes
idnumberyes
deltanumberyese.g. -3 to decrement, +10 to restock. Returns error if result would go below 0.

store_product_options_create

Add a product option axis (e.g. "Size", "Color"). Use store_product_option_values_create to add its values.

  • Auth: store:write
InputTypeRequired
productIdnumberyes
namestringyes
ordernumberno

store_product_option_values_create

Add a value (e.g. "Red", "Large") to an existing product option.

  • Auth: store:write
InputTypeRequired
optionIdnumberyes
valuestringyes
labelstringno
ordernumberno

store_product_variants_create

Create a product variant. optionValues ties the variant to specific option values (e.g. Size=Large, Color=Red). Price in cents.

  • Auth: store:write
InputTypeRequiredNotes
productIdnumberyes
namestringyes
pricenumberyesCents
skustringno
compareAtPricenumberno
quantitynumberno
optionValuesArray<{ optionId: number, valueId: number }>no
imagestringnoURL

store_product_variants_update

Update any mutable field on a variant.

  • Auth: store:write
InputTypeRequired
idnumberyes
namestringno
skustring | nullno
pricenumberno
compareAtPricenumber | nullno
quantitynumberno
activebooleanno
imagestring | nullno

Product Categories#

store_categories_list

List product categories for a website.

  • Auth: store:read
InputTypeRequired
websiteIdnumberyes

store_categories_create

Create a product category. Supports parent/child hierarchy via parentId.

  • Auth: store:write
InputTypeRequiredNotes
websiteIdnumberyes
namestringyes
slugstringnoDerived from name if omitted
descriptionstringno
parentIdnumberno
imagestringnoURL

Orders#

store_orders_list

List orders for a website. Filter by status, payment status, customer email, or date.

  • Auth: store:read
InputTypeRequiredNotes
websiteIdnumberyes
statusstringnopending, processing, shipped, delivered, cancelled, refunded
paymentStatusstringnopending, paid, failed, refunded
customerEmailstringno
sincestringnoISO datetime — only orders created after this
limitnumbernoDefault 100, max 500

store_orders_get

Fetch order detail: the order row, line items, and full status history.

  • Auth: store:read
InputTypeRequired
idnumberyes

Response

{
  "order": { "id": 55, "orderNumber": "ORD-0055", "status": "processing", "total": 5998 },
  "items": [{ "id": 1, "productName": "T-Shirt", "quantity": 2, "unitPrice": 2999 }],
  "history": [{ "status": "pending", "createdAt": "..." }, { "status": "processing", "createdAt": "..." }]
}

store_orders_update_status

Transition an order through fulfillment states. Stamps shippedAt/deliveredAt automatically. Does not send customer notifications.

  • Auth: store:write
InputTypeRequiredNotes
idnumberyes
statusstringyespending, processing, shipped, delivered, cancelled, refunded
notestringnoStored in status history
trackingNumberstringno
trackingUrlstringno
shippingMethodstringno

store_analytics_get

Return order and revenue aggregates for a storefront over a time window: total revenue, order count, average order value, and order counts by status. The siteId must belong to the authenticated client.

  • Auth: store:read
InputTypeRequiredNotes
siteIdnumberyesWebsite whose store to analyse
daysnumbernoLook-back window in days, 1–365 (default 30)

Response

{
  "totalRevenue": 124900,
  "totalOrders": 42,
  "averageOrderValue": 2974,
  "ordersByStatus": { "processing": 5, "shipped": 30, "delivered": 7 },
  "windowDays": 30
}

store_orders_add_note

Append or overwrite the internal staff-only note on an order. Does not affect customer-facing fields.

  • Auth: store:write
InputTypeRequiredNotes
idnumberyes
notestringyes
mode"append" | "replace"noDefault append

Customers#

store_customers_list

List storefront customers. Filter by status or search by email/name.

  • Auth: store:read
InputTypeRequiredNotes
websiteIdnumberyes
status"active" | "disabled"no
searchstringno
limitnumbernoDefault 100, max 500

Response — slim projection: id, email, first/last name, phone, status, orderCount, totalSpent.


store_customers_get

Fetch a store customer (PII-included but no password hash). Returns up to 10 recent orders.

  • Auth: store:read
InputTypeRequired
idnumberyes

Discounts#

store_discounts_list

List discount codes for a website.

  • Auth: store:read
InputTypeRequiredNotes
websiteIdnumberyes
activeOnlybooleannoDefault false

store_discounts_create

Create a discount code. percent amounts are in basis points (1000 = 10%); fixed_amount amounts are in cents.

  • Auth: store:write
InputTypeRequiredNotes
websiteIdnumberyes
codestringyesStored uppercased
discountType"percent" | "fixed_amount" | "free_shipping"yes
amountnumberyesBasis points for percent; cents for fixed_amount; 0 for free_shipping
descriptionstringno
minOrderAmountnumbernoCents
maxUsesnumberno
startsAtstringnoISO datetime
expiresAtstringnoISO datetime
applicableTo"store" | "booking" | "both"noDefault store

store_discounts_toggle

Flip the active flag on a discount code.

  • Auth: store:write
InputTypeRequired
idnumberyes
activebooleanyes

store_discounts_delete

Permanently delete a discount code.

  • Auth: store:write
InputTypeRequired
idnumberyes

Reviews#

store_reviews_list

List product reviews across a website. Filter by approval status and/or product.

  • Auth: store:read
InputTypeRequiredNotes
websiteIdnumberyes
status"pending" | "approved" | "rejected"no
productIdnumberno
limitnumbernoDefault 100, max 500

store_reviews_moderate

Approve or reject a product review.

  • Auth: store:write
InputTypeRequired
idnumberyes
action"approve" | "reject"yes

Customer Support Messages#

store_customer_messages_list

List customer support messages for a website.

  • Auth: store:read
InputTypeRequiredNotes
websiteIdnumberyes
statusstringnoe.g. open, replied
limitnumbernoDefault 50, max 200

store_customer_messages_reply

Post a staff reply on a customer support thread. Does not email the customer.

  • Auth: store:write
InputTypeRequired
messageIdnumberyes
bodystringyes

Store Settings#

store_settings_get

Get storefront configuration for a website (currency, tax, shipping, payout schedule, enabled flag).

  • Auth: store:read
InputTypeRequired
websiteIdnumberyes

Common Errors#

Response fieldMeaning
{ "error": "Site not found" }websiteId doesn't exist or belongs to another client
{ "error": "Permission denied" }Resource belongs to a different tenant
Permission denied: this API key lacks the "X" scope. (isError)Your API key is missing the required scope
{ "error": "Post not found" }id not found
{ "error": "Could not create (likely duplicate slug): ..." }Unique constraint violation
{ "pending": true, ... }Write staged for approval; follow approval.url