# Storefront API

> Keys, CORS, caching, errors and limits for the public Storefront API.

Source: https://shopfa.st/docs/headless/storefront-api

The Storefront API is what your own storefront, app or agent uses to read your catalog and sell: products with live
stock, collections and categories, search, carts with discount codes and gift cards, checkout, buyer accounts with
their orders, keys and downloads, and support tickets. Every operation runs the same code as your ShopFast store, so
prices, stock, taxes, discounts and fraud checks always match.

```sh
curl https://api.shopfa.st/v1/shops/your-store/products?limit=12 \
  -H "ShopFast-Storefront-Key: sfpk_live_…"
```

The [reference](https://shopfa.st/docs/headless/reference) lists every operation. The [JavaScript SDK](https://shopfa.st/docs/headless/sdk) wraps them with
types.

## Base URL

`https://api.shopfa.st/v1/shops/{shop}`, where `{shop}` is your store's handle (`your-store`) or its `st_` id. The store
is part of the URL so a shared cache can never mix two stores up.

## Keys

Create keys in **Settings → Headless & MCP → Storefront keys**.

| | Publishable `sfpk_…` | Private `sfsk_…` |
| --- | --- | --- |
| Where | Browsers, mobile apps, anywhere public | Your server only (Next.js server components, a backend) |
| Seen | Any time in the dashboard: it is public by design | Once, when you create it |
| Origins | Optionally limited to your sites | Refused from browsers |
| Buyer address | The caller's | Your server can pass `ShopFast-Buyer-IP` |

Send the key in `ShopFast-Storefront-Key` (or `Authorization: Bearer sfpk_…`). A key works only with its own store.
Revoking a key stops it within a minute everywhere.

Why private keys pass the buyer's address: limits and fraud checks are per buyer. Without `ShopFast-Buyer-IP`, every
buyer of a server-rendered site would look like your server.

A key's allowed origins also register your sites, for either kind of key: checkout sends buyers back only to them (a
cart's `returnUrl` and `cancelUrl`), and the embedded checkout opens only on them. See
[Headless checkout](https://shopfa.st/docs/headless/checkout).

## Buyers

Sign a buyer in with an emailed code (`POST /customer/code`, then `POST /customer/session`) and send their token in
`ShopFast-Customer-Token` on later calls. Read [Buyer accounts](https://shopfa.st/docs/headless/accounts).

## CORS

Browsers may call the API from any origin with a publishable key, or only from the origins you list on the key. Answers
that are the same for everyone carry `Access-Control-Allow-Origin: *`; personal answers (carts, accounts, orders) name
your origin. No cookies are involved: credentials are always headers.

## Caching

Catalog reads are made to be cached:

- `Cache-Control: public, max-age=0, s-maxage=60, stale-while-revalidate=600`
- `ETag`: send it back in `If-None-Match` and get `304 Not Modified` with no body.
- `Cache-Tag`: the same tags your store's pages use (`s:<store>`, `p:<product>`, `c:<store>:<collection>`,
  `pg:<store>:<page>`). When you change something, ShopFast drops exactly those.

Catalog answers are served from ShopFast's edge, close to your buyers, and refresh within seconds of a change. They are
the same for every caller, like your store's own pages, so the edge does not check the key again for an answer it
already holds.

Live stock (`GET /products/{handle}/availability`) is never cached. Carts, buyer accounts, orders and tickets are
`private, no-store`, and so is everything while your store is closed, in maintenance or behind a store password.

To keep your own cache fresh, subscribe to the [`storefront.updated` webhook](https://shopfa.st/docs/headless/revalidation).

## Errors

Every error is [RFC 9457 problem JSON](https://shopfa.st/docs/guides/errors):

```json
{ "type": "https://shopfa.st/problems/origin-not-allowed", "title": "Origin not allowed", "status": 403, "detail": "This key only works from https://shop.example.com.", "fields": [] }
```

| Status | When |
| --- | --- |
| 401 | No key, an unknown or revoked key, or a key from another store |
| 403 | A private key from a browser, or an origin the key does not allow |
| 404 | No such product, collection, category or page (a renamed product answers `301` to its new handle) |
| 422 | The input failed validation; `fields` says where |
| 429 | Too many requests; wait `Retry-After` seconds |

## Retries

Send `Idempotency-Key` with a write (a cart, a code, a checkout, a ticket) and a retry after a timeout runs it once:
the same request with the same key gets the first answer again, with `Idempotent-Replayed: true`. Keys are kept 24
hours per storefront key and signed-in buyer. See [Idempotency](https://shopfa.st/docs/guides/idempotency) for the rules.

```sh
curl -X POST https://api.shopfa.st/v1/shops/your-store/carts \
  -H "ShopFast-Storefront-Key: sfpk_live_…" -H "Content-Type: application/json" \
  -H "Idempotency-Key: 9b2f0c4e-cart" \
  -d '{"lines":[{"productId":"prd_…","quantity":1}]}'
```

## Limits

600 reads and 120 writes a minute per key and buyer address, on top of the store's own limits on sign-in codes,
discount codes and checkouts.

## Stability

Operations under `/v1` only ever gain fields and operations. A change that would break you gets a new version. The
SDK's schema snapshot is checked on every change to ShopFast, so a breaking change cannot slip in.
