Author and publish a hosted storefront
A hosted store is data, not a fork. Everything a visitor sees — the brand, the theme tokens, and the ordered list of page sections — is one JSON document, the manifest, rendered by one multi-tenant fleet renderer. You author the manifest, save it as a draft, preview it, then publish. Publishing an older version is how you roll back.
This guide walks the whole loop with an fdk_ tenant credential. The authoring
routes live under /v1/my/*; the tenant is resolved from the credential,
never from a path or body parameter.
These are merchant routes on the commerce API, authenticated with your fdk_
key as Authorization: Bearer. They are separate from the checkout-only
storefront token a headless frontend uses (an opaque secret, no fixed prefix,
provisioned by the platform operator; platform-wide today) — see
Build your own frontend.
Before you start
- An
fdk_tenant credential with theread:own_storeandwrite:own_storescopes (a pre-live build key carries both). - The API origin —
https://api.ruo.pro. - Your tenant ref (this guide uses
nova-peptide).
Steps
Read the current site
GET /v1/my/site is one honest read: the active manifest, the latest draft (the
newest version the active pointer does not reference, null when the newest
version is already live), and the full version history.
curl "https://api.ruo.pro/v1/my/site" -H "Authorization: Bearer fdk_your_key_here"
# -> { "active": { "version": 1, ... }, "draft": null, "versions": [ ... ] }Author the manifest as data
The manifest names the blocks the renderer relies on — store, brand,
theme, template.sections, seo, behavior — and preserves any extra
properties verbatim. Each section is a registry type plus an open props bag:
{
"brand": { "name": "Nova Peptide" },
"theme": { "tokens": { "color.brand": "#0b6" } },
"template": {
"id": "base-v1",
"sections": [
{ "type": "announcement-bar", "props": { "text": "Free shipping over $150" } },
{ "type": "header-nav", "props": {} },
{ "type": "epic-hero", "props": { "heading": "Research-grade peptides" } },
{ "type": "product-grid", "props": { "collection": "peptides" } },
{ "type": "legal-footer", "props": {} }
]
}
}See The manifest and sections for the block reference and Theming for how theme tokens map to CSS custom properties.
Save a draft
PUT /v1/my/site/draft writes a new version row without moving the active
pointer, so nothing a visitor sees changes yet. It returns the version it minted:
curl -X PUT "https://api.ruo.pro/v1/my/site/draft" \
-H "Authorization: Bearer fdk_your_key_here" \
-H "content-type: application/json" \
-d '{ "manifest": { "brand": { "name": "Nova Peptide" }, "template": { "sections": [] } } }'{ "version": 2, "rowHash": "…" }The draft save is where the RUO-claims guardrail scan runs. The scanner
walks every string in the manifest tree for prohibited claim language and is
fail-closed: a scanner error is a 503 (“scan did not run”), never a silent
pass. A CRITICAL finding is a hard 422 and no version row is written —
there is no merchant override.
On a 422 ruo_claims_violation, fix the offending copy and re-save — do not
look for a bypass header. The x-guardrails-override escape hatch exists only
on the operator publish route, which is not merchant-facing. If a finding is a
false positive, contact the platform operator to review.
The finding body names exactly what tripped the scan — one entry per finding
with { path, term, rule, severity, context }.
Preview the draft
POST /v1/my/site/preview-token mints a short-lived signed token for one
version. Nothing is written to the database — the expiry rides in the signed
payload, and the token grants a render of exactly that immutable version:
curl -X POST "https://api.ruo.pro/v1/my/site/preview-token" \
-H "Authorization: Bearer fdk_your_key_here" \
-H "content-type: application/json" \
-d '{ "version": 2 }'{ "token": "…", "version": 2, "expiresAt": "2026-09-14T12:30:00.000Z" }The renderer resolves it through the public GET /v1/preview-resolve?token=<token>
route (served with Cache-Control: no-store). Expired, tampered, malformed, or
unknown-version tokens all resolve to the same 404 invalid_preview_token — one
code, no oracle, and no cross-tenant preview.
Publish
POST /v1/my/site/publish moves the active pointer to a version. Publishing
does not re-scan — every version already passed the guardrail scan on its write:
curl -X POST "https://api.ruo.pro/v1/my/site/publish" \
-H "Authorization: Bearer fdk_your_key_here" \
-H "content-type: application/json" \
-d '{ "version": 2 }'{ "version": 2, "rowHash": "…" }An unknown version is a 404 version_not_found. The moment this returns, the
public read GET /v1/tenants/nova-peptide/site serves the new active manifest.
Roll back
Because any existing version can be published, rollback is the same route —
re-publish the earlier version ({ "version": 1 }). The version table is
append-only, so a rollback never destroys the version you rolled away from;
publish it again to roll forward.
The public read
One unauthenticated route serves the live store: GET /v1/tenants/{tenant}/site
returns the active manifest for a live store and nothing else.
{ "tenant_ref": "nova-peptide", "version": 2, "manifest": { /* … */ } }It is fail-closed: a draft or suspended store, or an unknown tenant, is an
honest 404 unknown_tenant (never an empty manifest); a failed read is a 503,
never absence. The response carries an ETag (the manifest row hash) so a
conditional If-None-Match request answers 304 when the active version is
unchanged. The SDK’s store.site.get() wraps this exact route.
Related
- Site builder reference — every route, response shape, and the guardrail gate in full.
- The manifest and sections — the manifest block and section-type reference.
- Theming — theme tokens and the preset matrix.
- Errors — the nested error body and status-code meanings.