Appearance
Get product collection (storefront callers see active, non-deleted products only)
GET
/rest/v1/product/product
Storefront callers — guest (no token) and customer — receive only active, non-deleted products (active = 1 AND soft_delete = 0): filter[active] and filter[softDelete] are server-forced to those values for them, so a supplied value is ignored rather than combined. Zero-priced products are NOT hidden — they are predominantly gift SKUs and a storefront needs the rows to render a gift option. Backend callers are unaffected and may filter by filter[active] / filter[softDelete] freely to see inactive and soft-deleted products. filter[priceGte] / filter[priceGt] / filter[priceLte] / filter[priceLt] are OPT-IN one-sided bounds over the raw shop_product.price column; none of them changes the default visibility described above — a request that sends no bound still receives zero-priced and NULL-priced products, and filter[priceGt]=0 is how a caller asks to exclude them. Two sort keys beyond the column sorts: sort=finalPrice / -finalPrice orders by the CONSUMER price (shop_prices_view.final_price — gross, VAT-inclusive, discount applied; sort=price orders the raw pre-discount, VAT-exclusive column and is not what a shopper sees), and sort=availability puts sellable products (negative stock allowed, or any active code with stock > 0) before sold-out ones. Both keep every row and leave the pagination total unchanged. Sort keys apply in request order, so sort=availability,-hits is "sellable first, best sellers within each group".
Parameters
Query Parameters
filter[id]
Filter by product IDs
Type
string
filter[vatId]
Filter by VAT ID
Type
string
filter[vendorId]
Filter by vendor ID
Type
string
filter[badgeId]
Filter by badge ID
Type
string
filter[priceGte]
INCLUSIVE minimum price — matches shop_product.price >= value, so a product priced exactly at the value IS returned. May be sent alone or combined with filter[priceLte] / filter[priceLt] to bound a range. Bounds the RAW price column, NOT a post-discount final price. Products whose price is NULL are EXCLUDED as soon as any of the four bounds is supplied (NULL >= x is never true). A non-numeric value is ignored for THIS key only — every other filter still applies and the request still returns 200. filter[price] is unaffected and stays an exact match.
Type
number
Format
"float"filter[priceGt]
EXCLUSIVE minimum price — matches shop_product.price > value, so a product priced exactly at the value is NOT returned. filter[priceGt]=0 is the supported way to request only non-zero-priced products (it excludes the zero-priced gift SKUs, which are returned by default); prefer it over filter[priceGte]=0.0001, which would hard-code this column's decimal(11,4) scale into the client. Otherwise the same semantics as filter[priceGte].
Type
number
Format
"float"filter[priceLte]
INCLUSIVE maximum price — matches shop_product.price <= value, so a product priced exactly at the value IS returned. Same semantics as filter[priceGte] otherwise.
Type
number
Format
"float"filter[priceLt]
EXCLUSIVE maximum price — matches shop_product.price < value, so a product priced exactly at the value is NOT returned. Same semantics as filter[priceGte] otherwise.
Type
number
Format
"float"filter[barcode]
Filter by barcode
Type
string
filter[tagId]
Filter by tag id. Comma-separated ids are combined with PLAIN OR: a product matches when it carries ANY of the listed tags — the same multi-value meaning filter[categoryId] and filter[vendorId] already have on this endpoint. INDEPENDENT OF ADMIN CONFIGURATION: the per-tag-category AND/OR flags (shop_product_tag_categories.tag_category_behavior / .tag_values_behavior) are NOT read for this key, so the same request returns the same rows however those flags are set. This is the key to use from an admin screen, a feed, MCP or an integration. Tag ids that match no product-tag rows return an empty result, never the unfiltered list. May be combined with filter[tagFacetId]; the two are ANDed.
Type
string
filter[tagFacetId]
Filter by tag id with LEGACY STOREFRONT FACET semantics. Same input shape as filter[tagId], but the ids are grouped by their tag category and the AND/OR combination — both BETWEEN categories and BETWEEN the tags inside one category — IS RESOLVED FROM PER-TAG-CATEGORY ADMIN CONFIGURATION (shop_product_tag_categories.tag_category_behavior: 0 = AND, 1 = OR; .tag_values_behavior: 0 = AND, 1 = OR), reproducing the legacy category-page facets. Out of the box that is the usual facet shape (a OR b) AND (c OR d). CONSEQUENCE: the same request can return different rows after an admin edits that configuration, with nothing in the request changing — prefer filter[tagId] unless you are reproducing the legacy storefront. Note that tag_values_behavior only takes effect on an AND-behaviour category: on an OR-behaviour one the category always matches on ANY of its tags. Ids that resolve to no tag return an empty result, never the unfiltered list. Addressed by ID; the legacy slug pair (category-slug/tag-slug) is not supported.
Type
string
filter[vendorCode]
Filter by vendor code (exact match)
Type
string
filter[active]
Filter by active status. Backend callers only — for storefront (guest/customer) callers this value is server-forced to 1 and any supplied value is ignored.
Type
boolean
filter[softDelete]
Filter by soft delete status. Backend callers only — for storefront (guest/customer) callers this value is server-forced to 0 and any supplied value is ignored.
Type
boolean
filter[negativeStock]
Filter by negative stock
Type
boolean
filter[name.el]
Filter by product name (Greek)
Type
string
filter[name.en]
Filter by product name (English)
Type
string
filter[slug.el]
Filter by product slug (Greek)
Type
string
filter[slug.en]
Filter by product slug (English)
Type
string
sort
Sort order
Type
string
page
Page number
Type
integer
limit
Rows per page. Defaults to 15 when omitted, empty or 0. For STOREFRONT callers (guest and customer) the value is capped at the server ceiling — 1000 by default, configurable per shop via the rest_max_page_size config key. A larger value is CLAMPED down to the ceiling, never rejected: the request still returns 200 and pagination.per_page reports the clamped size, so page through the result rather than assuming the requested size was honoured. Backend callers are exempt and keep whatever page size they request.
Type
integer
with
With relations (e.g., translations,vendor,vat,shelfcode,badge,categories,productCodes,barcodes,tags,lines,variationValues)
Type
string
Responses
Successful operation
application/json
JSON
"string"