Catalog
Catalog and campaign endpoints need the CATALOG scope. GET /brands accepts any key. Image URLs need no key.
Brands
Section titled “Brands”GET /brands returns the brands the key may use, as a plain array: [{id, name, slug, primaryColour, logoUrl}]. Send a brand id as brandId on an order or a personal-link batch.
Categories
Section titled “Categories”GET /catalog/categories returns categories and their subcategories, as a plain array.
[ { "code": "FOOD_DRINK", "name": "Food & Drink", "subcategories": [{ "code": "FOOD_DRINK__WINE_CHAMPAGNE", "name": "Wine & Champagne" }] }]A gift’s categories are subcategory codes. Both levels work as a filter.
Gifts for a country
Section titled “Gifts for a country”GET /catalog/products?country=DE lists gifts for the delivery country, sorted by name.
| Parameter | Meaning |
|---|---|
country |
Required. Delivery country, ISO 3166-1 alpha-2. An unknown code answers 400 invalid_request. |
locale |
BCP 47 locale of the gift text. Gifts without that translation use their default text. An unsupported locale answers 400 invalid_request with the supported list. |
q |
Text search. |
category |
Category or subcategory code. Repeat to match any of several. An unknown code answers 400 invalid_request. |
readyToSend |
true returns only gifts that ship as they are. |
updatedSince |
Switches to catalog sync. |
after, limit |
Paging. limit is 1–100, default 25. |
GET /catalog/products/{productId}?country=DE returns one gift. A listed gift that cannot be supplied to the country returns 200 with available: false. A draft, archived, internal or unknown gift answers 404 not_found.
A gift
Section titled “A gift”{ "id": "9b8a7c6d-5e4f-4a3b-2c1d-0e9f8a7b6c5d", "name": "Cashmere scarf", "description": "Hand-finished in Scotland.", "categories": ["FASHION__ACCESSORIES"], "giftSet": false, "readyToSend": true, "customizationAvailable": true, "variations": [{ "dimension": "Colour", "values": ["Black", "Tan"] }], "images": [{ "url": "https://…", "width": 1000 }], "listed": true, "available": true, "priced": true, "estimate": { "amount": "190.00", "currency": "USD" }, "estimateEur": { "amount": "180.00", "currency": "EUR" }, "updatedAt": "2026-10-05T09:12:44Z"}| Field | Meaning |
|---|---|
available |
The gift can be supplied to the country. Ordering an unavailable gift answers not_deliverable_to_country. |
priced |
The gift has a price for the country’s region. |
estimate |
Estimate in the tenant’s currency at today’s rate. null when unpriced. |
estimateEur |
The same estimate in EUR. null when unpriced. |
readyToSend |
The gift ships as it is. |
customizationAvailable |
The gift can be personalised. See customization on orders. |
giftSet |
The gift contains several items. |
variations |
Dimensions to choose, with their values. An order names one value per dimension. |
images |
Absolute URLs that work without a key. |
Estimates
Section titled “Estimates”An estimate is not a quote. No price is locked when you read it. You decide what to show: the estimate, the EUR estimate or no price.
Prices and availability change. After a sync, update your copy. Orders already placed are not affected. If a gift is no longer available, an order answers 422 not_deliverable_to_country. If campaign stock ran out, it answers 409 out_of_stock. In both cases no order is placed.
Catalog sync
Section titled “Catalog sync”Keep a local copy instead of reading the catalog on every page view.
- First load: page through
GET /catalog/products?country=DEand store every gift. - Every few minutes: call
GET /catalog/products?country=DE&updatedSince=<instant>.
With updatedSince, the response contains every gift whose listing, price or availability changed at or after that instant, oldest change first (by updatedAt, then id). Follow nextCursor until it is null.
countryandlocalestill apply. They setavailable,priced, the estimates and the text. Unknown values answer400 invalid_request. A gift that cannot be supplied returnsavailable: false.q,categoryandreadyToSendare ignored. Filter your copy yourself. An unknowncategorystill answers400 invalid_request.- Removed gift (unpublished, archived or made internal):
{"id": "…", "listed": false, "updatedAt": "…"}. Delete it from your copy. - Any other gift is returned in full. Replace your copy. Duplicates are harmless.
- Next sync: pass the newest
updatedAtyou hold, minus five minutes. KeepupdatedAtat full precision. SendZ, or encode+as%2B.
Campaigns
Section titled “Campaigns”A campaign is a set of gifts under one brand, such as a VIP birthday selection. Campaigns are read-only.
GET /campaigns?brandId=…&status=ACTIVElists active and completed campaigns within the key’s brands, sorted by name.GET /campaigns/{campaignId}returns one campaign with its offerings. Optionallocalesets the language of each offeringname. An unsupported locale answers400 invalid_request.
{ "id": "1e2d3c4b-5a69-4c1f-9a52-8d0e4f4b9a3e", "name": "VIP birthdays", "brandId": "0a6f2e1c-3b5d-4e7f-8a9b-0c1d2e3f4a5b", "status": "ACTIVE", "expiresAt": null, "offerings": [ { "productId": "9b8a7c6d-5e4f-4a3b-2c1d-0e9f8a7b6c5d", "name": "Cashmere scarf", "images": [{ "url": "https://…", "width": 1000 }], "supplyType": "STOCKED", "status": "ACTIVE", "skus": [ { "variations": { "Colour": "Black" }, "stockRemaining": 12 }, { "variations": { "Colour": "Tan" }, "stockRemaining": 0 } ] } ]}nameandimagesmatch the catalog gift, without a price. They are present for every offering, including gifts made for your brand only, which the catalog does not list.supplyTypeisSTOCKED(limited stock per variation) orON_DEMAND(unlimited;stockRemainingisnull).- An offering in
NEEDS_REVIEWcannot be ordered until Luxorr reviews it. - A campaign that is not activated, or belongs to another brand or tenant, answers
404 not_found.
To order from a campaign, send campaignId; the order uses the campaign’s brand. To order from the catalog, send brandId.
Images
Section titled “Images”Gift images and brand logos are absolute URLs that need no key. They point to the Luxorr CDN or to GET /catalog/images/{fileId}, which:
- serves images of published public gifts, of published gifts in active or completed campaigns, and brand logos;
- accepts
scale=x1|x2|x3for 500, 1000 or 1500 px; - can be cached for a year; a replaced image gets a new ID;
- allows 600 requests per minute per client IP.