Skip to content

The storefront API

Read the shop, products and collections, build a cart, check out with Stripe or on invoice and show the order, from your own website. Endpoints, fields, CORS, caching and rate limits.

Reference13 min read

The storefront API lets your own website show products with live prices and stock, run a cart and take orders, without sending shoppers to the hosted shop. It needs no key: it returns what the hosted shop shows anyway, and payments go straight from the shopper's browser to the shop's own Stripe account. An API key is optional: it counts your requests, and a secret key also reads draft products for a preview.

Every address starts with /commerce/api/public/ and the shop address from Settings:

Text
https://app.bifrostorbit.com/commerce/api/public/northwilds

Answers are JSON. A successful answer has the result in data; an error has error, with a code and a message you can show to shoppers as it is. Validation errors (422) add issues, one per field.

Amounts are whole minor units in the currency given: 27900 with EUR is €279.00.

Languages and currencies

The catalogue endpoints take ?locale=, such as nb, and the product endpoints ?currency=, such as NOK. Without them, or with one the shop doesn't offer, you get the main language and currency. Each product says which language it is in with locale. Text that isn't translated comes in the main language. Prices are the shop's own price in that currency, never a conversion; a variant without a price in it has "price": null and can't be bought in it.

Which websites can call it

The shop, products and collections
Who can call them from a browser
Any website.
Carts, checkout, orders and customer accounts
Who can call them from a browser
Only the shop's Website and Allowed websites in Settings, such as https://northwilds.com, and the hosted shop. Others get 403.
Anything with a customer token
Who can call them from a browser
The same allowed websites only.

Calls from a server, without an Origin header, are always allowed. Only the scheme, host and port count, so https://www.northwilds.com is a different website from https://northwilds.com. Allowed headers are Content-Type, Authorization, X-Api-Key and X-Customer-Token. Answers carry Vary: Origin, and preflight answers can be cached for 10 minutes.

The shop

GET/commerce/api/public/northwilds

Returns slug, name, logoUrl, colour, currency (the main one), currencies (main first), locales (main first), defaultLocale, pricesIncludeTax, shopUrl (the hosted shop), termsUrl and returnsDays. Cacheable for 60 seconds.

Products

GET/commerce/api/public/northwilds/products

Lists active products, by title. Query parameters, all optional:

ids
Means
Up to 100 product ids, separated by commas. The answer keeps your order.
slugs
Means
Up to 100 product addresses, separated by commas.
tag
Means
Only products with this tag, such as winter.
collection
Means
Only products in this collection, by address or id, in the collection's order.
query
Means
Products whose title contains this, such as tent.
limit
Means
How many, 100 unless you say, at most 250.
locale, currency
Means
As above.

GET/commerce/api/public/northwilds/products/fjell-gtx-boot

One product, by address or id.

