Skip to main content
This guide walks through the core storefront flow. All endpoints live under /api/v1 and use the publishable key (or the store domain).
Amounts as strings. The decimal fields of products, cart, checkout, and orders (price, unit_price, subtotal, total…) arrive as strings ("1199.00"). Convert them before doing math: in JavaScript "10.00" + "5.00" concatenates instead of adding. For money, use a decimal library or work in cents.
  • The min_price and max_price filters and the sort=price / sort=-price ordering work on the effective price (active sale and special price), and meta.total always matches the filtered rows. in_stock=true keeps only products with a purchasable variant.
  • In the list, sale_price is the variant’s active sale or the special price, whichever is lower; null when no discount is active.
The product detail (GET /products/{slug}) has two new fields:
On variants, sale_price is null outside the sale window (sale_start–sale_end). A variant with track_inventory: false can be bought even at zero stock.

2. Cart

The cart works for guests (with a session token) and for logged-in customers.
Cross-origin / headless cart. The guest cart is keyed by a session token, returned in the response’s session_token field. If your storefront runs on a different origin than the API (e.g. xxxx.com → api.vellaro.io), the cx_cart cookie (SameSite=Lax) is not sent cross-site: store session_token client-side (e.g. localStorage) and send it back on every cart call and at checkout as the X-Cart-Token header. The cart then persists anywhere and is restored on the shopper’s return (7-day TTL). Same-origin? The cookie is enough and the header is optional.

The cart at sign-in

When the guest signs in, their cart merges into the account’s cart on the first authenticated cart call, or checkout, that still carries the guest token (X-Cart-Token or the cx_cart cookie):
  • the same item adds up, but never beyond the available stock;
  • items no longer for sale stay behind;
  • the guest cart is then deleted, and the same token never merges twice.
What to do in the client:
  1. keep sending X-Cart-Token after the login;
  2. reload the cart right after signing in (password, social login or registration), so the badge and the drawer show the merged items;
  3. once the cart response has user_id set, stop using the guest token;
  4. on sign-out, clear the account’s cart from the screen.

The price to show is unit_price

Each line carries the price actually applied, which already accounts for sale, special price, quantity discount, and the customer’s group: unit_price, with line_total = unit_price × quantity. Show that one. variant.price and variant.sale_price are list prices and must not be used for lines and totals.

Store rules

The cart exposes the rules the checkout enforces, so you can show them before payment:
  • min_order_total: minimum order on the subtotal (zero = no minimum). Below the threshold, show how much is missing and disable the payment button.
  • guest_checkout_enabled: with false the store accepts orders only from registered customers. Ask for login or sign-up before checkout.

3. Checkout & payment

Always take the payment methods from the public list: the checkout accepts only those. Each method is a stable code (stripe, paypal, pokpay, paysera, bank_transfer, cod) to translate in the client.
The checkout response indicates the payment flow (Stripe, PayPal, PokPay, Paysera, cash on delivery, bank transfer): some return a client_secret/redirect to complete client-side, others confirm immediately. An online order that was never paid can be reopened with POST /orders/{order_id}/pay (“Pay now”): same response as the checkout, same order. See the API Reference for the exact schema of POST /checkout.

Store rule errors

A coupon’s discount never exceeds the subtotal, and a “free shipping” coupon zeroes the shipping cost.

Gift voucher

The customer enters the voucher code and you send it in voucher_code. The balance pays the total after the coupon, shipping included.
  • The balance is reserved when the order is created and becomes final on payment: two checkouts can’t spend the same balance.
  • The balance goes back to the voucher when the payment attempt is really closed (payment canceled, attempt replaced by a new checkout, order canceled by the store) and, for a paid order, with a full refund or a cancellation. A declined card does not release the voucher, because the same payment can be retried.
  • If the voucher covers the whole total, the response has payment_method: "voucher" and no gateway data: the order is already paid, go to the confirmation page.
  • The response carries voucher_code, voucher_amount (amount used), and voucher_remaining (balance left, to show to the customer).
  • A voucher can’t pay for a subscription.

4. Orders & tracking

The history (history) the customer sees is filtered:
  • it contains only status changes (event_type: "status_change") and public notes (event_type: "note"); payments, refunds, shipments, and emails stay with the staff;
  • a status row written by the system has comment: null: translate the status in the client. The comment stays only when a staff member wrote it and made it public;
  • a note has status: null.
payment_status can also be partially_refunded; payment_method can be voucher.

5. Subscriptions

A product can have recurring plans (recurrings in the product detail). The customer subscribes at checkout with subscribe_recurring_id (login and payment_method: "stripe" are required).
  • At purchase the customer pays the product price; renewals are charged at the plan price (price).
  • The “trial” only moves the first renewal: it comes after trial_length units of trial_frequency instead of after one interval. trial_price is not applied.
  • billing_interval says every how many frequency units it renews; billing_charges is the total number of charges, purchase included (0 = until canceled).
In GET /account/subscriptions the status can be active, trialing, past_due, paused, canceled, or completed: finished, every installment charged and next_billing_date: null. On a completed subscription hide the cancel button: POST /account/subscriptions/{id}/cancel answers 409 subscription.already_completed.
Quick tip: the full list of parameters, request bodies, and responses is in the API Reference, generated from the live OpenAPI schema — so it’s always aligned with production.

Campaigns

A campaign link looks like https://<store>/…?cmp=<code>. When the storefront finds the cmp parameter, it calls once per browser:
The same visit counts once every 30 minutes (counted: false for the following ones) and an unknown code answers 200 with counted: false. Limit: 30 requests per minute per IP.

Appearance & configuration

  • GET /settings/store-info — name, contacts, social, appearance (logo, colors) to brand your frontend like the store. It also contains:
    • catalog.items_per_page (products per page, null = your choice) and catalog.default_sort (default category ordering, already in the GET /products?sort= syntax, or null);
    • seo.meta_title_default and seo.meta_description_default: title and description for pages without their own meta (empty string = not set).
  • GET /banners, GET /information/<slug> — CMS content and hero.
  • GET /languages, GET /currencies — for language/currency selectors.