Line resolvers
For extension builders. The Product Catalog is a line resolver: the catalog carts a storefront creates are resolved carts, and Throttle calls the catalog to price their lines. This page describes the
catalog's resolver, POST /throttle/line-resolver, as an example of the
contract. Throttle's Line resolvers page is the full
contract for building your own.
When Throttle calls it
Throttle never takes a price from the browser for a line the catalog owns. It calls the resolver with an intent:
| intent | When | Lines sent |
|---|---|---|
add | A line is added to the cart. | That line. |
update | A line’s quantity changes. | That line. |
refresh | A shopper signs in with the cart, or the cart is refreshed. | Every catalog line. |
checkout | The shopper starts checkout. | Every catalog line. With “Reserve stock at checkout” on, the catalog also holds their stock for 15 minutes. |
Throttle waits at most 3 seconds and does not retry an add or update (the shopper is waiting); it retries a checkout once.
The request
{
"type": "cart.lines.resolve",
"id": "4b0f3a52-8f6e-4c4f-9d0a-3c1b2e7d9a10",
"installationId": "62e32355-72ed-4bf6-b738-fcd554a3e851",
"applicationId": "7f9d4c8a-5b2e-4f16-9a73-2d1e5c8b6f40",
"environmentId": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"cartId": "0f8e7d6c-5b4a-4392-8170-6f5e4d3c2b1a",
"intent": "checkout",
"currency": "USD",
"customerId": "c3d4e5f6-0a1b-4c2d-8e3f-4a5b6c7d8e9f",
"customerEmailVerified": true,
"locale": "en-US",
"lines": [
{
"lineId": "9a8b7c6d-5e4f-4a3b-9c2d-1e0f9a8b7c6d",
"referenceId": "BRK-PAD-01",
"quantity": 2
},
{ "lineId": null, "referenceId": "BRK-FLUID-1L", "quantity": 5 }
]
}
-
typeis alwayscart.lines.resolve, never a webhook event type;idis Throttle's request id. -
installationId,applicationIdandenvironmentIdonly say which installation to look up. The signature decides: the catalog checks that the signed body names the same application and environment as that installation. -
customerIdis the cart's customer, ornull; group prices apply only whencustomerEmailVerifiedis true.localepicks the language of the line names. -
1 to 50
lines, each{ lineId, referenceId, quantity }.referenceIdis the variant SKU, matched exactly; the body is at most 64 KB.
The signature
Throttle signs every call with the installation's webhook signing secret, in the header X-Throttle-Resolver-Signature: t=<unix seconds>,v1=<hex>. The signed
string is throttle.line-resolver.v1.<t>.<raw body>, HMAC-SHA256.
The purpose label throttle.line-resolver.v1 keeps resolver and webhook
signatures apart, though they share a secret: neither can be replayed as the other.
- The catalog verifies the raw body before it parses it as JSON, with a constant-time compare.
tmust be within 5 minutes; during a secret rotation the header carries onev1per secret, and any match passes.- Every verification failure is the same
401, whatever the reason.
The answer
200, no-store, with one line per requested line,
in order. A priced line carries ok: true and the name, unit price in minor
units, currency, image, quantity limits and shipping flag; a refused line carries ok: false and a code. The answer has one
other field, internal to Throttle's stock handling.
{
"lines": [
{
"referenceId": "BRK-PAD-01",
"ok": true,
"name": "Ceramic brake pads, front",
"unitPrice": 4999,
"currency": "USD",
"imageUrl": "https://cdn.example.com/brk-pad-01.jpg",
"minQuantity": null,
"maxQuantity": 10,
"requiresShipping": true,
"metadata": {
"catalogInstallationId": "62e32355-72ed-4bf6-b738-fcd554a3e851",
"catalogPriceMinor": 4999,
"sku": "BRK-PAD-01",
"variantId": "1042"
}
},
{
"referenceId": "BRK-FLUID-1L",
"ok": false,
"code": "QUANTITY_UNAVAILABLE",
"maxQuantity": 3
}
]
}
| Refusal code | Meaning |
|---|---|
NOT_FOUND | No public variant with that SKU in this installation (another store’s SKU too). |
NOT_PURCHASABLE | A draft, archived or unpriced variant. |
OUT_OF_STOCK | A tracked variant with none on hand. |
QUANTITY_UNAVAILABLE | Not enough available; maxQuantity is the most that can be ordered now, and can be 0. |
BELOW_MIN | Under the variant’s minimum order quantity. |
ABOVE_MAX | Over the variant’s maximum order quantity. |
NO_PRICE_IN_CURRENCY | No price in the cart’s currency. |
PRICE_SOURCE_MISMATCH | The source that set the price does not hold the cart’s currency. |
The price always comes from the catalog, never from the request. Stock quantities are never answered: maxQuantity on a priced line is the order limit, and on a refused line only
what a refused quantity needs.
The reserved metadata namespace
A priced line's metadata is stored on the Throttle line, and a browser can
never write it. The catalog fills it with catalogInstallationId, catalogPriceMinor, sku and variantId. It is at most 2 KB of JSON; an answer over that is never sent.
Refusals and failing closed
| Answer | When |
|---|---|
401 AUTHENTICATION_FAILED | A missing or bad signature, an unknown installation, or a body that names another store. |
400 INVALID_REQUEST | A signed body that is not a valid request. |
409 INSTALLATION_NOT_ACTIVE | The installation is not active. |
429 RATE_LIMITED | More than 6,000 calls a minute for one installation. Retry-After: 60. |
503 STOREFRONT_UNAVAILABLE | The catalog cannot answer right now, or its resolver is switched off. |
Throttle fails closed. When the resolver times out, refuses the call or answers anything
malformed (a missing line, an invalid line), the cart change is refused with 503 line_resolver_unavailable and nothing on the cart changes. Throttle never
falls back to a browser price or a stale one. The catalog does the same on its side: if it cannot read the
catalog, or would build an answer Throttle would refuse, it answers 503 rather
than guess.
Storefronts never call this route; they see its results as cart errors.