Search Products by Query
GET https://api.firmly.work/api/v1/domains-products/search
Returns products matching a keyword or phrase across the merchant catalogs your application is authorized for. Supports keyword matching, a set of catalog filters (price, in-stock, variant colors/sizes, single-domain scoping) and cursor-based pagination.
Authentication
This endpoint authenticates with a device access token in the x-firmly-authorization header.
x-firmly-authorization(string, required) — Device access token from Browser Session. See Authentication.
Query Parameters
-
query(string, required) — The search term or phrase to query (e.g.,shirt,blue dress,gift card 25). Required for the first page. It becomes optional only when you pass acursorto fetch a subsequent page — the cursor already encodes the original query. -
domain(string) — Restrict results to a single merchant domain (e.g.,staging.luma.gift). When omitted, the search spans every catalog your credentials are authorized for. -
colors(string) — Comma-separated list of variant colors to filter by (e.g.,Blue,Black). Matches products that have at least one variant in one of the listed colors. -
sizes(string) — Comma-separated list of variant sizes to filter by (e.g.,S,M,L). -
min_price(number) — Lower bound on the variant price range, as a decimal in the product’s currency (e.g.,20). -
max_price(number) — Upper bound on the variant price range, as a decimal in the product’s currency (e.g.,100). -
in_stock(boolean) — Whentrue(or1), returns only products with at least one available variant. Any other value is treated asfalse. -
page_size(number, default20) — Number of products to return per page. Clamped to the range1–100; values outside that range are adjusted to the nearest bound rather than rejected. -
include_realtime(boolean) — Whentrue(or1), each result is re-fetched live from the merchant for up-to-the-second price and availability, instead of being served from the search index. This is slower — use it only when freshness matters more than latency. -
cursor(string) — Opaque pagination cursor. Pass the value from the previous response’snext_pageURL to fetch the next page. On a cursor request,querymay be omitted.
Response
Returns a JSON object with a product_details array plus cursor-pagination metadata. (This is not a bare array — the matching products are nested under product_details.)
-
product_details(array) — Array of matching products. Each item carries the merchantdomainanddomain_name, the product detail page URL, an image, and the variant list with prices and availability.Product
domain(string) — Merchant domain the product belongs to (e.g.,staging.luma.gift)domain_name(string) — The merchant’s display name for that domain, ornullif it could not be resolved.pdp_url(string) — Full URL to the product detail page on the merchant’s sitetitle(string) — Product titleimage(object) — Primary product image —{ "url": "..." }mime_type(string) — MIME type of the primary image (e.g.,image/jpeg)variants(array) — Array of product variants matching the query.
Variant
sku(string) — Variant SKUprice(object) — Variant price —{ currency, value, number, symbol }(numberis the integer minor-unit amount, e.g.749for $7.49)available(boolean) — Stock availability for this variantvariant_option_values(array) — Array of selected option values for this variant (e.g.,["Blue", "M"])handle(string) — Stable variant identifieradd_to_cart_ref(object) — The value you pass to Add Line Item as the requiredadd_to_cart_refbody field. Opaque — echo it back unchanged; don’t construct it yourself
metadata(object) — Aggregated price metadata for the product.
Metadata
min_price(number) — Minimum variant price across the matched variants, as a decimal in the product’s currency (e.g.22= $22.00)min_price_variant_ids(array) — SKUs of the variants at the minimum pricemax_price(number) — Maximum variant price across the matched variants, as a decimal in the product’s currencymax_price_variant_ids(array) — SKUs of the variants at the maximum price
-
page_size(number) — The effective page size applied to this response (after clamping to1–100). -
time_taken_ms(number) — Time the underlying search took, in milliseconds. -
next_page(string) — Full URL — including thecursorandpage_sizequery parameters — to fetch the next page.nullwhen there are no more results. -
has_more(boolean) —truewhen another page is available (i.e.,next_pageis set),falseotherwise.
Request Example
curl --request GET \--url 'https://api.firmly.work/api/v1/domains-products/search?query=shirt' \--header 'x-firmly-authorization: YOUR_AUTH_TOKEN'
curl --request GET \--url 'https://api.firmly.work/api/v1/domains-products/search?query=shirt&domain=staging.luma.gift&colors=Blue&in_stock=true&page_size=20' \--header 'x-firmly-authorization: YOUR_AUTH_TOKEN'
curl --request GET \--url 'https://api.firmly.work/api/v1/domains-products/search?cursor=eyJ...&page_size=20' \--header 'x-firmly-authorization: YOUR_AUTH_TOKEN'
Response Example
{"product_details": [{"domain": "staging.luma.gift","domain_name": "Test Merchant (Staging)","pdp_url": "https://staging.luma.gift/products/radiant-tee","mime_type": "image/jpeg","image": {"url": "https://cdn.staging.luma.gift/products/radiant-tee-main.jpg"},"title": "Radiant Tee","variants": [{"sku": "RADIANT-TEE-BLUE-S","price": { "currency": "USD", "value": 22, "number": 2200, "symbol": "$" },"available": true,"variant_option_values": ["Blue", "S"]},{"sku": "RADIANT-TEE-BLUE-M","price": { "currency": "USD", "value": 22, "number": 2200, "symbol": "$" },"available": true,"variant_option_values": ["Blue", "M"]}],"metadata": {"min_price": 22,"min_price_variant_ids": ["RADIANT-TEE-BLUE-S", "RADIANT-TEE-BLUE-M"],"max_price": 22,"max_price_variant_ids": ["RADIANT-TEE-BLUE-S", "RADIANT-TEE-BLUE-M"]}}],"page_size": 20,"time_taken_ms": 14,"next_page": "https://api.firmly.work/api/v1/domains-products/search?query=shirt&cursor=eyJ...&page_size=20","has_more": true}
Usage Notes
Error Responses
Most errors return a JSON body with code, error, and description. The no-match 404 is the one exception — it returns a bare {"error": "..."} body (see below). Program against the status code and the error value; descriptions are human-readable and may change.
404 — No Matching Results Found
No products matched the query (or filters) in your authorized catalog. This is the normal “zero results” response — not a server error.
{ "error": "No Matching Results Found" }
400 — MissingAuthHeader
The x-firmly-authorization header is missing or empty.
{ "code": 400, "error": "MissingAuthHeader", "description": "x-firmly-authorization header is missing or invalid." }
400 — InvalidToken
The authorization token is not a valid JWT structure.
{ "code": 400, "error": "InvalidToken", "description": "Jwt token is invalid." }
401 — Unauthorized
The x-firmly-authorization header was rejected, or no authentication method accepted the request.
{ "code": 401, "error": "Unauthorized", "description": "Caller is not authorized to make this call" }
500 — Unexpected
An unexpected server-side error occurred — for example, calling the endpoint with neither query nor cursor. Supply a query (for the first page) or a cursor (for a subsequent page). Retry transient errors with backoff; if it persists, contact support.
{ "code": 500, "error": "Unexpected", "description": "An unexpected error occurred." }
503 — StoreUnavailable
The underlying search service was temporarily unavailable. Retry with backoff.
{ "code": 503, "error": "StoreUnavailable", "description": "Store temporarily unavailable. Please try again later." }