Docs
Firmly Agentic Commerce
Set theme to dark (⇧+D)

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.

​​ 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 a cursor to 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) — When true (or 1), returns only products with at least one available variant. Any other value is treated as false.

  • page_size (number, default 20) — Number of products to return per page. Clamped to the range 1–100; values outside that range are adjusted to the nearest bound rather than rejected.

  • include_realtime (boolean) — When true (or 1), 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’s next_page URL to fetch the next page. On a cursor request, query may 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 merchant domain and domain_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, or null if it could not be resolved.
    • pdp_url (string) — Full URL to the product detail page on the merchant’s site
    • title (string) — Product title
    • image (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 SKU
    • price (object) — Variant price — { currency, value, number, symbol } (number is the integer minor-unit amount, e.g. 749 for $7.49)
    • available (boolean) — Stock availability for this variant
    • variant_option_values (array) — Array of selected option values for this variant (e.g., ["Blue", "M"])
    • handle (string) — Stable variant identifier
    • add_to_cart_ref (object) — The value you pass to Add Line Item as the required add_to_cart_ref body 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 price
    • max_price (number) — Maximum variant price across the matched variants, as a decimal in the product’s currency
    • max_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 to 1–100).

  • time_taken_ms (number) — Time the underlying search took, in milliseconds.

  • next_page (string) — Full URL — including the cursor and page_size query parameters — to fetch the next page. null when there are no more results.

  • has_more (boolean) — true when another page is available (i.e., next_page is set), false otherwise.

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