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
{ "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.
Error responses
402 plan_blocks_publish — pre-paywall account
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
Idempotency-Key.
451 tos_not_accepted — user hasn’t accepted ToS
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 yourmk_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 arecoverable: trueerror 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.ts—runPublishTransaction.src/models/plan-limits.ts—isPublishable()(the 402 gate condition).