List and search products

This page covers the read catalog of the Products domain: the main listing with filters, free-text search, count, SKU existence check, resolving a parent product from a variant's SKU, the auxiliary brand/kind catalogs, and advanced search via POST.

No single envelope in Products

Unlike other domains in the API, Products does not respond with a consistent envelope shape across all its endpoints. Each endpoint on this page documents its actual response shape — don't assume {data, meta} in any of them except the explicit case flagged below.

Permission required on every endpoint on this page: products:read.

Main listing

Lists the authenticated tenant's products, with filters and pagination

qstring

Free-text search. Aliases: term, search.

statusstring

Filter by product status (active, disabled, draft).

bindingStatusstring

Filter by the status of a sales channel binding.

categorystring

Filter by category.

categoryPathstring

Filter by full category path.

brandsstring

Filter by one or more brands. Alias: brand.

minPricenumber

Minimum price.

maxPricenumber

Maximum price.

inStockboolean

Filter products with available stock.

minStocknumber

Minimum stock required.

locationIdstring

Filter by inventory location.

availableAtstring

Location ID. Activates the {data, meta} envelope — see the note below.

tagsstring

Filter by tag(s).

channelIdstring

Filter by a specific sales channel.

channelTypestring

Filter by channel type (shopify, amazon, mercadolibre, etc.).

channelIdsstring

Variant of channelId that accepts multiple IDs.

excludeChannelIdsstring

Excludes products bound to these channels.

createdAfterstring

Minimum creation date (ISO 8601).

createdBeforestring

Maximum creation date (ISO 8601).

updatedAfterstring

Minimum last-update date (ISO 8601).

updatedBeforestring

Maximum last-update date (ISO 8601).

typestring

Legacy filter over product type. See the productType note in the data model.

productTypestring

Filters by the actually persisted field: physical, digital, service, bundle, supply, material.

includeDisabledboolean

Includes products with status disabled in the result.

categoriesstring

Variant of category that accepts multiple values.

departmentsstring

Filter by department.

productKindsstring

Filter by one or more productKind.

activeboolean

Filter by active products.

hasImagesboolean

Filter products with (or without) images in media[].

hasVariantsboolean

Filter products with (or without) variants.

skusstring

Filter by a specific list of SKUs.

idsstring

Filter by a specific list of IDs.

includeInventoriesboolean

Attaches inventory data to each product in the result.

extendstring

Comma-separated list of extended fields to include. See 'The extend projection system' below.

pagenumber

Page number. Default: 0.

limitnumber

Results per page. Default: 20. Maximum: 500.

200
[
  {
    "id": "65f3a1b2c4d5e6f7a8b9c0e1",
    "sku": "CAM-ROJO-M",
    "status": "active",
    "productType": "physical",
    "title": { "value": "Camisa Roja Talla M", "translations": [] },
    "price": 499.00,
    "compareAtPrice": 599.00,
    "currency": "MXN",
    "taxable": true,
    "brand": "Fenicia Basics",
    "tags": ["playeras", "temporada-verano"],
    "media": [
      {
        "id": "m1",
        "src": "https://cdn.fenicia.io/img/cam-rojo-m.jpg",
        "type": "image",
        "position": 0,
        "alt": { "value": "Camisa roja talla M", "translations": [] }
      }
    ],
    "variants": [],
    "bindings": []
  }
]

The only endpoint in the domain with a {data, meta} envelope

Without the availableAt parameter, GET /products responds with a flat array of products (legacy behavior, shown above). When you send availableAt=<locationId>, the response shape changes to an envelope with pagination:

{
  "data": [
    { "id": "65f3a1b2c4d5e6f7a8b9c0e1", "sku": "CAM-ROJO-M", "...": "..." }
  ],
  "meta": {
    "pagination": {
      "page": 0,
      "limit": 20,
      "total": 143,
      "totalPages": 8,
      "hasMore": true
    }
  }
}

This is the only endpoint in the entire Products domain that uses this envelope. No other one — not the rest of the catalog, not collections, not categories, not the bulk routes — replicates it. Don't build your client assuming this contract is general.

Free-text search over the catalog

qstring

Search term. Alias: query.

limitnumber

Results per page. Default: 20.

pagenumber

Page number. Default: 0.

200Exact envelope not verified line by line — see the Callout below.
[
  {
    "id": "65f3a1b2c4d5e6f7a8b9c0e1",
    "sku": "CAM-ROJO-M",
    "title": { "value": "Camisa Roja Talla M", "translations": [] },
    "price": 499.00,
    "currency": "MXN"
  }
]

Response shape not verified field by field

This endpoint delegates directly to the library's ProductsService.search. The audit confirmed the input parameters, but did not verify line by line the exact output envelope (flat array vs. wrapping object). The products it returns have the real Product shape (see data model); confirm the exact container shape against a real response before depending on it.

Count

Counts the products matching the given filters

200
{ "count": 143 }

Accepts the same set of filters as the main listing (q, status, category, brands, minPrice, maxPrice, tags, channelId, etc.); it does not apply page/limit because it doesn't paginate results. When you send a search term (term), the response also includes the search strategy used:

{ "count": 27, "searchStrategy": "text" }

Tip

The exact value of searchStrategy was not exhaustively enumerated in the audit — treat it as informational, not as a closed enum for your business logic.

Products by location

