The Storefront API
The Storefront API is your store for your own front end: a Next.js site, an app, or a page made
by Claude Code, Lovable or v0. It serves your catalogue, search, the basket, the checkout and
each order’s status at https://api.tuppence.ai/storefront/v1, with your publishable key
(pk_test_… or pk_live_…). A basket priced through it costs exactly what the same basket
costs on your Tuppence store, because Tuppence prices both the same way.
The publishable key is public by design. It reads only what your store shows anyone: never a customer, never a stock count beyond low stock, never an order without its status token.
Switch it on
Section titled “Switch it on”It is on for every store. Under Store › Settings › Developers:
- Storefront API on switches it off and on. Off, your publishable key gets nothing from it.
- Allowed origins lists the sites whose browsers may call it, such as
https://shop.example.com(https, no path, up to 20). Calls from a server always work.
Test and live are separate stores, each with its own list: set the origins in both modes. Your server can set them with your secret key:
curl -sS https://api.tuppence.ai/v1/sites/site_… \ -H "Authorization: Bearer $TUPPENCE_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{"headless_origins": ["https://shop.example.com"]}'Call it
Section titled “Call it”Every request carries the key in the Tuppence-Publishable-Key header. Never a cookie: the
basket is named by its token in the path.
curl -sS "https://api.tuppence.ai/storefront/v1/products?limit=3" \ -H "Tuppence-Publishable-Key: $TUPPENCE_PUBLISHABLE_KEY"| Route | What it answers |
|---|---|
GET /site | The store’s name, currency, address, brand tokens, menus and policy links |
GET /products, GET /products/{slug} | Products online, each priced all-in with its variants and availability |
GET /collections, GET /collections/{slug} | Collections and their products |
POST /search | Products matching {query}, with filters |
GET /availability?variants=… | in_stock, low_stock, out_of_stock or preorder per variant, never a count |
POST /carts, GET /carts/{token} | A new basket and its cart_token; the basket |
POST /carts/{token}/lines, PATCH or DELETE …/lines/{id} | Add, change or remove a line |
POST /carts/{token}/discounts, …/gift_cards | A discount code; a gift card |
POST /carts/{token}/delivery_options | The ways to deliver to {address}, with prices and dates |
POST /checkouts, GET or POST /checkouts/{token} | The checkout for a basket, and its snapshot |
GET /checkouts/{token}/fields, POST …/pay | What the payment fields need; pay the snapshot’s due |
GET /checkouts/{token}/order, GET /orders/{status_token} | The order a payment made; its status page as data |
The full reference, every field and error, is the Storefront API reference.
The basket
Section titled “The basket”Every write answers the whole basket: its lines, totals (subtotal, discount,
delivery, tax, total, due) and delivery_options. Show those as they come. Never add up
prices or work out tax in your own code; a total sent in a request is ignored.
Keep the cart_token in the browser’s localStorage (a functional value, so no consent
banner). A basket that has expired answers 404: forget the token and start a new one.
Take payment on your own domain
Section titled “Take payment on your own domain”Your shop front takes the payment itself, on your page, through
@tuppence/js:
POST /checkoutswith thecart_token,contact,shipping_addressanddelivery. The answer’ssnapshotis what the shopper is shown and charged: its lines, delivery, tax anddue.GET /checkouts/{token}/fieldsgives what@tuppence/jsneeds to mount its card fields. Card details go from those fields to the card network, never through your code or ours.POST /checkouts/{token}/paywith{amount: snapshot.due, confirmation_token}, then pollGET /checkouts/{token}/orderuntil it hasorder.status_url.
Both POST /checkouts and pay need an Idempotency-Key header: a new one for each attempt, the
same one when you retry it, so a retry never pays twice.
Rather not take the payment yourself? Send the shopper to your Tuppence store’s own checkout for
the same basket: <your store's address>/checkout/c/<cart_token>. A drop product always goes
there (409 drop_requires_hosted_checkout, with the link), so its queue stays fair.
In test mode, pay also takes payment_method: "pm_card_gb", a test card, with no card fields.
Apple Pay and Google Pay on your domain
Section titled “Apple Pay and Google Pay on your domain”Each allowed origin is set up for Apple Pay and Google Pay when you add it. Apple also asks your
site for one file, /.well-known/apple-developer-merchantid-domain-association. Serving it is
your site’s part: it is the same file for every site, so copy
Tuppence’s.
Without it Apple Pay doesn’t appear, and nothing errors. Tuppence’s Next.js starter serves it
for you.
Errors
Section titled “Errors”| Status | Code | Why |
|---|---|---|
403 | origin_not_allowed | The browser’s origin isn’t in Allowed origins. fix_url opens the setting |
403 | storefront_api_off | The Storefront API is switched off. fix_url opens the setting |
404 | resource_missing | No such product, or the basket expired |
409 | drop_requires_hosted_checkout | A drop sells only on your store’s own checkout; the error has the link |
429 | rate_limited | Too many calls from one address. Wait for Retry-After |
Going live
Section titled “Going live”Your live store answers to pk_live_… once your account is live. Add your origins again in
live mode, and swap the key in your site’s settings. Nothing else changes.
- Store prompt kit: prompts that build a shop front on this API
- Build your store with Claude Code