1. Browsing the catalog
A plain list, then the narrowing and trimming a real grid does.Response
fields fetches only those, keeping the response
small:
fields accepts a fixed allowlist of column names (e.g. id, name, price, sale_price,
media, thumbnail_url, category_name, stock) — not arbitrary keys. An unknown name
returns 400 invalid_request (“Invalid field names: …”). id and product_id are
interchangeable.next_cursor goes back as
cursor until has_more is false.
categoryId, and combines with subcategoryId for hierarchy:
2. Adding to an anonymous cart
A shopper who isn’t signed in gets a session cart keyed by anX-Session-Id you generate and
persist (a UUID in a cookie). No customer token needed yet.
Response
id (09695de2-…) is the cart-item id, not the product or variant id, and it is the
handle for updating or removing that line. It is distinct from variant_id: the same variant added
twice remains one line, mutated by its id.
2b. Managing the cart — view, update, remove, clear
The rest of the cart lifecycle runs on the same identity as the add:xSessionId for an anonymous
cart, or xAuthToken once the shopper signs in.
Reading the current cart, for a cart drawer or a badge count:
id, not the variant:
updateCartItem with quantity: 0 is equivalent to removeCartItem, so a quantity stepper
decremented to zero needs no separate branch.addToWishlist / removeFromWishlist / moveWishlistToCart, the last of which hands a
saved item back into the cart flow above.
3. Signing the shopper in
Checkout needs a session for the shopper. Registration and login aresk-only endpoints, so they run
on your server with the secret client.
Response
4. Merging the anonymous cart into the customer
Everything the shopper added before signing in carries onto their account.5. Quoting shipping
Delivery is quoted for the shopper’s address before the order is totalled. Read-only, publishable key. The amount returned here is the one the order endpoint validates against, so it is the amount the total is built from.Response
shipping_latitude + shipping_longitude so the server re-resolves
this fee and validates shipping_amount against it.
With multi-carrier shipping (Shippo)
When the store has multi-carrier shipping connected, a destinationaddress_to plus a parcel
returns live carrier rates for the shopper to choose from. The response then carries rates[] and
rate_source: "shippo".
Response
rates[]; shipping_amount becomes that rate’s amount, and its rate_id
travels into the order as shippo_rate_id, which the server validates the amount against.
6. Reserving stock before checkout (optional)
Where checkout takes a while — an async payment flow, a multi-page wizard — a reservation holds the stock so the shopper cannot be oversold partway through. Each hold expires by itself afterttl_seconds. The call originates in the browser, so it takes a publishable key, and the
variant_id comes forward from the cart.
Response
If the requested quantity isn’t available, nothing is reserved and the call returns
409.
Reserving is optional — skip it for a fast synchronous checkout; the order itself still validates
stock at creation.7. Placing the order (server, signed)
Order creation is a secret-key call, HMAC-signed and carrying an idempotency key. The idempotency key is what makes a retried checkout safe: the same key returns the original order rather than a duplicate. HMAC Signing has the helper. The three orders below are the same cart — the 2 Galaxy Watches from step 2 — priced three ways: base, with a promotion, then with a promotion and a gift card.Order items take
variant_id (the exact SKU the shopper picked) or product_id (which resolves
the product’s default variant). When both are sent, variant_id wins. On a multi-variant product,
dropping to product_id at checkout can order a different variant than the one in the cart, so the
variant_id added to the cart is the one to carry into the order line. See
Products and variants.7a. The base order
Response
7b. The same order with a promotion applied
The cart subtotal of 50) promotion (df290d89-0400-4b2c-aa56-11fcc486150c). storefront.promotions.calculatePromotionDiscount(...)
quotes the discount; the order then carries it as discount_amount plus a promotion_usages entry,
which is what tracks the usage.
15% of 89.70, so the total becomes subtotal − discount + shipping = 598 − 89.70 + 5.99 =
$514.29.
Response
7c. The same order with a gift card as well
The shopper also redeems gift cardGTSA-1B2C-3D4E-5F6G (50 against this order,
as a gift_card_redemption: { code, amount } block with the $50 folded into discount_amount.
A gift card reduces the total as part of
discount_amount. The server validates
total_amount === subtotal + tax + shipping − discount_amount, and the discount it accepts is the
sum of every real entitlement: promotions plus the gift card’s redeemable amount. With subtotal
598, shipping 5.99, the 50 gift card, discount_amount is 514.29 — reduces nothing, and over-claiming a card beyond its balance is
rejected as discount_invalid (7d).Response
7d. The two rejections
The server is the price authority. Order creation recomputes every item price from the live catalog
and validates the claimed
discount_amount against the promotions and gift card named on the
order, so amounts computed client-side carry no weight. The two guards below are what a mismatch
meets.total_amount that does not equal subtotal + tax + shipping − discount_amount is rejected as
400 price_mismatch before the order is created — here the gift card is in discount_amount but the
total was left unchanged:
Response (400)
discount_amount above it as
400 discount_invalid — a discount with no promotion or gift-card balance behind it has a maximum of
0, which is why neither an invented discount nor a redemption beyond a card’s balance lowers the
price:
Response (400)
201, and the failure arrives in post_processing_warnings and
stamped onto order.notes.
Response (201)
Stock reduction fails the same way: a paid order short on inventory still returns
201, with a
{ "stage": "stock_reduction", ... } warning and a [STOCK_REDUCTION_PARTIAL] note. A non-empty
post_processing_warnings means the order was placed and a person needs to follow up on it.8. Taking payment (server, signed)
Payment initialization is alsosk + HMAC + idempotency. The shopper is redirected to the returned
checkout_url, and the order is finalized from the provider’s webhook. The amount charged is the
order’s own total_amount, which already reflects any gift card folded into its discount_amount.
Response
When the provider confirms payment, the webhook handler patches the order to
paid
(server.orders.updateOrder(...)). Galactic Core then reduces stock, books the sale, and updates
the customer’s metrics, so none of that is orchestrated by the integration.
Webhooks & automation covers the handler.9. Fulfilment and tracking
Once the order is paid, the merchant buys the carrier label from their admin. Buying a label charges the merchant’s carrier account, marks the order shipped, stamps the order’stracking_number, and
fires an order.shipped webhook — it is a fulfilment action, not a storefront SDK call.
The carrier’s own tracking page is the shortest route to shopper-visible tracking. When the label is
bought, the carrier tracking URL is saved on the order at shipping_metadata.shippo.tracking_url,
and rendering it as a link needs no API call:
trackShipment returns the live status and
history to build a timeline from:
order.shipped webhook is the moment either of the above becomes available to show.
What’s next
- The shopper can track the order and lodge returns.
- Reviews and support threads pick up after the purchase.
- A multi-merchant storefront checks out through the Marketplace split checkout instead.

