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:
- Your site reads products, prices and stock with a publishable key.
- It builds a cart on btab. btab prices every line.
- It asks for freight by postcode.
- It opens a checkout session and sends the buyer to its
continue_url. - The buyer pays on your store's btab checkout.
- Your server gets the
checkout_session.completedwebhook, and the buyer goes back to yourreturn_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
| Request | Answer |
|---|---|
GET /site | your store's public settings |
GET /products | the product list (filters as on your store) |
GET /products/{id} | one product, with options |
GET /products/{id}/reviews, /related | reviews, 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-Keyis optional onPOST /carts,POST /carts/{id}/enquiryandPOST /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_urlopens your store's btab checkout with the cart filled in. It works for 30 minutes.return_urlmust be on one of the key's sites. After paying, the buyer sees a "Back to …" link to it.GET /checkout_sessions/{id}showsopen,completeorexpired.
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
| What | Limit |
|---|---|
| Reads | 120 a minute per buyer address per key; 3000 a minute per key |
| Cart writes | 20 a minute per buyer address per key |
| Enquiries | 8 per 10 minutes per buyer address per store |
| Requests without a valid key | 30 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.