Skip to main content

Publishing

POST /v1/storefronts/:storefrontId/publish takes a draft storefront live. It is the only operation that exposes the storefront to the public internet — every other catalog mutation only changes the draft. Required scope: storefront:publish (held by user keys after verification; not held by developer keys, not held by restricted pre-verify user keys).

Behavior

The handler enforces the following gates in this order: The plan paywall and ToS gate are account-level checks that fire before any storefront read, so an unauthorized caller cannot probe whether a storefront id exists by status-code timing. The ownership pre-check runs before the empty-products check for the same reason: a wrong-user-key probe cannot distinguish “empty storefront I own” from “storefront I don’t own”.

Request

Body is optional. Pass { "versionId": "ver_xxx" } to publish a specific named version; omit for auto-version (the common case). Include Idempotency-Key if you want the call to be safe to retry — see Safe mutations.

Successful response (200)

_links.publicUrl is now non-null and stable. Surface it to the user.

Republishing

Calling publish a second time on the same storefront:
  • If the version already published matches the request, the call is idempotent: the same 200 + DTO is returned.
  • If a new version is created (auto-version with new content), a fresh public snapshot is generated.
This means an agent can call publish on every “save” without worrying about the user seeing a flicker — publish is cheap when nothing changed.

Error responses

402 plan_blocks_publish — pre-paywall account

Today only the NO_ACTIVO (pre-paywall) state hits this. Free, Basic, Pro, and Business all publish successfully. Surface upgrade.upgradeUrl as a CTA. Do not retry until the user upgrades.

422 no_products — empty storefront

Add at least one product, then retry publish with the same Idempotency-Key.

451 tos_not_accepted — user hasn’t accepted ToS

The user must accept the ToS via the dashboard modal — the agent cannot bypass. Surface nextActions[0].url verbatim. Once accepted, retry publish with the same Idempotency-Key. See ToS jurisdiction.

404 — storefront not found OR cross-tenant access

If your mk_user_* key doesn’t own the storefront, you get 404 storefront_not_found (silent denial — not 403). See Storefronts.

What NOT to do

  • Don’t auto-publish. Publishing is destructive and user-visible — once it’s live, the URL is shared and may be indexed. Always require explicit user confirmation.
  • Don’t retry 402 / 422 / 451 without surfacing nextActions[] to the user. Each is a recoverable: true error that needs user input, not a backoff.
  • Don’t auto-accept the ToS. The 451 → modal flow is counsel-reviewed and intentional.

Verification in code

  • src/api/v1/storefronts.publish.ts — the handler with the locked step order.
  • src/utils/publish.utils.tsrunPublishTransaction.
  • src/models/plan-limits.tsisPublishable() (the 402 gate condition).