Table of Contents

Public API

/api/public — what a third party consumes

The only surface consumed by someone outside the business. Small on purpose, and the one place where a breaking change costs a phone call rather than a deploy.

Important

What exists today is one endpoint group: batch product lookup by EAN or SKU, returning a customer's price and live stock. It is not a general-purpose commerce API, and this page should not be read as promising one.

Who calls it

A third party integrating against the client — a procurement system, a marketplace, a customer's own purchasing tool.

Authentication An X-API-Key header, on a scheme of its own
Authorization The key scheme alone — no policy beyond a valid, live key
Scope Bypass; the caller names the customer
Rate limit Yes — a fixed window per token, tunable live
Contract stability Published, external. Assume someone depends on every field

Tokens

Credentials are created and revoked from the admin surface. Two kinds exist, and the difference decides how the caller names a customer:

Type Bound to The caller supplies
Integrator Nothing — it can act for any customer X-Customer-No on every request
Customer One customer, at creation Nothing; the customer comes from the token

The plaintext key is shown once and never stored. Only a hash and a short non-secret prefix persist — the prefix so a token can be identified in a list without revealing it. A lost key cannot be recovered, only replaced.

Tokens carry an optional expiry, a revocation timestamp, and a last-used timestamp. Last-used is the one to look at before revoking something nobody remembers issuing.

What it returns

Batch lookup, by EAN or by SKU, capped at 100 keys per call. Each result carries whether the product was found, the customer's net unit price at quantity one with promotions excluded, the currency, and live stock.

Every successful response is wrapped:

{
  "data": [
    { "key": "SHIRT-BLK-M", "found": true, "value": { "unitPrice": 16.33, "currency": "AUD" } }
  ]
}

Why an envelope on this surface only. An external consumer benefits from one predictable shape they can write a client against, and from room to add fields beside data later without breaking anyone. Internal surfaces do not need that — they change together with their callers.

Why unknown keys are not errors. A batch of a hundred SKUs where four are unknown is a normal result, not a failure. Each item reports found individually, so the caller gets the ninety-six it asked for.

Failures look different here too

Errors on this surface go through a problem shape of their own, applied at the outermost layer of the pipeline — ahead of everything else. That ordering is deliberate: it guarantees that a fault anywhere inside Commerce cannot leak an internal error shape, a stack trace or a domain-specific message to an external party.

400 Missing X-Customer-No on an integrator token, an empty list, or more than 100 keys
401 Missing, invalid, revoked or expired key
404 Unknown customer

The rate limiter's one subtlety

The limit is partitioned per token, so one noisy integrator cannot exhaust everyone else's budget. Making that work required putting the rate limiter after authorization in the pipeline, which is the opposite of the usual advice.

The reason: the API-key scheme is not the default scheme, so it only runs during authorization. Place the limiter earlier and there is no token identity to partition by yet — every public request falls into one bucket and the isolation silently disappears. It is a comment in the code, repeated here because comments get deleted.

If you are extending this surface

Three things are true here that are not true anywhere else in Commerce:

  1. Someone outside will notice a change. Additive is safe; renaming and removing are not.
  2. The envelope is the contract. Return the payload; the wrapper adds data around it.
  3. A rate limit exists and applies. Test with it on, not around it.