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...
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
| Method | Path | Returns |
|---|---|---|
| GET | /store | Store info: name, currency, support email, logo URL |
| GET | /packages | All active packages (paginated, see below) |
| GET | /packages/:id | Single package with full deliverable metadata |
| GET | /categories | All visible categories |
| GET | /categories/:id/packages | Packages in a category |
| GET | /community-goals | Active fundraising goals + progress |
| GET | /active-sales | Currently-running sales |
| GET | /pages | Your custom CMS pages |
| GET | /pages/:slug | Single CMS page (HTML body) |
| GET | /gateways | Enabled payment gateways (for rendering payment-method picker) |
| GET | /lookup-player/:username | Resolve a Minecraft username to UUID via Mojang / Geyser |
Basket endpoints
| Method | Path | Action |
|---|---|---|
| POST | /baskets | Create a new basket; returns a basket ident |
| GET | /baskets/:ident | Fetch full basket state (items, totals, applied codes) |
| POST | /baskets/:ident/items | Add a package; returns updated basket |
| PATCH | /baskets/:ident/items/:packageId | Update quantity |
| DELETE | /baskets/:ident/items/:packageId | Remove from basket |
| POST | /baskets/:ident/coupon | Apply a coupon code (body: { code }) |
| DELETE | /baskets/:ident/coupon | Remove the applied coupon |
| POST | /baskets/:ident/gift-card | Apply a gift-card code |
| POST | /baskets/:ident/checkout | Initiate 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
detailsfor field errors) - E1100 — Auth token invalid or revoked
- E1500 — Rate limit exceeded
Rate limits
| Plan | Limit |
|---|---|
| Free | 60 requests/minute per token |
| Pro | 300 requests/minute per token |
| Business | 1,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.
Updated recently