Two endpoints return products filtered by inventory location, with different purposes and payload sizes.

Products with inventory at a specific location

locationIdstringrequired

Location ID. Required.

limitnumber

Results per page. Default: 20.

pagenumber

Page number. Default: 0.

termstring

Additional text filter.

200Result of getProductsByLocation — exact shape not verified line by line, see the Callout below.
[
  { "id": "65f3a1b2c4d5e6f7a8b9c0e1", "sku": "CAM-ROJO-M", "price": 499.00 }
]
400No code field, unlike the rest of the domain.
{ "error": "Missing required query parameter: locationId" }

400 error without a code, and response shape not verified

If you omit locationId, this endpoint responds 400 with {"error": "Missing required query parameter: locationId"}without a code field, unlike the rest of the domain's convention. The exact shape of the successful response (the result of getProductsByLocation) was also not verified line by line in the audit; confirm it against a real response.

Minimized view of products with inventory at a location (reduced payload, meant for large lists)

locationIdstringrequired

Location ID. Required.

pagenumber

Page number. Default: 0.

limitnumber

Results per page. Default: 20. Maximum: 100.

termstring

Additional text filter.

200
{
  "products": [
    { "sku": "CAM-ROJO-M", "title": "Camisa Roja Talla M", "price": 499.00 }
  ],
  "total": 128
}
400
{
  "code": "bad-request/missing-param",
  "message": "Missing required query parameter: locationId",
  "parameter": "locationId"
}

Exact composition of each product not enumerated

The confirmed response wraps in {products, total} — with a minimized payload (~500 bytes per product, vs ~3 KB for the full product). The audit did not enumerate the complete and definitive list of fields each item carries; the example above is illustrative with fields that do exist in the real model, not an exact verified copy of the code.

Common errors for /products/inventory-view: 400 bad-request/missing-param, 400 bad-request/invalid-param, 500 inventory-view/error.

Check whether a SKU exists

Checks whether a product with the given SKU exists

skustringrequired

SKU to check.

200
{ "exist": true }

Responds 400 if the sku path param is empty.

Parent product by variant SKU

Resolves the full parent product from the SKU of one of its variants

skustringrequired

Variant SKU (not the parent product's).

404
{
  "code": "not-found",
  "message": "No product found containing variant with SKU: CAM-ROJO-M-AZUL"
}

The successful response is the full parent Product — same shape as GET /products/{id}, not a reduced view.

Brands and kinds

Two auxiliary catalogs to populate filters in your UI, both return a simple array of strings:

Lists all distinct brands used in the tenant's catalog

200
["Fenicia Basics", "Nike", "Adidas"]

Lists all distinct productKind values used in the tenant's catalog

200
["playera", "pantalon", "calzado"]

Advanced search (POST)

For complex filters that don't fit comfortably in a query string, or to explicitly request the level of detail for each returned product.

Advanced search via JSON body, with explicit control over projection

filtersobject

Structured filters object. One of filters or query, never both.

querystring

Free-text search term, an alternative to filters. One of filters or query, never both.

pagenumber

Page number.

limitnumber

Results per page.

includeInventoriesboolean

Attaches inventory data to each product.

inStockboolean

Filters only products with stock.

projectionstring

'default' or 'full' — controls the set of fields each product carries.

{
  "filters": { "status": "active", "category": "playeras" },
  "page": 0,
  "limit": 50,
  "projection": "default"
}
200
{
  "products": [
    { "id": "65f3a1b2c4d5e6f7a8b9c0e1", "sku": "CAM-ROJO-M", "price": 499.00 }
  ],
  "total": 27,
  "page": 0,
  "limit": 50
}
400
{
  "code": "bad-request/ambiguous-param",
  "message": "Body must contain `query` OR `filters`, not both",
  "parameter": "query|filters"
}

Permission required: products:read

filters and query are mutually exclusive

Sending filters and query in the same request responds 400 with bad-request/ambiguous-param. Send exactly one of the two.

Errors: 400 bad-request/missing-param, 400 bad-request/ambiguous-param, 500 internal-error/missing-count.

The ?extend= projection system

Several Products endpoints (this listing and GET /products/{id}) accept extend as a comma-separated list of additional fields to include in the response, from a closed set:

variants, options, description, bindings, metadata, inventory, dimensions, seo, all

all includes all available extended fields. The details of what extend=inventory/extend=all specifically do (attach inventory per variant and location) are documented in Retrieve a product, where the parameter has its most complete effect.

Pagination

page starts at 0. The default limit and the maximum allowed vary by endpoint:

EndpointDefault limitMaximum limit
GET /products20500
GET /products/search20— (not confirmed)
GET /products/inventory20— (not confirmed)
GET /products/inventory-view20100

Errors

CodeStatusDescription
bad-request/missing-param400Missing a required parameter (e.g. locationId in /products/inventory-view, or filters/query in /products/query).
bad-request/ambiguous-param400/products/query received both filters and query at once.
bad-request/invalid-param400A parameter has an incorrectly formatted value.
not-found404No product resolves the given SKU.
internal-error/missing-count500Internal error computing the total result count.
inventory-view/error500Internal error in /products/inventory-view.

Authentication codes (invalid token, missing permission) are the same across the whole platform — see the full error catalog.

Tenant isolation

All endpoints on this page filter exclusively by the authenticated tenant. There is no way to list or search products from another tenant.

Next steps