Developer guide

Storefront API

Build your store's own website on any domain with btab's products, cart and freight. The buyer pays on your store's btab checkout.

Written with the code it describes, in the same pull request. Plain Markdown: /developers/storefront-api.md

Build your store's own website on any domain with btab's products, cart and freight. The buyer pays on your store's btab checkout.

How it works:

  1. Your site reads products, prices and stock with a publishable key.
  2. It builds a cart on btab. btab prices every line.
  3. It asks for freight by postcode.
  4. It opens a checkout session and sends the buyer to its continue_url.
  5. The buyer pays on your store's btab checkout.
  6. Your server gets the checkout_session.completed webhook, and the buyer goes back to your return_url.

1. Make a publishable key

In the btab dashboard: Settings → Developers → Publishable keys → Make a key. List every site the key is used on, for example https://www.yourshop.com.au and http://localhost:3000 (up to 5). The key is shown once.

  • A publishable key (btab_pk_live_…) is safe in page code. It can read your shop window, build carts, ask for freight, send an enquiry and open a checkout session. It cannot read buyers or orders, and it cannot change anything in your store.
  • It works only from the sites you list. Answers carry CORS for those sites only.
  • Never put a store API key (btab_live_…) in page code. The Storefront API refuses one.
  • A test key (btab_pk_test_…) works on btab's demo store and ends in "Place test order". Nothing is charged or sent.

Send the key in the X-Btab-Storefront-Key header (or Authorization: Bearer).

2. Read the shop window

Base URL: https://api.btab.app/api/storefront/v1

RequestAnswer
GET /siteyour store's public settings
GET /productsthe product list (filters as on your store)
GET /products/{id}one product, with options
GET /products/{id}/reviews, /relatedreviews, related products
GET /categories, /search?q=, /facets, /collections/{slug}browsing
const KEY = 'btab_pk_live_…';
const api = (path, init = {}) =>
  fetch(`https://api.btab.app/api/storefront/v1${path}`, {
    ...init,
    headers: { 'X-Btab-Storefront-Key': KEY, 'Content-Type': 'application/json', ...(init.headers || {}) },
  }).then((r) => r.json());

const { products } = await api('/products?limit=24');

3. Build a cart

Send product ids, options and quantities, never a price. btab prices every line the way your checkout does.

const { cart } = await api('/carts', {
  method: 'POST',
  headers: { 'Idempotency-Key': crypto.randomUUID() },
  body: JSON.stringify({ items: [{ product_id: 123, variant_id: 456, quantity: 2 }] }),
});
// cart.id = 'cart_…' (keep it; it is the cart's secret)
// cart.lines[]: unit_price_cents, line_total_cents, available, enquiry_only, needs_option, max_quantity
// cart.subtotal_cents, cart.prices_include_gst
  • PATCH /carts/{id} { items } replaces the lines. GET /carts/{id} prices them again.
  • A cart lives 72 hours after its last change.
  • Idempotency-Key is optional on POST /carts, POST /carts/{id}/enquiry and POST /checkout_sessions. Use a UUID. A retry with the same key and body returns the first answer.

4. Freight and quotes

const { estimate } = await api(`/carts/${cart.id}/shipping-estimate`, {
  method: 'POST',
  body: JSON.stringify({ postal_code: '2000' }),
});
// estimate.available, estimate.shipping_cents
// estimate.refusal_code = 'SHIPPING_VOLUME_UNKNOWN' → "needs a quote"

Items marked enquiry_only cannot be bought online. Send them as a quote request:

await api(`/carts/${cart.id}/enquiry`, {
  method: 'POST',
  body: JSON.stringify({ email: '[email protected]', name: 'Ann', message: 'For 12 people' }),
});

5. Send the buyer to checkout

const { checkout_session } = await api('/checkout_sessions', {
  method: 'POST',
  headers: { 'Idempotency-Key': crypto.randomUUID() },
  body: JSON.stringify({ cart_id: cart.id, return_url: 'https://www.yourshop.com.au/thanks' }),
});
window.location.href = checkout_session.continue_url;
  • continue_url opens your store's btab checkout with the cart filled in. It works for 30 minutes.
  • return_url must be on one of the key's sites. After paying, the buyer sees a "Back to …" link to it.
  • GET /checkout_sessions/{id} shows open, complete or expired.

6. Know when it is paid

Subscribe to the checkout_session.completed webhook (Settings → Developers → Webhooks). It fires once, when the order is paid:

{ "event": "checkout_session.completed", "data": { "livemode": true,
  "checkout_session": { "id": "cs_…", "status": "complete", "order_number": "ORD-…" } } }

order.paid and order.refunded are also offered to stores with the Storefront API. Verify every delivery's X-Btab-Signature (see WEBHOOKS.md).

7. Test first

Make a test key. It reads btab's demo store (made-up goods). Its checkout session ends in "Place test order": an order marked as a test, never charged or delivered. The webhook says "livemode": false.

Limits

WhatLimit
Reads120 a minute per buyer address per key; 3000 a minute per key
Cart writes20 a minute per buyer address per key
Enquiries8 per 10 minutes per buyer address per store
Requests without a valid key30 a minute per address

Over a limit, the answer is 429 with Retry-After.

Errors

Every refusal has a code, for example ORIGIN_NOT_ALLOWED, STORE_API_KEY_REFUSED, CART_NOT_FOUND, CART_NOT_BUYABLE, RETURN_URL_NOT_ALLOWED, WEAK_IDEMPOTENCY_KEY, IDEMPOTENCY_KEY_REUSED.