Build a storefront
An end-to-end walkthrough of building your own e-commerce frontend on the Storefront API — from rendering the shop to a completed order. Every endpoint shown here is documented in full (schemas, parameters, live playground) in the API reference.
Before you start
You need your business API key (sb_live_*), sent as X-Periscale-Key on every request. That single header identifies the store — there is nothing else to configure. Keep it server-side; see Authentication.
Build against the sandbox first, then change one constant to go live:
const API = process.env.NODE_ENV === "production"
? "https://storefront.periscale.app"
: "https://dev-storefront.periscale.app";Proxy pattern
In a browser app, don't call the API directly from the client — the key would leak. Route calls through your own backend (a Next.js route handler, an Express route, …) that injects X-Periscale-Key and, when the shopper is logged in, forwards their Authorization header.
The snippets below assume two small helpers: api() for key-only calls and shopperApi() for calls that also carry the shopper's Bearer token.
1. Load the store configuration
Everything needed to render the shell of the shop — identity, theme, hero, navigation, footer, payment methods, announcement bar — comes from one call:
curl https://dev-storefront.periscale.app/api/v1/business/website/config/ \
-H "X-Periscale-Key: sb_live_..."const res = await fetch(`${API}/api/v1/business/website/config/`, {
headers: { "X-Periscale-Key": process.env.PERISCALE_KEY },
});
const { data: config } = await res.json();The response is cached server-side, so call it freely on every page render. → Reference: Get store config
2. Render the catalog
List products, fetch one by slug, and browse categories. Lists come back as { items, next_cursor, has_more } — see Pagination & fields:
curl "https://dev-storefront.periscale.app/api/v1/business/website/products/?sort=newest&limit=24" \
-H "X-Periscale-Key: sb_live_..."
curl https://dev-storefront.periscale.app/api/v1/business/website/products/classic-tee/ \
-H "X-Periscale-Key: sb_live_..."const { items, next_cursor, has_more } =
(await api("/business/website/products/?sort=newest&limit=24")).data;
const product = (await api("/business/website/products/classic-tee/")).data;
const categories = (await api("/business/website/categories/")).data.items;Sort with ?sort= — newest (default), popular, price_asc, price_desc. For a listing grid you rarely need the full product object; ask for just the card fields and the response gets both smaller and faster to produce:
await api("/business/website/products/?fields=id,slug,name,price,images");For the home page, dedicated endpoints serve curated sections: top-products/, new-arrivals/, flash-deals/, bundle-products/, featured-reviews/. → Reference: Catalog
3. Register and log in shoppers
Shopper accounts use the customer auth endpoints, with the same API key as every other call:
curl -X POST https://dev-storefront.periscale.app/api/v1/customer/auth/login/ \
-H "X-Periscale-Key: sb_live_..." \
-H "Content-Type: application/json" \
-d '{ "email": "shopper@example.com", "password": "…" }'const { data } = await fetch(`${API}/api/v1/customer/auth/login/`, {
method: "POST",
headers: {
"X-Periscale-Key": process.env.PERISCALE_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({ email, password }),
}).then((r) => r.json());
// data.access_token, data.refresh_token, data.customerStore data.access_token and send it as Authorization: Bearer <token> on all shopper actions below; renew it with POST /api/v1/customer/auth/refresh/ (body: { "refresh_token": "…" }). → Reference: Shopper authentication
4. Cart
All cart calls need both headers — the key and the shopper's Bearer token:
curl -X POST https://dev-storefront.periscale.app/api/v1/business/website/cart/items/ \
-H "X-Periscale-Key: sb_live_..." \
-H "Authorization: Bearer <access>" \
-H "Content-Type: application/json" \
-d '{ "product_id": 42, "quantity": 2 }'await shopperApi("/business/website/cart/items/", {
method: "POST",
body: JSON.stringify({ product_id: 42, quantity: 2 }),
});
const cart = await shopperApi("/business/website/cart/");Use cart/summary/ for a lightweight totals-only payload (badge counters), PATCH cart/items/{item_id}/ to change quantities, and DELETE cart/clear/ to empty the cart. → Reference: Cart
5. Checkout
- (Optional) validate a coupon:
POST coupon/validate/ - (Optional, for cash on delivery) run the risk check:
POST cod-risk-check/ - Create the order from the cart:
curl -X POST https://dev-storefront.periscale.app/api/v1/business/website/order/ \
-H "X-Periscale-Key: sb_live_..." \
-H "Authorization: Bearer <access>" \
-H "Content-Type: application/json" \
-d '{ "payment_method": "cod", "shipping_address": { } }'const order = await shopperApi("/business/website/order/", {
method: "POST",
body: JSON.stringify(checkoutPayload),
});For gateway payments (bKash/Nagad), follow the order with POST payment/initiate/ (returns a redirect URL) and confirm with POST payment/verify/ after the shopper returns. → Reference: Checkout & payment
Test checkout in the sandbox
Sandbox orders are real rows in the sandbox store — safe to create, cancel and refund freely. Point at production only once the flow is settled.
6. Post-purchase account area
Everything a typical "My account" section needs, all shopper-token protected:
| Feature | Endpoints |
|---|---|
| Order history & tracking | GET orders/, GET orders/{order_id}/requests/ |
| Cancellations & refunds | POST orders/cancel/, POST orders/refund/, GET orders/refunds/ |
| Wishlist | GET/POST/DELETE wishlist/ |
| Addresses | GET/POST addresses/, PUT/DELETE addresses/{address_id}/ |
| Reviews | POST reviews/, GET my-reviews/ |
| Support tickets | GET/POST tickets/, GET tickets/{ticket_id}/, POST tickets/{ticket_id}/messages/ |
7. Content pages
Render store pages (pages/, pages/{slug}/) and the blog (blog/, blog/{slug}/), and wire the newsletter (POST subscribe/) and contact form (POST contact/) — all with just the API key. → Reference: Content
Going live
- Swap the base URL to
https://storefront.periscale.app. - Swap in the production
sb_live_*key — sandbox keys return401against production. - Re-check anything that hardcoded IDs or slugs: sandbox and production are separate stores with separate data.
Analytics
For visitor analytics and event tracking, drop in the Analytics tag — it handles identity and event ingestion automatically; there's nothing to build against here.
Endpoint groups
| Group | Auth | Reference |
|---|---|---|
| Store config & catalog | Key | Browse |
| Shopper authentication | Key | Browse |
| Cart | Key + Bearer | Browse |
| Checkout & payment | Key + Bearer | Browse |
| Orders | Key + Bearer | Browse |
| Account | Key + Bearer | Browse |
| Content | Key | Browse |