Each product has id, slug, title, summary, description (HTML), vendor, productType, tags, options (name, values), variants, images (url, alt), specs (label, value), price (min, max, currency, compareAt), available (any variant can be bought), status, seo (title, description, filled from the title and summary when empty), url (the product on the hosted shop), locale, updatedAt, stockSyncedAt (when stock last came from MAM, or null), metafields (your custom fields by key, only those shown to websites) and bundle (components: productId, variantId, quantity, or null when it isn't a bundle).

Each variant has id, title (such as 42 / Brown), options, sku, price, compareAtPrice (only in the main currency), available, quantity (units in stock, or null when stock isn't tracked), requiresShipping and digital (a download, nothing posted), plans (subscription plans: id, name, interval of week, month or year, intervalCount and price), metafields, volume and quantityRule:

JSON
{ "id": "v3f9a1c2b7e", "title": "42 / Brown", "options": ["42", "Brown"], "sku": "FJELL-GTX-42-BRN", "price": 24900, "compareAtPrice": null, "available": true, "quantity": 7, "requiresShipping": true, "digital": false, "plans": [], "metafields": { "leather": "Full-grain" }, "volume": [{ "min": 2, "percent": 5, "price": 23655 }], "quantityRule": null }
  • volume lists the volume prices: from min items of the product, across its variants, each costs price. It is empty for a price from a MAM account.
  • quantityRule is { "min", "max", "step" } or null: the fewest, the most (or null) and the pack size one order can have. Step your quantity picker by step.

A bundle's quantity is how many whole bundles its products make, so it sells out when one of them does.

A product with giftCard: true is a gift card. Its code is emailed once the order is paid, and it needs no delivery. See Gift cards.

Product answers can be cached for 30 seconds.

For a signed-in customer

Send the customer's token in X-Customer-Token and products, collections and carts come with that customer's prices and, for a MAM account, live stock per branch. Variants then also have listPrice, priceSource and branches, and products pricing. Those answers are private and never cached. See Customer accounts and personal prices.

Collections

GET/commerce/api/public/northwilds/collections

Active collections by title, each with id, slug, title, description, image, productCount and url. ids= asks for up to 100 chosen collections. Cacheable for 60 seconds.

GET/commerce/api/public/northwilds/collections/tents

One collection, by address or id, with products in the collection's order. Takes locale, currency and limit (100 unless you say). Cacheable for 30 seconds.

Carts

A cart lives in Commerce Orbit and is known by its token, such as c_Q2x…. Anyone with the token can read and change the cart, so keep it in the shopper's browser, not in a link others see.

POST/commerce/api/public/northwilds/carts starts a cart and answers 201 with it. The body can hold any of the update fields below, and source, up to 60 characters, to say where the order came from; it defaults to the calling website.

JavaScript
const response = await fetch("https://app.bifrostorbit.com/commerce/api/public/northwilds/carts", {  method: "POST",  headers: { "Content-Type": "application/json" },  body: JSON.stringify({ currency: "NOK", locale: "nb", add: [{ variantId: "v3f9a1c2b7e", quantity: 1 }] }),});const { data: cart } = await response.json();localStorage.setItem("cart", cart.token);

GET/commerce/api/public/northwilds/carts/{token} reads it, priced.

PATCH/commerce/api/public/northwilds/carts/{token} changes it and answers with the cart. Send only what changes; null clears a field.

add
Means
Lines to add: variantId, quantity, optional productId and planId (one of the variant's plans, to subscribe). Up to 50. Adds to a line already there, up to 999; a planId given here, or null, changes that line's plan.
lines
Means
Replaces every line. A quantity of 0 removes a line. Send each line's planId again to keep it a subscription. Up to 100.
currency, locale
Means
One of the shop's currencies and languages.
email, name, phone
Means
The shopper. An email that belongs to a customer in a company gives the cart that company's prices and terms.
vatNumber
Means
A business buyer's VAT number, such as DE123456789. It can make the order reverse charged.
discountCode
Means
A discount code, such as WINTER15.
giftCards
Means
Gift card codes, up to 5, such as ["7KQ2-MNB4-XW8P-3HTR"]. Replaces the ones there; [] removes them all. Spaces, hyphens and small letters don't matter.
country
Means
Two letters, such as NO, for shipping choices and VAT before there is an address.
shippingAddress
Means
name, company, line1, line2, postalCode, city, region, country (two letters), phone.
shipping
Means
{ "kind": "rate", "rateId": "…" } or { "kind": "pickup", "locationId": "…" }, from shippingChoices.
note
Means
A note from the shopper, up to 1,000 characters.

Lines whose product isn't active are dropped. Changing a cart while its checkout is waiting for payment starts the checkout over.

The cart

The cart answer has token, currency, currencies, locale, lines, count (items), email, name, phone, vatNumber, company (name, terms: card or invoice, termsDays, onStop, or null), discountCode, discountProblem, offers, giftCards, giftCardCodes, giftCardProblems, badGiftCards, needsShipping, shippingAddress, country, shipping, shippingChoices, totals, prices, problems and orderId.

  • Each line has variantId, productId, slug, title, variantTitle, sku, image, quantity, unitPrice (what the shopper pays each), listPrice (before a company price or volume price), total, available, problem, planId and planName (for a subscription, else null), digital, quantityRule, volumePercent (the volume tier reached, or 0) and giftCard.
  • Each shipping choice has kind, id, name, price, free, minDays and maxDays.
  • offers lists every discount used: id, code (null for an automatic offer), title, kind, automatic, amount and freeShipping. Show an automatic offer by its title.
  • giftCards lists the usable gift cards with code, last4, amount (what it pays) and balance (what stays on it). giftCardProblems says why a code can't be used, such as "The gift card ending 3HTR has expired", and stops checkout; badGiftCards are those codes, to remove.
  • needsShipping is false when nothing in the cart is posted: every line is a download or a gift card. Then the cart needs no shipping choice or address.
  • totals has subtotal, discountTotal, shippingTotal, taxTotal, total, discount (the code's code and amount), discounts (as offers), giftCardTotal, amountDue (what is left to pay by card) and tax: mode (domestic, oss, reverseCharge or export), country, pricesIncludeTax and lines (VAT by rate, with base and tax). totals is null while the cart can't be priced.
  • problems lists what stops checkout, such as "Only 2 of Tind 2 tent are left" or "Gas canister is sold in multiples of 6". discountProblem says why a code doesn't apply, such as "That code has expired"; it doesn't stop checkout.
  • prices is list for the shop's or a price list's prices, account when every line with a part number has the MAM account's price, and fallback when MAM didn't answer for some lines.
  • With a signed-in customer, customer has their name and email, and account their MAM account: number, onStop, creditLimit, balance, overCredit (this order would take the account over its limit) and branch. Lines then have priceSource and branches as on products, and pricesCheckedAt says when MAM answered.

Check out

POST/commerce/api/public/northwilds/carts/{token}/checkout with { "method": "card" } or { "method": "invoice" }.

The cart needs lines, an email, a name, a shipping choice and an address unless it is collection or only downloads, and no problems. Checkout creates the order and takes the stock. It answers with orderId, reference (such as NW1042), status and payment:

  • "status": "pay" with payment: clientSecret, publishableKey, amount and currency. Show Stripe's Payment Element with them. See Checkout with Stripe.
  • "status": "placed" with payment: null: an order on invoice, or one gift cards paid in full, placed straight away.

With gift cards, payment.amount is amountDue: the gift cards are held at checkout and only the rest goes to Stripe. Gift cards can't be used with invoice. If the shopper doesn't pay, the held amount goes back on the cards.

For a customer with a MAM account, checkout asks MAM for the account's prices again. If a price changed since the cart showed it, checkout answers 409 with the code price_changed and a message naming each change; read the cart again, show the new total and check out again. An account on stop can't check out on invoice. Orders go to MAM under the customer's account.

A cart with a subscription line is paid by card only, and every subscription in it needs the same interval. Checkout then starts a Stripe subscription on your account: payment looks the same, and the amount is the first period of the subscriptions plus everything else in the cart. Each later period Stripe charges the subscriptions (and their delivery), and Commerce Orbit adds a paid order for it. See Subscriptions.

Calling checkout again before the order is paid cancels the earlier unpaid order and makes a new one. Once an order is paid or on invoice, the cart can't be checked out again.

The order

GET/commerce/api/public/northwilds/orders/NW1042?email=ingrid@northwilds.com

The order for a thank-you or order page. It needs the email the order was placed with; any other email gets 404. If a card payment is still waiting, Commerce Orbit asks Stripe first, so the answer is current when the shopper comes back from paying.

It has reference, status (open or cancelled), paymentStatus (pending, paid, failed, invoiced, partiallyRefunded or refunded), fulfilment (unfulfilled, partial or fulfilled), placedAt, currency, lines (title, variantTitle, quantity, total, image), subtotal, discountTotal, shippingTotal, taxTotal, total, pricesIncludeTax, taxMode, shipping (the rate's or location's name), tracking (carrier, number, url for each parcel), email and downloads, one for each file bought: filename, title, url, downloads (used), limit, expiresAt and usable. A download url works for 7 days; read the order again for a fresh one.

GET/commerce/api/public/northwilds/orders/NW1042/pdf?email=ingrid@northwilds.com downloads the receipt, or the invoice for an order on invoice, once it is paid or placed. Before that it answers 409.

Returns

GET/commerce/api/public/northwilds/returns?reference=NW1042&email=ingrid@northwilds.com

What can be returned from an order: orderReference, open (whether a return can be asked for now), until (the end of the return window), reason (why not, when open is false), lines (lineId, title, variantTitle, image and left, how many can still come back) and returns, the returns already asked for. For a signed-in customer, leave out email.

POST/commerce/api/public/northwilds/returns asks for a return:

JSON
{  "reference": "NW1042",  "email": "ingrid@northwilds.com",  "lines": [{ "lineId": "l1", "quantity": 1, "reason": "tooSmall" }],  "note": "The boots are half a size too small."}

reason is one of tooSmall, tooBig, notAsDescribed, damaged, wrongItem, changedMind or other. It answers 201 with the return, or 409 when the return window has closed or nothing on the order can come back. The shop's team gets a notification and the customer an email.

GET/commerce/api/public/northwilds/returns/R1001?email=ingrid@northwilds.com reads a return: reference, orderReference, status (requested, approved, rejected, received, refunded or cancelled), lines, note, decisionNote (the shop's message), label (carrier, trackingNumber, trackingUrl, labelUrl), refundAmount, currency and createdAt.

Gift card balance

POST/commerce/api/public/northwilds/gift-cards with { "code": "7KQ2-MNB4-XW8P-3HTR" } answers with last4, balance, currency, expiresAt and usable, or 404 for a code that doesn't exist. It allows a few tries a minute.

Rate limits

Limits count per IP address. Over them, the answer is 429 with "Too many tries. Wait a minute and try again."

The shop, products and collections
Limit
120 requests a minute per shop
Start a cart
Limit
10 at once, then 5 a minute per shop
Check out
Limit
20 a minute per cart
Send a sign-in code
Limit
5 at once, then 1 a minute per address and per IP address
Sign in
Limit
10 at once, then 2 a minute
Personal prices
Limit
30 a minute per customer
The order and its PDF
Limit
20 a minute per order
Read returns
Limit
20 a minute per order or return
Ask for a return
Limit
10 at once, then 5 a minute per order

If your server calls on behalf of many shoppers, all its calls share its address, so cache catalogue answers on your side.

Try it

The API playground runs these endpoints with the shop's own products, and GraphQL reads the same catalogue in one request: see GraphQL.

Was this page helpful?

Read next