Docs/Developer API/Headless API

Headless API

Read your store's products, categories, and customer data via REST so you can build a custom storefront.

The Headless API is a public REST surface for reading your store's data and driving customer-facing basket flows. Use it to:

  • Power a custom storefront on Next.js, Astro, SvelteKit, or vanilla JS
  • Embed Storra products in an existing website
  • Build a mobile app with native checkout
  • Sync your catalog into a Discord bot, Twitch panel, or other live surface

Base URL

https://<your-store>.storra.xyz/api/headless/

Authentication

Bearer-token auth using a public token from API Keys. Tokens prefixed pk_ are publishable — safe to embed in client-side JS. They grant read access to store data and write access to baskets only (which is the whole point of customer-facing API surfaces).

Authorization: Bearer pk_live_abc123...
Don't confuse with secret keysSecret keys (sk_) are for the Checkout API and grant server-only privileges. Never expose them to a browser. If your token starts with sk_, you're using the wrong API for client-side work.

Endpoints

Read endpoints

MethodPathReturns
GET/storeStore info: name, currency, support email, logo URL
GET/packagesAll active packages (paginated, see below)
GET/packages/:idSingle package with full deliverable metadata
GET/categoriesAll visible categories
GET/categories/:id/packagesPackages in a category
GET/community-goalsActive fundraising goals + progress
GET/active-salesCurrently-running sales
GET/pagesYour custom CMS pages
GET/pages/:slugSingle CMS page (HTML body)
GET/gatewaysEnabled payment gateways (for rendering payment-method picker)
GET/lookup-player/:usernameResolve a Minecraft username to UUID via Mojang / Geyser

Basket endpoints

MethodPathAction
POST/basketsCreate a new basket; returns a basket ident
GET/baskets/:identFetch full basket state (items, totals, applied codes)
POST/baskets/:ident/itemsAdd a package; returns updated basket
PATCH/baskets/:ident/items/:packageIdUpdate quantity
DELETE/baskets/:ident/items/:packageIdRemove from basket
POST/baskets/:ident/couponApply a coupon code (body: { code })
DELETE/baskets/:ident/couponRemove the applied coupon
POST/baskets/:ident/gift-cardApply a gift-card code
POST/baskets/:ident/checkoutInitiate checkout; returns gateway redirect_url

Pagination

List endpoints support ?page=1&limit=20 query params (defaults shown). Responses include a meta envelope:

{
  "data": [...],
  "meta": { "total": 47, "page": 1, "limit": 20, "pages": 3 }
}

Use limit=100 max. For larger pulls, paginate.

Quickstart: list packages

// JavaScript (browser or Node)
const res = await fetch(
  "https://your-store.storra.xyz/api/headless/packages",
  { headers: { Authorization: "Bearer pk_live_..." } }
);
const { data: packages } = await res.json();
console.log(`Loaded ${packages.length} packages`);
# curl
curl -H "Authorization: Bearer pk_live_..." \
  https://your-store.storra.xyz/api/headless/packages

Quickstart: full custom checkout flow

// 1. Create a basket
const basket = await fetch(`${BASE}/baskets`, {
  method: "POST",
  headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json" },
  body: JSON.stringify({
    email: "buyer@example.com",
    username: "BuyerMC",
    complete_url: "https://my-app.com/thanks",
    cancel_url: "https://my-app.com/cart",
  }),
}).then((r) => r.json());

// 2. Add an item
await fetch(`${BASE}/baskets/${basket.ident}/items`, {
  method: "POST",
  headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json" },
  body: JSON.stringify({ package_id: "pkg_abc123", quantity: 1 }),
});

// 3. Initiate checkout
const { redirect_url } = await fetch(`${BASE}/baskets/${basket.ident}/checkout`, {
  method: "POST",
  headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json" },
  body: JSON.stringify({ gateway: "stripe" }),
}).then((r) => r.json());

// 4. Send the customer to the gateway
window.location.href = redirect_url;

Response shape

All responses are JSON. Successful responses return 200 OK with { "data": ... } or { "data": ..., "meta": ... }. Errors return appropriate HTTP status codes with:

{
  "error": {
    "code": "E2001",
    "message": "Package not found",
    "details": { ... }
  }
}

Error codes

Every error includes a Storra error code prefixed E####. The most common headless-API codes:

  • E2001 — Resource not found (package, category, basket)
  • E2010 — Basket already checked out (cannot modify)
  • E2020 — Coupon invalid or expired
  • E2030 — Gift card invalid or zero balance
  • E0002 — Input validation failed (see details for field errors)
  • E1100 — Auth token invalid or revoked
  • E1500 — Rate limit exceeded

Rate limits

PlanLimit
Free60 requests/minute per token
Pro300 requests/minute per token
Business1,200 requests/minute per token

Limit is per-token, not per-IP. Burst-friendly: limits use a token-bucket algorithm with a 10-second burst capacity. When exceeded, responses return 429 Too Many Requests with a Retry-After header indicating seconds until the next slot opens.

Caching

Read-only responses (/packages, /categories, /store) include Cache-Control: public, max-age=60, stale-while-revalidate=300. Use a CDN in front of your storefront for additional caching — the API is happy to be cached by edge networks.

Basket endpoints are Cache-Control: no-store. Don't cache them.

CORS

The headless API responds to Origin: * with Access-Control-Allow-Origin: *. It's intended for client-side use, so CORS is permissive. If you want to lock it down to a specific origin (defense in depth), allowlist your origin in API Keys → Token settings → Allowed origins.

Webhooks vs polling

The Headless API is request-response. If you want push notifications when orders complete, use outbound webhooks instead — your server gets POST'd whenever a relevant event fires.

FAQ

Can I read order history with the headless API?

No. Order data is private (contains customer PII). Use the Checkout API with a secret key from your server to query orders.

Are publishable tokens safe to ship in a mobile app binary?

Yes — they're designed for it. They can read public store data and create baskets, nothing destructive. The worst-case attacker scenario is "they can run your storefront", which they could do anyway by visiting your store URL.

Does the API support GraphQL?

Not at launch. The REST surface is intentionally small + flat — tracking the GraphQL maintenance burden for a single-vertical commerce product wasn't a priority. If you want a typed client, use OpenAPI codegen against the spec at /api/headless/openapi.json.

Where's the OpenAPI spec?

At https://your-store.storra.xyz/api/headless/openapi.json. Generate clients with openapi-typescript, openapi-generator, or paste the URL into Postman.

Was this page helpful?Suggest an edit →

Updated recently