Storefront API v3 lets you build your own storefront UI while Scalev handles catalog, carts, checkout, customer accounts, delivery locations, orders, and payment instructions. Use this as the complete A-Z implementation guide after you understand the Storefront API overview, auth model, checkout flow, and optional advertising conversion events.
A Storefront API storefront can be hosted as a static frontend, for example on Cloudflare Pages, Vercel, Netlify, or any CDN, without building a custom backend or same-origin proxy. The frontend calls https://api.scalev.com directly.
Call Storefront API v3 directly from the browserStorefront API v3 is designed to be called directly from the browser or end-user client. Do not place Storefront API v3 behind your own backend proxy, reverse proxy, middleware, edge function, serverless function, or pass-through API layer.The rate limiter for Storefront API v3 is intentionally strict because this API is designed for direct client-side usage. If you proxy these requests, many users may appear to come from the same server IP, which can negatively affect rate limiting, spam detection, duplicate-order protection, and other validation logic.For headless storefront deployments, treat the browser as the intended caller of Storefront API v3. Use your own backend only for non-Scalev concerns such as admin settings, custom business logic, or private integrations.
Architecture
Use these rules for storefront frontends:
- Use the store
unique_id in public storefront runtime URLs.
- Use a publishable storefront public API key in frontend code.
- Send the key as
X-Scalev-Storefront-Api-Key for /public/* endpoints.
- Send the customer JWT as
Authorization: Bearer <access> for /customers/me/* endpoints.
- Store and resend
X-Scalev-Guest-Token for guest cart flows.
- Do not expose business API keys in frontend code.
- Do not build a custom backend endpoint for catalog, cart, checkout, payment instructions, order tracking, or customer account flows.
All endpoint paths below are relative to:
https://api.scalev.com/v3/stores/{store_id}
Setup checklist
- Create or retrieve a storefront public API key.
- Register the storefront origin, for example
https://demo.example.com, as an allowed origin.
- Configure the frontend with:
SCALEV_API_BASE=https://api.scalev.com
SCALEV_STORE_UNIQUE_ID=<store_unique_id>
SCALEV_STOREFRONT_API_KEY=<publishable_storefront_key>
- Serve the frontend from the registered origin.
- Make API calls directly from the browser.
- If the storefront runs ads, wire browser pixels and Scalev server conversion events with shared event IDs. See Advertising conversion events.
Storefront runtime uses the store unique_id. Storefront setup endpoints are authenticated business endpoints. The setup endpoints may accept legacy numeric store IDs for compatibility, but the storefront frontend should be configured with the store unique_id.
Minimal fetch helpers
Use one helper for anonymous public storefront routes:
const API_BASE = "https://api.scalev.com";
const STORE_ID = "store_xxx";
const STOREFRONT_KEY = "sfpk_xxx";
async function storefrontFetch(path: string, init: RequestInit = {}) {
const headers = new Headers(init.headers);
headers.set("Accept", "application/json");
headers.set("X-Scalev-Storefront-Api-Key", STOREFRONT_KEY);
return fetch(`${API_BASE}/v3/stores/${STORE_ID}${path}`, {
...init,
credentials: "omit",
headers,
});
}
Use another helper for signed-in customer routes:
async function customerFetch(
path: string,
accessToken: string,
init: RequestInit = {}
) {
const headers = new Headers(init.headers);
headers.set("Accept", "application/json");
headers.set("Authorization", `Bearer ${accessToken}`);
return fetch(`${API_BASE}/v3/stores/${STORE_ID}${path}`, {
...init,
credentials: "omit",
headers,
});
}
Persist the guest cart token from the first guest cart response:
const response = await storefrontFetch("/public/cart");
const guestToken = response.headers.get("x-scalev-guest-token");
if (guestToken) {
localStorage.setItem("scalev_guest_token", guestToken);
}
Resend the guest token on later guest cart and guest checkout calls:
const headers = new Headers({
"Content-Type": "application/json",
});
const guestToken = localStorage.getItem("scalev_guest_token");
if (guestToken) {
headers.set("X-Scalev-Guest-Token", guestToken);
}
await storefrontFetch("/public/cart/items", {
method: "POST",
headers,
body: JSON.stringify({
type: "variant",
variant_id: 494535,
quantity: 1,
}),
});
Catalog flow
Use this catalog sequence:
GET /public/items/count
GET /public/items
- Use
entity_type to distinguish:
product
bundle_price_option
- For product details, call
GET /public/products/{slug}.
- For bundle price option details, call
GET /public/bundle-price-options/{slug}.
- For variant pricing, call
GET /public/variants/pricing?ids=....
- For availability, call
GET /public/variants/{variant_id}/availability.
Use variant_id for product variants. Use bundle_price_option_id for bundle price options. Both can be placed in cart and checkout payloads with the typed item union.
Checkout item shapes
Use this shape for product variants:
{
"type": "variant",
"variant_id": 494535,
"quantity": 1
}
Use this shape for bundle price options:
{
"type": "bundle_price_option",
"bundle_price_option_id": 31925,
"quantity": 1
}
Guest cart and guest checkout flow
Use this flow for anonymous buyers:
GET /public/cart
- save
x-scalev-guest-token
POST /public/cart/items
PATCH /public/cart/items/{item_id}
DELETE /public/cart/items/{item_id}
POST /public/checkout/shipping-options
POST /public/checkout/summary
POST /public/checkout
GET /public/orders/{secret_slug}
- For manual
bank_transfer, POST /public/orders/{secret_slug}/transfer-proof-upload, upload the image, then PATCH /public/orders/{secret_slug} with transferproof_url.
POST /public/orders/{secret_slug}/payment
Checkout source behavior:
- If
items is supplied, the order is created from direct items.
- If
items is omitted, the order is created from the guest cart referenced by X-Scalev-Guest-Token.
- If both direct
items and X-Scalev-Guest-Token are supplied, direct items is the source and the guest cart is cleared after successful order creation.
For Meta, TikTok, and SnackVideo conversion tracking, fire checkout and purchase events only after the related checkout API call succeeds. See Advertising conversion events.
Customer auth flow
Call:
If the response contains access and refresh, login is complete.
If the response is an OTP challenge, show an OTP input and call:
POST /public/auth/otp/verify
Token handling:
- Store
access for authenticated customer calls.
- Store
refresh if you implement persistent sessions.
- Refresh tokens are single-use.
- After calling refresh, discard the old refresh token and store the new one.
- Logout uses
POST /public/auth/jwt/blacklist.
Blacklist the customer tokens with:
{
"tokens": ["<access>", "<refresh>"]
}
Customer account flow
Use these customer endpoints in the natural account UI order:
GET /customers/me
PATCH /customers/me
POST /customers/me/set-new-password
GET /customers/me/addresses
POST /customers/me/addresses
GET /customers/me/addresses/{address_id}
PATCH /customers/me/addresses/{address_id}
DELETE /customers/me/addresses/{address_id}
Customer cart and checkout flow
Use this flow for signed-in customers:
GET /customers/me/cart
POST /customers/me/cart/items
PATCH /customers/me/cart/items/{item_id}
DELETE /customers/me/cart/items/{item_id}
GET /customers/me/checkout/addresses
GET /customers/me/checkout/payment-methods
POST /customers/me/checkout/shipping-options
POST /customers/me/checkout/summary
POST /customers/me/checkout
GET /customers/me/orders
GET /customers/me/orders/{id}
Customer checkout source behavior:
- If
items is supplied, the order is created from direct items.
- If
items is omitted, cart_id selects the customer cart source.
- If both
items and cart_id are supplied, direct items is the source and the referenced cart is cleared after successful order creation.
Location flow
Use either supported location selection pattern.
Pattern A:
- Select province:
GET /public/locations/provinces
- Select city:
GET /public/locations/cities?province_id=...
- Select subdistrict:
GET /public/locations/subdistricts?city_id=...
- Postal codes:
GET /public/locations/{location_id}/postal-codes
Pattern B:
- Direct search:
GET /public/locations?search=...
- Use the returned location or subdistrict directly.
- Postal codes:
GET /public/locations/{location_id}/postal-codes
Location responses include only the fields needed for display and the next request:
{
"province_id": 31,
"province_name": "DKI Jakarta"
}
{
"city_id": 3171,
"city_name": "Kota Jakarta Pusat"
}
{
"id": 9090,
"subdistrict_name": "Gambir",
"city_name": "Kota Jakarta Pusat",
"province_name": "DKI Jakarta",
"display": "Gambir, Kota Jakarta Pusat, DKI Jakarta"
}
{
"postal_code": "10110"
}
Password reset flow
Implement password reset as a link-based browser flow:
- User enters email.
- Frontend calls
POST /public/auth/forget-password.
- Scalev sends an email link to
/reset-password?token=<reset-token>.
- Frontend reads
token from the URL.
- User enters new password and confirmation.
- Frontend calls
POST /public/auth/save-password.
Do not show a manual reset token input. The frontend should never ask the user to copy and paste the reset token. The reset hostname is selected from the registered allowed origin when the request includes Origin.
Payment rendering
Use these order fields to render the payment page:
product_price
product_discount
shipping_cost
shipping_discount
other_income
gross_revenue
payment_method
sub_payment_method
payment_account_holder
payment_account_number
transferproof_url
transfer_time
payment_url
pg_payment_info
store.payment_accounts
handler_phone
chat_message
Manual bank transfer may use store.payment_accounts when no specific order-level account has been selected. Use transferproof_url and transfer_time to show whether the buyer has already submitted payment proof. Hosted or provider-backed methods may use payment_url or pg_payment_info. gross_revenue is the amount the buyer pays.
See Checkout and Payments for method-specific rendering rules.
Customer subscriptions and courses
For customer account pages, use:
GET /customers/me/subscriptions
GET /customers/me/subscriptions/{id}
POST /customers/me/subscriptions/{id}/cancel
POST /customers/me/subscriptions/{id}/resume
GET /customers/me/subscription-items/{id}/upgrade
POST /customers/me/subscription-items/{id}/upgrade
GET /customers/me/subscription-items/{id}/downgrade
POST /customers/me/subscription-items/{id}/downgrade
GET /customers/me/variants/{uuid}/course
GET /customers/me/course-sections/{uuid}
GET /customers/me/course-contents/{uuid}
PATCH /customers/me/course-contents/{uuid}
Course endpoints are entitlement-dependent. A 403 or 404 can mean the customer does not own the course variant, not that the storefront integration is broken.
Minimal full checkout payload
Use a complete variant checkout payload like:
{
"items": [
{
"type": "variant",
"variant_id": 494535,
"quantity": 1
}
],
"customer_name": "Demo Customer",
"customer_email": "demo.customer@example.com",
"customer_phone": "6281234567890",
"shipping_address": "Jl. Demo Storefront API No. 3",
"shipping_province": "DKI Jakarta",
"shipping_city": "Kota Jakarta Pusat",
"shipping_subdistrict": "Cempaka Putih",
"shipping_postal_code": "10510",
"shipping_location_id": 9089,
"payment_method": "bank_transfer"
}
Use a bundle price option checkout payload like:
{
"items": [
{
"type": "bundle_price_option",
"bundle_price_option_id": 31925,
"quantity": 1
}
],
"customer_name": "Demo Customer",
"customer_email": "demo.customer@example.com",
"payment_method": "bank_transfer"
}
Agent checklist
An implementation is complete when it can:
- list catalog items
- show product detail
- show bundle price option detail
- check pricing and availability
- persist guest cart using
X-Scalev-Guest-Token
- add, update, and remove cart items
- select delivery location
- compute shipping options and checkout summary
- create public checkout orders
- show public order status and payment instructions
- create or fetch payment instructions
- login customer with direct-token or OTP branch
- refresh and blacklist JWTs
- manage customer profile and addresses
- use customer cart
- create authenticated checkout orders
- list and view authenticated orders
- send Meta, TikTok, and SnackVideo conversion events through Scalev
- list subscriptions and render entitlement-gated courses when present
- do all of the above from the frontend without a custom backend