Skip to content

Single Product

Returns the full PDP-ready document for a product — categories, channels, attribute family, images, videos, super-attributes (configurable parents), variants, bundle options, booking config, grouped members, downloadable links / samples, customizable options, related/up-sell/cross-sell products — all embedded inline. No follow-up requests are needed to render a complete product detail page.

Endpoint

GET /api/shop/products/{id}

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.

Path Parameters

ParameterTypeRequiredDescription
idintegerYesProduct primary key

Response

200 OK — single product object. The card-level fields (id, sku, type, name, price, …) are present, plus the embedded relations below.

Card-level fields

Same shape as items in Products — see that page for the field table. This includes the per-customer isInWishlist and isInCompare booleans.

The isInWishlist and isInCompare flags report whether the signed-in customer already has this product saved, so the product page can highlight both icons without a separate lookup. isInWishlist is scoped to the active channel. Send the customer Bearer token alongside the storefront key; for guests both are 0. REST returns them as the integers 1 / 0, where GraphQL returns the strings "1" / "0".

Always-present extras (over the list)

FieldTypeDescription
descriptionstringFull HTML description
createdAtstring (ISO)Creation timestamp
updatedAtstring (ISO)Last update timestamp
isSaleablebooleanWhether the product passes saleability checks (stock, status, …)
attributeFamilyobject{ id, code, name }

Filterable attribute summary

The default-family product output includes top-level shortcuts for the common filterable attributes:

FieldTypeDescription
colorobject | null{ id, code, label } for the assigned color option
sizeobject | nullSame shape, for size
brandobject | nullSame shape, for brand

The exact set depends on the product's attribute family. These three are present for the default family.

Embedded relations (always present, may be empty [])

FieldTypeNotes
categoriesarrayCategories the product belongs to
channelsarrayChannels exposing this product ({ id, code, hostname, currencyCode, localeCode })
imagesarrayImage objects ({ id, type, path, productId, position, publicPath })
videosarrayVideo objects
superAttributesarrayConfigurable type only — list of attributes used to build variants, with their options inlined
variantsarrayConfigurable type only — child variant products, each carrying card-level fields
bookingProductsarrayBooking type only — slot config with type (default/appointment/rental/event/table), a type-specific slots block, and the "starting from" price fields (see below)
bundleOptionsarrayBundle type only — option groups with their member products inlined
groupedProductsarrayGrouped type only — associated products
downloadableLinksarrayDownloadable type only — purchasable links
downloadableSamplesarrayDownloadable type only — preview samples
customizableOptionsarrayPer-product custom inputs (text fields, file uploads, dropdowns added by the merchant)
relatedProductsarray"Customers also bought" cards
upSellsarray"Upgrade to" cards
crossSellsarray"Pairs well with" cards (shown in the cart/checkout)

Blocks that do not apply to the product's type come back as an empty array rather than being absent — a simple product has no variants, a booking product has no bundleOptions. Render each only when it is non-empty.

What this endpoint deliberately omits

  • attributeValues — the raw EAV table is not returned. Use the typed fields (name, description, price, color, …) instead.
  • Reviews — fetched separately via the Product Reviews endpoint, paginated.

Sub-resource endpoints

Each embedded relation is also available as its own URL — useful when you only need one slice without re-fetching the entire PDP. These are tagged Product or Product Types in Swagger UI:

URLReturns
GET /api/shop/products/{productId}/variantsVariants for a configurable product
GET /api/shop/products/{productId}/booking-productsBooking config row(s) for a booking product
GET /api/shop/booking-products/{id}Single booking config (with type-specific slots)
GET /api/shop/booking-slots?id={bp}&date=YYYY-MM-DDRuntime slot availability — see Booking Slots

Booking "starting from" price

Each bookingProducts[] entry carries a computed "starting from" price — the product base price plus the cheapest bookable extra — matching the storefront card / detail page:

FieldMeaning
startingPriceFinal (post-discount) starting price.
formattedStartingPriceCurrency-formatted startingPrice.
startingRegularPricePre-discount starting price. Differs from startingPrice only when an event ticket is on sale — use both for a strike-through.
formattedStartingRegularPriceCurrency-formatted startingRegularPrice.

The extra is the cheapest event ticket (event) or the minimum rental unit rate (rental). For default / appointment / table all four are null and the product's own price is the correct figure.

Use Cases

  • Render a full product detail page in one network round trip.
  • Configurable variant picker: read superAttributes[] for the dimensions, variants[] for the inventory of combinations.
  • Bundle builder: walk bundleOptions[] for the selectable groups; each group has its member products inlined.
  • Determine product type at render time via type (simple, configurable, bundle, …) and bookingType (default, appointment, rental, event, table for booking products).

Best Practices

  • Look up by numeric id — the path takes the product ID only; a slug answers 404. Resolve a storefront slug through the GraphQL product(urlKey:) lookup, or match urlKey from the listing.
  • Render blocks conditionally on length, not on type — every relation key is present on every product, so variants and bundleOptions exist as empty arrays on a simple product.
  • Check isSaleable before enabling add-to-cart — a product can be published yet out of stock, and only this flag combines the checks.
  • Read booking slots separatelybookingProducts describes the configuration; the bookable times for a date come from Booking Slots.
  • Send the customer token — without it isInWishlist and isInCompare are 0, and the product page renders both icons as unset.
  • Products — paginated card-level list
  • Search Products — full filter / sort / search reference
  • Booking Slots — runtime availability for booking products
  • Categories — category ids and slugs for filtering the catalog
  • Attributes — attribute definitions with their options inlined

Released under the MIT License.