Deploy & hosting

Cartbase builds and hosts your storefront. You send the app's source files — one snapshot of the whole project — and Cartbase turns it into a running site: first on a permanent preview URL, then, when you press Publish (or call the publish endpoint), on the store's live domain. You never touch build servers, DNS, or hosting configuration; the platform provisions all of it on your first deploy.

Two URLs exist per store, both created automatically:

Surface URL What serves there
Preview https://preview--{store-slug}.cartbase.net The newest deploy, always — the link never changes
Live https://{store-slug}.cartbase.net The last published deploy, and nothing else

A failed build never replaces what's live or what's on preview — the previous successful deploy keeps serving. Custom domains for the live site attach in the admin (Settings → Domains) and don't change anything on this page.

These are Admin API endpoints — they operate the store, so they authenticate with an admin session (sign-in cookie), not the storefront headers the rest of this corpus uses. In practice you'll deploy through the admin UI (Storefront in the sidebar), the Cartbase CLI, or an agent connection — all three are wrappers over exactly these calls. Users who belong to several stores name the target store with an x-barter-store: <client-id> header.

What a deploy is

A deploy is a full snapshot: the complete file tree of your storefront app, uploaded in one request. Snapshot semantics cut both ways — a file you stop sending is removed from the deployed site. There is no partial-update mode; your working directory is the truth, every time.

Bundle rules (enforced server-side; a violation fails the whole deploy with a readable 400 before anything is built):

  • ≤ 1500 files, ≤ 3 MB per file, ≤ 25 MB total (base64-decoded sizes)
  • package.json must exist at the bundle root — the app must be a buildable Next.js project (next build)
  • File content travels base64-encoded (content_base64), so binary assets — images, fonts — work
  • Never include: node_modules/ (dependencies install at build time), .next/ (build output is generated), .git/, any .env* file
  • Not allowed in storefront bundles: middleware.*, app/api/ or pages/api/ routes, vercel.json — a storefront is pages plus the @cartbase/storefront package; server seams and hosting config are platform territory. The CORS proxy from the runbook is a next.config rewrite, which is fine.

Error codes you can hit: empty_bundle, bundle_too_large, invalid_bundle_file, invalid_path, blocked_path, duplicate_path, invalid_encoding, file_too_large, missing_package_json.

Environment — provided, not configured

Hosted storefronts receive the three runbook inputs automatically at build time; do not put them in the bundle (.env* files are blocked anyway):

Variable Value
NEXT_PUBLIC_BARTER_URL The store's API origin
NEXT_PUBLIC_BARTER_CLIENT_ID The store's client id
NEXT_PUBLIC_BARTER_PUBLISHABLE_KEY The store's publishable key, when one exists

Only these public values ever reach a storefront build — secret keys are never injected, so code that expects one is a design error.

Deploy to preview

POST /api/admin/storefront/deploys — the one ingestion door. The first call on a store also provisions its hosting (takes a few extra seconds); every later call is just a deploy.

# doc-noexec — admin-session auth; run from an authenticated context.
curl -s -X POST "$BASE/api/admin/storefront/deploys" \
  -H "content-type: application/json" \
  -d '{
    "files": [
      { "path": "package.json",  "content_base64": "<base64>" },
      { "path": "app/layout.js", "content_base64": "<base64>" },
      { "path": "app/page.js",   "content_base64": "<base64>" }
    ],
    "message": "homepage copy update"
  }'

Response 201:

{
  "deploy": {
    "id": "sfd_01J…",
    "target": "preview",
    "status": "building",
    "url": "https://preview--my-store.cartbase.net",
    "message": "homepage copy update",
    "file_count": 3,
    "created_at": "2026-08-03T22:41:00.000Z"
  }
}

message (≤ 500 chars) is your label in the deploy history — write what changed. source (api | cli | mcp | admin | agent) tags where the deploy came from; omit it unless you're building tooling.

Statuses move queued → building → ready (or failed / canceled). Poll the overview endpoint until the deploy you created is ready, then open the preview URL. Builds of a small app land in roughly a minute; failed rows carry a readable error_message.

Overview — hosting status + deploy history

GET /api/admin/storefront returns the hosting record and the deploy ledger, newest first:

# doc-noexec — admin-session auth.
curl -s "$BASE/api/admin/storefront"
{
  "storefront": {
    "id": "sfp_01J…",
    "status": "ready",
    "production_url": "https://my-store.cartbase.net",
    "preview_url": "https://preview--my-store.cartbase.net",
    "last_error": null,
    "created_at": "…"
  },
  "deploys": [
    {
      "id": "sfd_01J…",
      "target": "preview",
      "status": "ready",
      "message": "homepage copy update",
      "url": "https://preview--my-store.cartbase.net",
      "source": "cli",
      "file_count": 3,
      "total_bytes": 1930,
      "error_message": null,
      "promoted_deploy_id": null,
      "created_at": "…",
      "updated_at": "…"
    }
  ]
}

storefront is null until the store's first deploy. status: "provisioning" / "failed" (with last_error) describe hosting setup, not builds; a failed provisioning resumes automatically on the next deploy attempt.

Publish — and rollback

POST /api/admin/storefront/publish promotes a ready preview deploy to the live domain. This is the only path to production — nothing deploys straight to live, no matter who's asking.

# doc-noexec — admin-session auth.
# Bare POST publishes the newest ready preview:
curl -s -X POST "$BASE/api/admin/storefront/publish"

# Naming an older deploy id IS the rollback mechanism:
curl -s -X POST "$BASE/api/admin/storefront/publish" \
  -H "content-type: application/json" \
  -d '{ "deploy_id": "sfd_01H…older…" }'

Response 201 is a new ledger row with target: "production" and promoted_deploy_id pointing at the preview it promoted. Publishing is near-instant — the snapshot was already built; the live domain switches to it without a rebuild.

Rollback is not a separate feature: publish any earlier ready deploy from the history and the live site is that snapshot again. Roll forward the same way.

Workflow summary

  1. Build locally against your store (the runbook).
  2. Deploy → check the permanent preview URL.
  3. Iterate — every deploy replaces preview, live is untouched.
  4. Publish when it's right; the deploy history is your undo button.