Get a Product
GET https://api.firmly.work/api/v1/domains-products/{merchant-domain}/{product-handle}
GET https://api.firmly.work/api/v1/domains-products/{merchant-domain}/{product-handle}
Gets comprehensive product information including variants, pricing, availability, and images. Use this endpoint to display product detail pages or product cards in your application.
Authentication
x-firmly-authorization(string, required) — Device access token from Browser Session
Path Parameters
-
merchant-domain(string, required) — The domain of the merchant website (e.g.,staging.luma.gift) -
product-handle(string, required) — The product handle or identifier. This is typically the URL-friendly version of the product name.
Query Parameters
postal_code(string) — Postal code to customize pricing, inventory, or availability based on location
Response
-
base_sku(string, required) — The base (parent) SKU identifier for the product. -
title(string, required) — The product title. -
handle(string, required) — The product handle / identifier (URL-friendly). -
description(string) — Product description. Often full HTML. -
short_description(string) — Short description. May equaldescriptionwhen the merchant doesn’t provide a separate short form. -
tags(string[]) — Merchant-supplied tags / labels for the product. -
product_type(string) — Merchant’s category or type for the product. -
pdp_url(string) — Full URL to the product detail page on the merchant’s site. -
images(object[]) — Product-level images.Image
url(string, required) — Image URL.alt(string) — Alternative text.type(string) — Image size variant. One ofpreview,small,medium,large,default,video. Defaults todefault.aspect_ratio(number) — Width-to-height ratio, when known.width(integer) — Pixel width, when known.height(integer) — Pixel height, when known.position(integer) — Ordering position among the product’s images.srcset(object[]) — Alternate renditions of the same image (each with its ownurl,alt,type,aspect_ratio,width,height).
-
variant_option_values(object[], required) — The option axes a buyer chooses between (e.g. Size, Color), each with its selectable values.Variant option axis
display_name(string, required) — Display name of the axis (e.g. “Size”, “Color”).property_accessor(string, required) — Key used to reference this axis programmatically.position(integer, required) — Display order of this axis.option_values(object[], required) — Selectable values for this axis.
Option value
display_name(string, required) — Value label (e.g. “Blue”, “Medium”).value(string, required) — Internal value identifier.available(boolean) — Whether this value is currently selectable.swatch(object) — Swatch presentation for the value.
Swatch
url(string) — Swatch image URL.rgb(string) — Swatch color as an RGB / hex string.
images(object[]) — Images specific to this value (same Image shape as above).
-
variants(object[], required) — The concrete purchasable variants.Variant
handle(string, required) — Variant handle / identifier.display_name(string, required) — Variant display name (e.g. “XS / Orange”).title(string, required) — Variant title.sku(string, required) — Variant SKU.add_to_cart_ref(object, required) — Opaque reference for adding this variant to the cart. Pass the entire object asadd_to_cart_refin the Add Line Item body — do not reconstruct it. It commonly containsvariant_id, and may also carryproduct_id,variant_handles, orpropertiesdepending on the merchant.variant_option_list(string[], required) — The option values that define this variant, in the axis order ofvariant_option_values. Each entry is avaluefromvariant_option_values[].option_values[], matched exactly — so["M", "Orange"]in the example below. These values are merchant-defined and are frequently opaque identifiers rather than readable labels (a live merchant may return["5", "7"]for the same variant), so never parse or display them. Usedisplay_namefor buyer-facing text, and look the entry up inoption_valuesto find its label.description(string) — Variant-specific description.available(boolean) — Whether this variant is currently purchasable.price(object) — Current price.
Price
currency(string, required) — Currency code (e.g. “USD”).value(number, required) — Price as a decimal.number(integer, required) — Price in the smallest currency unit (e.g. cents).symbol(string) — Currency symbol.
msrp(object) — Manufacturer’s suggested retail price. Same shape asprice.pdp_url(string) — Direct URL to this variant’s product page.variant_attributes(object[]) — Merchant-defined key/value attributes for the variant. Each entry is{ "key": string, "value": string }.highlights(string[]) — Short marketing highlights / bullet points.messages(object[]) — Variant-level messages (promotions, stock warnings, badges, etc.).
Message
type(string) — One ofpromotion,stock,badge,banner,shipping,urgency,info.text(string, required) — Display text.
images(object[]) — Images specific to this variant (same Image shape as above).requires_shipping(boolean) — Whether the variant ships physically.taxable(boolean) — Whether the variant is taxable.weight(number) — Variant weight.weight_unit(string) — Unit forweight.modified_at(string) — ISO 8601 timestamp of the variant’s last update.max_purchase_quantity(integer) — Per-SKU purchase cap the merchant enforces but may not show on its public PDP. When set, respect it before calling Add Line Item — exceeding it can cause a merchant-conditional add-line-item rejection. Absent / null means no override.show_quantity_selector(boolean) — Hint for whether the UI should expose a quantity selector for this variant.option1 … option10(string) — Positional option-value accessors — flat fields (option1,option2, … up tooption10) holding each axis value, mirroringvariant_option_list. Only as many as the product has axes are populated.
-
properties(object) — Structured product properties.Properties
vendor(string) — Vendor / brand name.product_family(string) — Product family / grouping.
-
product_attributes(object) — Free-form merchant-specific attributes (e.g. dimensions, material). Schema varies by merchant — treat as an opaque key/value map. -
reviews(object) — Aggregated review data.Reviews
total_reviews(integer, required) — Total number of reviews.average_rating(number, required) — Average rating (typically 0–5).review_list_url(string) — URL to the merchant’s full review listing.review_list(object[]) — Individual reviews. Each entry may includereviewer_name,review_date(ISO 8601),rating,review_title,review_text,verified_purchase,helpful_votes.rating_list(object[]) — Per-star distribution. Each entry is{ "rating": integer, "count": integer, "percentage": number }.
-
ui_hints(object) — Rendering hints for the storefront UI.UI Hints
no_variants(boolean) — The product has no meaningful variants — render as a single item.no_options(boolean) — The product has no selectable options — hide option pickers.
-
domain(string) — Merchant domain this product belongs to. -
domain_name(string) — Merchant display name.
Request Example
curl --request GET \--url 'https://api.firmly.work/api/v1/domains-products/staging.luma.gift/radiant-tee?postal_code=10001' \--header 'x-firmly-authorization: YOUR_AUTH_TOKEN'
Response Example
{"base_sku": "WS12","title": "Radiant Tee","handle": "radiant-tee","description": "A comfortable and stylish tee perfect for any occasion","product_type": "Tees","images": [{"url": "https://cdn.staging.luma.gift/products/radiant-tee-main.jpg","type": "default"}],"variant_option_values": [{"display_name": "Size","property_accessor": "size","position": 0,"option_values": [{"display_name": "Small","value": "S","available": true},{"display_name": "Medium","value": "M","available": true},{"display_name": "Large","value": "L","available": false}]},{"display_name": "Color","property_accessor": "color","position": 1,"option_values": [{"display_name": "White","value": "WHITE","available": true,"swatch": {"url": "https://cdn.staging.luma.gift/swatches/white.jpg"}},{"display_name": "Orange","value": "Orange","available": true,"swatch": {"url": "https://cdn.staging.luma.gift/swatches/orange.jpg"}}]}],"variants": [{"handle": "radiant-tee-m-orange","sku": "WS12-M-Orange","title": "Radiant Tee - M Orange","display_name": "M / Orange","add_to_cart_ref": {"variant_id": "WS12-M-Orange"},"price": {"currency": "USD","value": 22.00,"number": 2200,"symbol": "$"},"msrp": {"currency": "USD","value": 22.00,"number": 2200,"symbol": "$"},"available": true,"variant_option_list": ["M", "Orange"],"images": [{"url": "https://cdn.staging.luma.gift/products/radiant-tee-orange.jpg","type": "default"}],"messages": [{"type": "promotion","text": "20% off - Limited Time Offer"},{"type": "stock","text": "Only 3 left in stock"}]},{"handle": "radiant-tee-s-white","sku": "WS12-S-White","title": "Radiant Tee - S White","display_name": "S / White","add_to_cart_ref": {"variant_id": "WS12-S-White"},"price": {"currency": "USD","value": 22.00,"number": 2200,"symbol": "$"},"msrp": {"currency": "USD","value": 22.00,"number": 2200,"symbol": "$"},"available": true,"variant_option_list": ["S", "WHITE"],"images": [{"url": "https://cdn.staging.luma.gift/products/radiant-tee-white.jpg","type": "default"}]}],"reviews": {"total_reviews": 127,"average_rating": 4.5}}
Error Responses
Errors return a JSON body with code, error, and description. Program against the error value — descriptions are human-readable and may change.
400 — MissingAuthHeader
The x-firmly-authorization header is missing or empty (when authenticating as a device).
{ "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 — InvalidJWTToken
The device JWT signature does not verify, or required claims are missing.
{ "code": 401, "error": "InvalidJWTToken", "description": "Jwt token is invalid." }
404 — PartnerNotFound
The appid claim on the device JWT does not map to a known partner / tenant.
{ "code": 404, "error": "PartnerNotFound", "description": "Partner not found." }
404 — DomainNotFound
The {merchant-domain} does not match any merchant configured with Firmly, or the merchant has been disabled.
{ "code": 404, "error": "DomainNotFound", "description": "This domain was not found in firmly servers." }
404 — ProductNotFound
No product matches the supplied {product-handle} for this merchant.
{ "code": 404, "error": "ProductNotFound", "description": "Product not found." }
412 — OperationNotSupported
The merchant’s adapter does not support this operation.
{ "code": 412, "error": "OperationNotSupported", "description": "This operation is not supported for this store." }
501 — NotImplemented
The merchant’s adapter does not implement product-by-handle lookup. Use Get Product from URL where the merchant supports it.
{ "code": 501, "error": "NotImplemented", "description": "Operation not implemented." }