Crystallize logo

The storefront API contract: which API serves which task

Crystallize has four APIs a frontend could call, and a storefront needs two of them. This page gives the rule, the credential each API takes and where it may run, and a table of the common storefront tasks with the API and call for each.

The rule

  1. Discovery API: every catalogue read. An item by path, browse, search, typeahead, facets, variant groups, translated paths, list and market prices, stock per location, ranking and similarity, and draft preview for editors. It serves published content unless you ask for drafts with publicationState: draft, and it is eventually consistent (see Caching and revalidation).
  2. Shop API: everything about a shopper. Carts, the price for this customer and market, promotions and vouchers, placing the cart, orders created from the cart or directly, payments, pipeline stages on those orders, the shopper's order history, customers, bookings, subscription contracts and locks.
  3. Catalogue API: a fallback for per-customer price display. When a signed-in buyer should see their contract price on a product page, the Catalogue API's priceFor(customerIdentifiers) resolves it on the server. Everything else it does, Discovery does too.
  4. Core API: never in the storefront's request path. It is for seeding, imports, scheduled jobs, webhook setup, and admin or seller tools behind a staff login. It is rate limited.
  5. Legacy PIM API: setup only. Some tenant settings (VAT types, price variants, stock locations, pipelines, markets) are still created there, at build or setup time.
warning

Core is not a storefront API

Every build that called Core from a page ran into rate limits or slow pages. Carts, orders, customers and subscription contracts belong on the Shop API, which is edge-distributed and syncs to Core in the background. If a page seems to need Core, the data is usually in Discovery already, or it belongs in a back-office tool.

Credentials and where each API may run

API

Endpoint

Credential

From the browser?

Discovery

https://api.crystallize.com/your-tenant-identifier/discovery

None when open (the default). When restricted, the static token in X-Crystallize-Static-Auth-Token, on published and draft reads alike.

Yes, if it is open. Then everything it serves is public, including every price variant, every market price and every draft.

Shop API

https://shop-api.crystallize.com/your-tenant-identifier/{cart, order, customer, …}

A JWT from /auth/token, minted from an access token pair

No. Call it from your server.

Catalogue

https://api.crystallize.com/your-tenant-identifier/catalogue

Open, static token or token pair, depending on the tenant setting

Server, for contract prices

Core

https://api.crystallize.com/@your-tenant-identifier

Access token pair

Never

Shop API tokens

The server exchanges the access token pair for a JWT, with one scope per route it calls: cart, order, customer, subscription-contract, booking, lock, and the matching :admin scopes for admin routes. See Shop API: Authentication for the request.

  • Cache the JWT on the server and get a new one before it expires. A rejected token answers HTTP 401 with { "success": false } and no errors list, so handle the status code.
  • The JWT is per tenant, not per shopper. It proves your server may use the Shop API. Which cart belongs to which shopper is your session's job.
  • Use an access token without a tenant scope. A token limited to one tenant cannot mint a Shop API JWT.
  • Subscription contracts need their own token. Ask for the subscription-contract scope on a token of its own.

The Shop API also reads products from the Catalogue with the request's credentials. If your Catalogue API is not open, send its credentials on every Shop API call as well.

Tasks and the API for each

Task

API

Call

Item by path (product, category, article)

Discovery

search(language, filters: { path: { equals } }) with a fragment per shape

Category listing

Discovery

browse.<shape>(filters: { parentPaths: { equals } })

Facets and filters

Discovery

facets argument and summary { facets }; each selection option is its own filter field

One card per colour on a listing

Discovery

variantGroups(by, filter, first)

Variant pick on the product page

Discovery

variant(selected: [{ attribute, value }]), then its SKU to the cart

Search results and typeahead

Discovery

search(term), sorted by score; autocomplete or filters: { name: { autocomplete } }

Translated paths and hreflang

Discovery

One aliased query per language by itemId

Draft preview for editors

Discovery

publicationState: draft on the server, in preview mode, never cached, noindex. Public on an open Discovery.

List price, sale price, price tiers

Discovery

<priceVariant>Price, <priceVariant>PriceTiers

Public market price

Discovery

<priceVariant>PriceFor(marketIdentifiers), for the products on screen

Contract price for a signed-in buyer

Shop API or Catalogue

A cart hydrated with the customer, or Catalogue priceFor(customerIdentifiers) cached per organisation. Never Discovery.

Stock on the page

Discovery

<location>Stock per variant, for example defaultStock

Personal ranking, "more like this"

Discovery

rankBy with context.userTaste, nearestTo. See Vector Search.

Create or change the cart

Shop /cart

hydrate with the whole item list

Price the cart for the customer and market

Shop /cart

context.price and the customer on the cart

Lock the cart before payment

Shop /cart

place

Order from the cart

Shop /order

createFromCart with pipelines, payment status and payments

Order without a cart (accepted quote, point of sale)

Shop /order

create

Record payments, move through stages

Shop /order

addPayments, setPayments, addToStage

Shopper's order history

Shop /order

orders(customerIdentifier)

Booking availability, hold, confirm

Shop /cart

availability, bookSkuItem, confirmCartBooking. See Booking APIs.

Cache revalidation on publish

Core (setup)

A webhook on items that calls a route in your storefront

Seeding, model changes, publishing, imports

Core, mass operations

Scripts only. See Mass operations.

Write an order in one store

The Shop API keeps its own order store, which syncs to Core. Changes made in Core come back in a way that can duplicate or overwrite a Shop API order. If your storefront lists orders through the Shop API, create and change them through the Shop API too: createFromCart, create, setMeta, addToStage. Keep Core order mutations for orders no storefront reads, such as an import into a back office.

warning

An open Discovery API publishes every price and every draft

When Discovery is open, anyone who knows your tenant identifier can read every price variant and every market's price list. Contract prices and markets created for a single customer don't belong there. Resolve them on the server through the Shop API or the Catalogue API, and cache them per organisation. Drafts are public too: publicationState: draft needs no credentials on an open Discovery. If drafts must stay private, restrict Discovery and send the static token from your server, and don't keep confidential or embargoed content in drafts on an open tenant.