Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.scalev.com/llms.txt

Use this file to discover all available pages before exploring further.

Storefront API v3 lets you build your own storefront UI while Scalev handles catalog, carts, checkout, customer accounts, delivery locations, orders, and payment instructions. Start here to understand the setup you need and the API flow your frontend uses from product browsing to checkout and customer account pages. 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.

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

  1. Create or retrieve a storefront public API key.
  2. Register the storefront origin, for example https://demo.example.com, as an allowed origin.
  3. 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>
  4. Serve the frontend from the registered origin.
  5. Make API calls directly from the browser.
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:
  1. GET /public/items/count
  2. GET /public/items
  3. Use entity_type to distinguish:
    • product
    • bundle_price_option
  4. For product details, call GET /public/products/{slug}.
  5. For bundle price option details, call GET /public/bundle-price-options/{slug}.
  6. For variant pricing, call GET /public/variants/pricing?ids=....
  7. 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:
  1. GET /public/cart
    • save x-scalev-guest-token
  2. POST /public/cart/items
  3. PATCH /public/cart/items/{item_id}
  4. DELETE /public/cart/items/{item_id}
  5. POST /public/checkout/shipping-options
  6. POST /public/checkout/summary
  7. POST /public/checkout
  8. GET /public/orders/{secret_slug}
  9. 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.

Customer auth flow

Call:
POST /public/auth/login
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:
  1. GET /customers/me
  2. PATCH /customers/me
  3. POST /customers/me/set-new-password
  4. GET /customers/me/addresses
  5. POST /customers/me/addresses
  6. GET /customers/me/addresses/{address_id}
  7. PATCH /customers/me/addresses/{address_id}
  8. DELETE /customers/me/addresses/{address_id}

Customer cart and checkout flow

Use this flow for signed-in customers:
  1. GET /customers/me/cart
  2. POST /customers/me/cart/items
  3. PATCH /customers/me/cart/items/{item_id}
  4. DELETE /customers/me/cart/items/{item_id}
  5. GET /customers/me/checkout/addresses
  6. GET /customers/me/checkout/payment-methods
  7. POST /customers/me/checkout/shipping-options
  8. POST /customers/me/checkout/summary
  9. POST /customers/me/checkout
  10. GET /customers/me/orders
  11. 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:
  1. Select province: GET /public/locations/provinces
  2. Select city: GET /public/locations/cities?province_id=...
  3. Select subdistrict: GET /public/locations/subdistricts?city_id=...
  4. Postal codes: GET /public/locations/{location_id}/postal-codes
Pattern B:
  1. Direct search: GET /public/locations?search=...
  2. Use the returned location or subdistrict directly.
  3. Postal codes: GET /public/locations/{location_id}/postal-codes

Password reset flow

Implement password reset as a link-based browser flow:
  1. User enters email.
  2. Frontend calls POST /public/auth/forget-password.
  3. Scalev sends an email link to /reset-password?token=<reset-token>.
  4. Frontend reads token from the URL.
  5. User enters new password and confirmation.
  6. 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
  • 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. 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
  • list subscriptions and render entitlement-gated courses when present
  • do all of the above from the frontend without a custom backend