Skip to content

Search products

GET
/products/search
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.

q
string
<= 256 characters

Search query. Omit to match all. Maximum 256 characters — longer values return 400 with error: "SEARCH_TERM_TOO_LONG".

size
integer
default: 20 <= 100
from
integer
0
syncToken
string

Cursor for deep pagination (from a previous response).

brandName
string

Exact brand name filter.

updatedAfter
string format: date-time

ISO 8601 timestamp for incremental sync.

includeArchived
string
default: false
Allowed values: true false
sort
string
Allowed values: name code brand price category department subDepartment supplierCost createdAt updatedAt

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.

order
string
default: desc
Allowed values: asc desc

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".

fields
string

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.

displayOnWeb
string
Allowed values: true false

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).

Search results with pagination metadata.

Media type application/json
object
data

Full product records (the search handler returns each hit’s complete source plus imageUrl). Use the fields query param to project a subset.

Array<object>

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
id
string
productCode
string
name
string
description
string
brandName
string
brandSlug
string
range
string
category
string
department
string
subDepartment
string
stockType
string
catalogue
string
displayOnWeb

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.

boolean
notes

Public-facing short description shown to API consumers. Still labelled “Internal Notes” in the CRM UI pending the

string
colour

Product colour/finish.

string
price

Trade selling price.

number
nullable
retailPrice

Recommended retail price (RRP).

number
nullable
supplierCost

Wholesale/cost-basis price. Is returned; treat as commercially sensitive and never expose it to end consumers.

number
nullable
barcode
string
length
number
height
number
width
number
weight
number
weightUnit
string
imageUrl
string
nullable
images
Array<object>
object
url
string format: uri
name
string
documents

Product documents (spec sheets, manuals). Same {url, name} shape as images.

Array<object>
object
url
string format: uri
name
string
createdAt
string format: date-time
erpCode

Supplier/ERP identifier — also what q= search matches for identifier lookups. Used for an ERP catalogue-sync integration.

string
altSupplierCode

Alternate supplier identifier.

string
altSupplierCode2

Second alternate supplier identifier.

string
webHierarchy

Website navigation/category tree path.

string
type

Top-level product classification.

string
subType

Second-level product classification.

string
subSubType

Third-level product classification.

string
retailRange

Product range/collection name shown on the integrator’s site. Distinct from range, which is frequently empty for the same products.

string
relatedProducts

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.

Array<object>
object
productId
required
string format: uuid
productCode

Inline display field — GET /products/{productId} only.

string
erpCode

Inline display field — GET /products/{productId} only.

string
relationType
string
Allowed values: required optional substitute
qty
number
source

Present only on server-derived reverse (used by) entries.

string
Allowed values: reverse
pagination
object
total
integer
totalIsLowerBound

When true, total is a floor (result count capped at 10,000).

boolean
from
integer
size
integer
hasMore
boolean
syncToken
string
nullable
syncToken

Cursor for the next page (also in pagination); null on the last page.

string
nullable
meta
object
queryTimeMs
integer
source
string
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.

Media type application/json
object
error

Machine-readable code. Best-effort — the generic 500 path carries none.

string
message
required
string
requestId

Unique request ID for tracing.

string
nullable
details
object
key
additional properties
any
Example
{
"error": "INVALID_SYNC_TOKEN",
"message": "Invalid or malformed syncToken provided",
"requestId": "req-123abc"
}

Authentication required or failed.

Media type application/json
object
error

Machine-readable code. Best-effort — the generic 500 path carries none.

string
message
required
string
requestId

Unique request ID for tracing.

string
nullable
details
object
key
additional properties
any
Example
{
"error": "UNAUTHORIZED",
"message": "Valid authentication token required."
}

Insufficient permissions (missing scope or Admin role).

Media type application/json
object
error

Machine-readable code. Best-effort — the generic 500 path carries none.

string
message
required
string
requestId

Unique request ID for tracing.

string
nullable
details
object
key
additional properties
any
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.

Media type application/json
object
error

Machine-readable code. Best-effort — the generic 500 path carries none.

string
message
required
string
requestId

Unique request ID for tracing.

string
nullable
details
object
key
additional properties
any
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"
}