Getting Started
Changelog
Dated, newest-first record of every change to the public storefront API. Read this before upgrading an integration.
View as MarkdownThe storefront API is versioned by URL prefix (/v1). Within v1 every change is additive: fields, endpoints, optional parameters and enum values are only ever added. Each entry below names the date it shipped, what changed, and which pages of this reference were updated. The same entries appear in the repository changelog.
2026-10-11
Added
capability(standardormade_to_measure) andcapability_active(boolean) on the product object ofGET /v1/api/commerce/productsandGET /v1/api/commerce/products/{id}.capabilitysays how the product goes into a cart;capability_activeisfalsewhile the module that sells it (commerce forstandard, configurator formade_to_measure) is disabled. See Products.uniton the product object (piece,kg,g,l,m,m2,m3,hour) and on every order line (lines[].unit, the product's unit at order time). See Products and Orders.- Product fields
compare_at_amount,barcode,weight_grams,requires_shipping,track_stock,returnable,lead_time_days,tagsandsocial_image, and variant fieldsbarcodeandweight_grams. See Products. GET /v1/api/redirects?path=returns{data: {from_path, to_path, status}}for a product or category path whose slug changed (404 when none), andGET /v1/api/sitemaplists every active product and category path as{data: [{type, path, lastmod}]}. Both takecatalog:readorcommerce:readand the catalog module. See Redirects and sitemap.- A top-level
codeon the 422 a cart line add refuses with (POST /v1/api/commerce/carts/{id}/lines):CAPABILITY_INACTIVE,INVALID_CONFIGURATIONorNOT_PURCHASABLE, next to the unchangedmessageanderrors. See Cart lines and Errors and rate limits. attributes.returnableandattributes.lead_time_dayson every order line placed through checkout or an accepted quote; a made-to-measure line is never returnable. See Orders.
Changed
- Breaking:
typeis removed from the product object. Usecapabilityinstead, plusrequires_shippingandtrack_stock: formersimpleandvariableproducts arestandard;configurableproducts aremade_to_measure(and not returnable);digital,licenseandsubscriptionproducts arestandardwithrequires_shipping: falseandtrack_stock: false. See Products. - Breaking: quantities are decimal. Cart line, order line and quote line
quantity, availabilityavailable, the checkout and quote-acceptout_of_stock[].requestedand.available, and thestock.adjustedwebhookquantityare JSON numbers that may be fractional (0.75,12).quantityonPOST /v1/api/commerce/carts/{id}/linesandPATCH .../lines/{lineId}accepts up to three decimals; a product withunitpiecestill takes whole numbers only (422 onquantity). See Cart lines, Carts, Orders, Availability and Webhooks. - Breaking: the configurator endpoints (
GET /v1/api/commerce/configurator/products/{id}/schema,POST .../quote) are gated by the configurator module: without it they answer403 {"error":"Module not enabled: configurator"}(wascatalog). They answer 404 for a product whosecapabilityis notmade_to_measure. See Configuration schema, Price quote and How the configurator works. - Configurator dimensions must be whole numbers; a fractional value used to be truncated and is now refused. The quote endpoint answers 422 on
dimensions.{key}({Label} must be a whole number.) in its plain validation envelope, without a top-levelcode; a cart add answers the same error withcodeINVALID_CONFIGURATION. See Price quote and Cart lines. - Breaking:
components[].qtyin the configuration snapshot on cart and order lines is an exact decimal string ("2.16"), was an integer. See Carts and Orders. - Breaking:
POST /v1/api/commerce/carts/{id}/quote-requestbelongs to the sales module and answers404 {"error":"Module not enabled: sales"}when the store has sales disabled. Quote triggers fire only when sales is enabled and quote requests are switched on; otherwiserequires_quoteis always false and checkout never refuses withQUOTE_REQUIRED. See Quote requests, Checkout and Errors and rate limits. purchasabilityis set by the sales module and readsdirectwhen the merchant set nothing; it is reported even when sales is disabled. Aquoteproduct can be added to a cart: the cart reportsrequires_quote: truewith reasonproduct_purchasability, checkout refuses with409 {"code": "QUOTE_REQUIRED", "reasons": [...]}, and the cart goes through the quote request instead. A contact group's always-quote setting is now a sales setting; thecustomer_groupreason is unchanged. See Products and Quote requests.stock_statusisuntrackedfor a product withtrack_stock: falseand all of its variants, even when stock rows exist, on the product object and onGET /v1/api/stock/availabilityandGET /v1/api/stock/products/{productId}(tracked: false). See Products and Availability.- Checkout requires
shipping_method_idwhen the cart holds a line whose product hasrequires_shipping: true(was: a product of typesimple,variableorconfigurable). See Checkout and Shipping methods. - Quote lines created from a cart carry
unitpcsfor a product sold by the piece and the product's unit otherwise (wasnull). See Quote requests and Quote tokens. - Accepting a quote in order mode reserves stock through each product's line handler at the quoted price, so a made-to-measure line deducts its components. Only a real shortage refuses with
OUT_OF_STOCK; a line whose product can no longer be sold converts without stock movement. See Quote tokens.
Deprecated
QUOTE_NOT_ORDERABLE(422) onPOST /v1/api/sales/quotes/by-token/{token}/acceptandPOST /v1/api/commerce/quotes/by-token/{token}/acceptis no longer returned: a quote with fractional line quantities converts to an order with those quantities. Keep tolerating the code, but expect never to see it. See Quote tokens.
Notes
- The
/v1surface was restructured before any production consumer depended on it, so the breaking changes above ship inside/v1instead of under a new URL version. From this release on, changes within/v1are additive again. - Documentation fixes, nothing changed in the API: the product and category endpoints accept
catalog:readorcommerce:readand are gated by the catalog module (the reference namedcommerce:readandcommerce); the order object'sinvoice_idand the full quote request response (origin,intro,terms,acceptance_mode,pdf_url,signed_at,signed_pdf_url, and the line fieldskind,unit,description,tax_rate,unit_price) are now documented. See Products, Categories, Orders and Quote requests.
2026-09-17
Added
discount_total(money) anddiscount_breakdown(array or null) on the order object returned byPOST /v1/api/commerce/carts/{id}/checkout,GET /v1/api/commerce/orders/{id}andGET /v1/api/commerce/my/orders.discount_total.amountis the goods discount applied at checkout (0when none);discount_breakdownlists one{discount_id, name, code, kind, amount}entry per applied discount and isnullwhen none applied. Both are additive; no existing field changed. See Orders and Checkout.messageon theOUT_OF_STOCKrefusal ofPOST /v1/api/commerce/quotes/by-token/{token}/accept(order mode only). The 409 body now reads{"code": "OUT_OF_STOCK", "message": "Insufficient stock.", "out_of_stock": [...]}; theout_of_stockrows are unchanged. Additive, and the envelope now matches every other refusal on the endpoint (code+message). See Quote tokens.
Changed
POST /v1/api/commerce/carts/{id}/claimnow merges every otheractivecart the customer owns into the claimed cart, not only the first one found. Each folded cart flips tomergedand releases its stock holds, exactly as before; there is simply no longer an orphaned active cart left behind. See Claiming a cart and Storefront guide.- The claim merge carries identity across: the claimed cart keeps its own
emailandmetadatawhen set and otherwise takes the merged cart's value, so a guest email captured before login is no longer lost. No field was added or retyped on the cart envelope. See Claiming a cart.
Notes
POST /v1/api/commerce/carts/{id}/lines: two simultaneous identical adds (same product, variant and configuration) used to make the loser fail with a 500. The loser now folds its quantity into the winning line and returns the same 201 envelope as before, still clamped tomax_quantity_per_line. See Cart lines.X-Customer-Tokenhandling is now consistent with the documented rule "must resolve to a customer, else 401": a token past itsexpires_at, or older than the platform session window, no longer resolves, so it cannot claim or check out a customer-owned cart. Log in again to obtain a fresh token. See Claiming a cart and Checkout.POST /v1/api/customers/forgot-passwordstill answers 202 with the same body for known and unknown addresses; it now also does the same amount of queue work on both paths, so the response time no longer hints at whether an account exists. See Passwords.- A
bogodiscount now grants its get quantity once per qualification instead of once per cart: the discounted units areget_quantity × floor(qualifying buy quantity ÷ minimum buy quantity), capped at the quantity of the cheapest get line. Thediscounts[].amounton the cart and the order'sdiscount_totalgrow accordingly; no field or enum value changed. See Cart discounts. - Products of type
configurableare priced by the configurator engine only; customer-group and tier price rows left over from a type change no longer surface inprice,quantity_tiersor the cart line price for such a product. Simple products are unaffected. See Products. POST /v1/api/commerce/quotes/by-token/{token}/accept: theQUOTE_NOT_OFFEREDrefusal raised while the order is being built (a race with another accept of the same quote) now carries the documented messageQuote is not awaiting a response (status: ...)instead ofQuote cannot be accepted in its current status (...). Code and status are unchanged. See Quote tokens.
2026-09-04
Changed
- A configurator choice whose
requiresrule cannot be satisfied by the other published choices is now omitted fromGET /v1/api/commerce/configurator/products/{id}/schemainstead of being published and then rejected at quote time. The filtering propagates, so every choice the schema exposes can be selected and priced. See Configuration schema and How the configurator works. POST /v1/api/commerce/configurator/products/{id}/quoteapplies the same availability rule: selecting a choice the schema no longer publishes fails with the existing genericchoicesmessage, and the per-choicechoices.{choiceId}messages now name only choices that are still available, so a deactivated choice is never disclosed in an error. See Price quote.- A price matrix with a condition on a choice that is not available is now omitted from
price_matricesinstead of being listed with a shortenedconditionsarray. Such a matrix can never become eligible, so the engine treats the configuration asnot_priceablerather than falling back to it. See Configuration schema.
Notes
- Documentation fix on both configurator endpoints: they accept an API key carrying either the
catalog:reador thecommerce:readscope (unchanged since 2026-08-25), and their module gate is the catalog module, so the 403 body reads{"error":"Module not enabled: catalog"}. The reference namedcommerce:readandcommercefor both. Nothing changed in the API. See Price quote, Configuration schema and How the configurator works.
2026-08-26
Added
- Customer endpoints are now also served at
/v1/api/contacts/*; the previous/v1/api/customers/*paths remain as a permanent alias with identical request and response shapes. New API-key scopescontacts:readandcontacts:writeare available; existingcustomers:read/customers:writekeys keep working unchanged. See Contact accounts. - The contact profile (
GET/PATCH /v1/api/contacts/me, also.../customers/me) now includeskind(personorcompany),company_name,registration_number,vat_id,external_id, anddisplay_name. These fields are read-only through the storefront API.emailcan now benullfor any merchant-created contact; storefront registration still requires it. See Profile. - The address
typefield accepts a new value,work, alongsideshippingandbilling. See Addresses. contact_idon order and merchant quote objects: the same value ascustomer_id, under the canonical name going forward.customer_idis unchanged and stays. The token-addressed quote object carries neither. See Orders and Checkout.- The quote-token endpoints are now also served at
/v1/api/sales/quotes/by-token/{token}(GET,/accept,/decline)./v1/api/commerce/quotes/by-token/*keeps working, identically and permanently; both paths accept a key withsales:read/sales:writeorcommerce:read/commerce:write. See Quote tokens. GET /v1/api/{sales,commerce}/quotes/by-token/{token}returnsorigin,intro,terms,scope_items[],acceptance_mode,pdf_url,signed_atandsigned_pdf_url; each entry oflines[]gainskind,unit,descriptionandtax_rate. See Quote tokens.POST /v1/api/{sales,commerce}/quotes/by-token/{token}/acceptaccepts an optionalsignaturebody parameter — a PNG data URL of at most 2 MB, required when the store'sacceptance_modeissignature— and returnssigned_pdf_url. New error codesSIGNATURE_REQUIRED(422),SIGNATURE_INVALID(422),QUOTE_NOT_ORDERABLE(422),ALREADY_CONVERTED(409) andQUOTE_EXPIRED(410). See Quote tokens.- New scopes
sales:readandsales:write, accepted anywherecommerce:read/commerce:writeis accepted on the quote-token endpoints. See Authentication. - A quote's
statusmay now bedraft: merchant-written quotes exist before they are sent. A draft has no number and is never reachable by token.
Changed
- Customer endpoints no longer require a module to be enabled; the 403
Module not enabled: customersresponse can no longer occur. See Customer accounts and Errors and rate limits. - Quote line
quantity(merchant quotes and the token-addressed quote object) is documented asnumberinstead ofinteger: product lines still always report a whole number, but service and material lines can now carry a fractional quantity (hours, metres). Every previously-integer value keeps serializing exactly as before. See Quote requests and Quote tokens. - Accept and decline on the quote-token endpoints are throttled to 30 requests per minute, keyed by the caller's IP. See Errors and rate limits.
Notes
lines[].quantityon the quote-token endpoints was always a JSON number and still is; it may now be fractional (two decimals). No field was removed, renamed or retyped.
2026-08-25
Changed
- Public catalog routes (
/v1/api/commerce/products,/v1/api/commerce/categories,/v1/api/commerce/configurator/*) now accept an API key carrying either thecatalog:reador thecommerce:readscope. Existing keys keep working unchanged. See Authentication. - New API-key scopes
catalog:readandcatalog:writeare available to stores with the Catalog module enabled. Scope names for every other module are unchanged.
Notes
- No request parameter, response field, status value or error envelope changed in this release.