Search products
const url = 'https://api.example.com/products/search?size=20&from=0&includeArchived=true&sort=name&order=asc&displayOnWeb=true';const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request GET \ --url 'https://api.example.com/products/search?size=20&from=0&includeArchived=true&sort=name&order=asc&displayOnWeb=true' \ --header 'Authorization: Bearer <token>'Search products by query string (name, description, productCode) and
filter/sync. This is the recommended endpoint for catalog sync — see the
M2M Product Sync guide. Supports both offset (from/size) and deep
cursor (syncToken) pagination.
Authorizations
Section titled “Authorizations ”Parameters
Section titled “ Parameters ”Query Parameters
Section titled “Query Parameters ”Search query. Omit to match all. Maximum 256 characters — longer
values return 400 with error: "SEARCH_TERM_TOO_LONG".
Cursor for deep pagination (from a previous response).
Exact brand name filter.
ISO 8601 timestamp for incremental sync.
Sort column. Accepted values are the column IDs in the enum below —
note these are column IDs, not response field names (code sorts by
productCode, brand by brandName, name by name). Any other
value returns 400 with error: "INVALID_SORT_FIELD" and a message
listing the allowed values.
Sort direction. Defaults to desc. Only consulted when sort is
also present (ignored otherwise). With sort present, a value other
than asc/desc returns 400 with error: "INVALID_SORT".
Comma-separated projection of fields to return. For M2M callers this is narrowing-only — it can never widen the response beyond the external field allowlist; a field outside the allowlist is silently omitted rather than erroring.
Three-state web-visibility filter — "true" returns only
web-visible products, "false" returns hidden products, omit
for all. Visibility reflects the value configured for the
calling client ("false" also matches records where no value
is configured).
Responses
Section titled “ Responses ”Search results with pagination metadata.
object
Full product records (the search handler returns each hit’s
complete source plus imageUrl). Use the fields query param
to project a subset.
The external product representation — what GET /products/search
and GET /products/{productId} return to an M2M caller. This is the
FULL contract: every field this schema documents is everything the
endpoint can ever return to this caller class. status is internal
metadata and deliberately absent, as are the remaining cost/margin
fields other than supplierCost — see the “Field Policy” section
above.
object
Whether the product should be published/shown on the consuming integrator’s site (e.g. WooCommerce published vs. draft). For a client bound to a web channel, this is always an explicit true/false reflecting that channel’s visibility. For an unbound client, it reflects the product’s stored visibility flag, which may be omitted when no flag is stored — treat a missing value as not visible.
Public-facing short description shown to API consumers. Still labelled “Internal Notes” in the CRM UI pending the
Product colour/finish.
Trade selling price.
Recommended retail price (RRP).
Wholesale/cost-basis price. Is returned; treat as commercially sensitive and never expose it to end consumers.
object
Product documents (spec sheets, manuals). Same {url, name} shape as images.
object
Supplier/ERP identifier — also what q= search matches for identifier lookups. Used for an ERP catalogue-sync integration.
Alternate supplier identifier.
Second alternate supplier identifier.
Website navigation/category tree path.
Top-level product classification.
Second-level product classification.
Third-level product classification.
Product range/collection name shown on the integrator’s site. Distinct from range, which is frequently empty for the same products.
A PROJECTED view of related-items relationships, never
the raw persisted shape. productCode/erpCode are inline display
fields present ONLY on GET /products/{productId} (enrichment runs
before projection there); GET /products/search entries carry only
productId/relationType/qty/source. note/name/imageUrl/archived
are never present; spares is never exposed at all.
object
Inline display field — GET /products/{productId} only.
Inline display field — GET /products/{productId} only.
Present only on server-derived reverse (used by) entries.
object
When true, total is a floor (result count capped at 10,000).
Cursor for the next page (also in pagination); null on the last page.
object
Example
{ "data": [ { "id": "ac1198a7-33f9-460c-b648-5cbbf9841869", "productCode": "PRD-XQEPWD", "brandName": "Wanabiwood Flooring", "category": "Beauty", "description": "Advanced technology increases capabilities", "price": 3227.15, "createdAt": "2025-11-21T15:56:18.847Z" } ], "pagination": { "total": 45, "totalIsLowerBound": false, "from": 0, "size": 5, "hasMore": true, "syncToken": "WzE3NTYyOTA5NjAzMDks..." }, "syncToken": "WzE3NTYyOTA5NjAzMDks...", "meta": { "queryTimeMs": 23, "source": "opensearch" }}Invalid query parameters or malformed pagination cursor.
object
Machine-readable code. Best-effort — the generic 500 path carries none.
Unique request ID for tracing.
object
Example
{ "error": "INVALID_SYNC_TOKEN", "message": "Invalid or malformed syncToken provided", "requestId": "req-123abc"}Authentication required or failed.
object
Machine-readable code. Best-effort — the generic 500 path carries none.
Unique request ID for tracing.
object
Example
{ "error": "UNAUTHORIZED", "message": "Valid authentication token required."}Insufficient permissions (missing scope or Admin role).
object
Machine-readable code. Best-effort — the generic 500 path carries none.
Unique request ID for tracing.
object
Example
{ "error": "FORBIDDEN", "message": "Insufficient scope for this operation."}Search results incomplete (OpenSearch inconclusive). Do not treat as end-of-results; do not advance cursor.
object
Machine-readable code. Best-effort — the generic 500 path carries none.
Unique request ID for tracing.
object
Examples
The search could not be proven complete (timeout, shard failure, or malformed response)
{ "error": "SEARCH_INCOMPLETE", "message": "Search result set is incomplete. Retry after a delay.", "requestId": "req-456def"}Identifier lookup had internal failures
{ "error": "IDENTIFIER_LOOKUP_INCOMPLETE", "message": "Identifier lookup did not complete. Retry your request.", "requestId": "req-789ghi"}