Product Catalog

Stock and availability

The storefront API never answers a stock quantity. It tells you whether a product or variant can be bought and whether stock is running low, and the cart refuses a quantity it cannot fill.

What the API tells you

FieldOnMeaning
availabilityProducts and variantsin_stock, out_of_stock, preorder, backorder or null (not set).
lowStockProducts and variantsA tracked variant at or below the store’s low-stock threshold. A product whose stock is all held in checkouts answers false (it is out of stock instead).
purchasableFull products and variantsPublic, priced in the answer’s currency, and available or backorderable.

That is all: no counts, ever. A storefront can show “In stock”, “Only a few left” or “Sold out”, never “3 left”. The one number the API answers is maxQuantity, and only when a requested quantity is refused (below). Stock is counted only for variants that track inventory, and a tracked variant that allows backorders is never refused for stock.

Availability labels and a quantity check
import { createCatalogClient } from '@usethrottle/catalog';

const catalog = createCatalogClient({
  publishableKey: 'pc_pk_your_browser_key',
});

const answer = await catalog.products.get('ceramic-brake-pads');
if (!('redirectTo' in answer)) {
  for (const variant of answer.product.variants) {
    // No quantities: availability, lowStock and purchasable are all there is.
    const label = !variant.purchasable
      ? 'Unavailable'
      : variant.availability === 'backorder'
        ? 'Ships when back in stock'
        : variant.lowStock
          ? 'Only a few left'
          : 'In stock';
    console.log(variant.sku, label);
  }
}

// Check lines without creating a cart, for example when a cart page loads.
const check = await catalog.carts.check([{ sku: 'BRK-PAD-01', quantity: 12 }]);
for (const item of check.items) {
  if (item.ok || item.code !== 'QUANTITY_UNAVAILABLE') continue;
  // maxQuantity can be 0: other shoppers' checkouts hold the last units.
  console.log(
    item.maxQuantity === 0
      ? 'In other shoppers’ checkouts right now. Try again in a few minutes.'
      : `Only ${item.maxQuantity} can be ordered.`,
  );
}

A quantity the store cannot fill

carts.check, carts.create and every change to a resolved cart judge each line against the stock available right now. A tracked line that wants more than that is refused with QUANTITY_UNAVAILABLE and maxQuantity, the most that can be ordered now (also capped by the variant's maximum order quantity). A line with none on hand is OUT_OF_STOCK.

  • On carts.check the refusal is a line of the answer (ok: false).
  • On carts.create it is 422 CART_ITEMS_INVALID, and on a cart session Throttle's 409 quantity_unavailable; the SDK throws both as CartLineError, whose lines carry maxQuantity.
  • maxQuantity can be 0 while units are still on hand: with reservations on, other shoppers' checkouts can hold the last units (the variant then reads out_of_stock until a hold lapses). Say so (“In other shoppers' checkouts right now”) rather than “Sold out”: a hold that is not paid for lapses within minutes.

Reserve stock at checkout

Reserve stock at checkout is a per-store setting in the Product Catalog dashboard, under Storefront › Settings › Stock. It is off by default; with it off, stock is the quantity you import or enter, and nothing below happens. With it on, the catalog keeps its own count:

  • Holds at checkout. When a shopper starts checkout from a catalog cart, the catalog holds the cart's tracked lines for 15 minutes, only if every line fits; otherwise it holds nothing and refuses the lines that do not fit. Starting checkout again replaces the cart's holds. A cart's own holds never count against it.
  • Holds lapse on their own. An unpaid hold simply expires; nothing has to release it. An abandoned cart releases its holds earlier when Throttle reports it.
  • Sales. An order takes its catalog lines off stock and releases its cart's holds.
  • Cancels and returns. A cancelled order puts its lines back, and so does a return once it is received with restocking on. A refund of money alone changes no stock.
  • Counting starts from the quantities you have when you turn it on; earlier orders never move stock. A new quantity from an import or the API becomes the new starting point.

Turning it on needs the Product Catalog extension version that reads carts and returns; the dashboard says so if the store has not accepted it yet.

Oversold orders

The order is the truth. A paid order is never refused or changed because of stock: its sale is always recorded, even when that takes a variant below zero. This can happen because Throttle's checkout session lasts 30 minutes, longer than the 15-minute hold, so a shopper who pays late can buy units another checkout has since taken.

What the merchant sees
The product's Inventory card and the Products list show Oversold by N for a variant that sold N more units than it has. The merchant fixes it by restocking, with “Adjust stock”, or by cancelling an order, which puts its units back.

See Cart and checkout for every cart error, and Line resolvers for how Throttle asks the catalog to check stock.