Skip to content

Products

Paginated list of catalog products. The response is a slim card-level payload — fields needed to render listing/grid/search results. For the full PDP shape (categories, variants, bundle options, booking config, customizable options, related products, …) use Single Product.

Endpoint

GET /api/shop/products

This same endpoint also powers all filtering, sorting and search — see Search Products for the full set of query parameters.

Request Headers

HeaderRequiredDescription
AcceptYesapplication/json
X-STOREFRONT-KEYYesStorefront API key (pk_storefront_…)
X-LocaleNoOverride request locale
X-ChannelNoOverride channel scope
X-CurrencyNoOverride currency in formattedPrice etc.

Query Parameters

ParameterTypeDefaultDescription
pageinteger1Page number (1-based)
per_pageinteger30Items per page. Capped at 50.

For all filter / search / sort parameters (query, sort, type, category_id, price, attribute filters …) see Search Products.

Pagination headers (X-Total-Count, X-Page, X-Per-Page, X-Total-Pages) are always emitted — see Pagination.

Card-level fields

These ~20 fields are returned for every product in the list. The PDP endpoint returns these plus every related resource inlined.

FieldTypeDescription
idintegerProduct primary key
skustringStock-keeping unit
typestringsimple, configurable, bundle, grouped, virtual, downloadable, booking
bookingTypestring | nullWhen type = booking: default, appointment, rental, event, table — otherwise null
namestringLocalized name
urlKeystringURL slug
statusbooleanWhether the product is published
shortDescriptionstringShort marketing description
pricenumberDefault price
specialPricenumber | nullSale price when in promotion window
newboolean"New" flag set in admin
featuredboolean"Featured" flag set in admin
minimumPricenumberLowest price across variants (configurable / bundle)
maximumPricenumberHighest price across variants
formattedPricestringCurrency-formatted price
formattedSpecialPricestring | nullCurrency-formatted specialPrice
formattedMinimumPricestringCurrency-formatted minimumPrice
formattedMaximumPricestringCurrency-formatted maximumPrice
baseImageUrlstring | nullPrimary thumbnail URL
isInWishlistinteger (0/1)1 if this product is in the signed-in customer's wishlist (active channel), else 0. 0 for guests.
isInCompareinteger (0/1)1 if this product is in the signed-in customer's compare list, else 0. 0 for guests.

Heavy relations (images, videos, categories, channels, variants, bookingProducts, bundleOptions, customizableOptions, relatedProducts, etc.) are omitted from the list response. Fetch the Single Product to get them inlined.

Wishlist & compare flags

Every product card carries two per-customer booleans, isInWishlist and isInCompare, so you can render the wishlist and compare icon states straight from the listing response.

Why they exist: the wishlist and compare lists are their own endpoints and paginate independently of the catalog — a product on catalog page 1 may have its wishlist entry on a different wishlist page, so matching the two lists on the client is unreliable. These flags answer the question per product, in the same response, so the wishlist/compare icon can be highlighted without any extra requests.

  • Authentication is required. Include the customer Bearer token alongside the storefront key. For guests (no customer token) both flags are always 0.
  • isInWishlist is scoped to the active channel; isInCompare applies across the store.
  • A product is flagged (1) when its own product ID is in the customer's wishlist / compare list — so a configurable parent is flagged when the parent itself was added.
  • The REST API returns these as 1 / 0 integers. (Over GraphQL the same flags are returned as the strings "1" / "0".)

Use Cases

  • Render a category page / grid view / search results with paginated cards.
  • Implement infinite scroll using ?page=N&per_page=20 and X-Total-Pages.
  • Pre-load the next page based on X-Page < X-Total-Pages.

Released under the MIT License.