Catalog

Search and read the digital-goods catalog — gift cards, eSIMs, and top-ups, priced in your sell currency.

The catalog is your workspace's sellable inventory of digital goods — gift cards, eSIMs, and phone top-ups — continuously kept up to date and priced in your sell currency. Reads require the products.read scope.

Search the catalog

GET /api/v1/products?query=&countryCode=&category=&offset=0&limit=50
ParameterTypeNotes
querystringFree-text match on product names.
countryCodestringTwo-letter ISO 3166-1 alpha-2 region (e.g. US, DE).
categorystringCategory filter — the product-kind aliases gift-card, esim, and top-up always work, and richer per-catalog categories (as returned in each product's category field) match exactly.
offset / limitintOffset pagination; limit 1–100 (default 50).

The response is a page:

{ "items": [ ... ], "offset": 0, "limit": 50, "hasMore": true }

hasMore tells you whether another page exists at offset + items.length. Results are ordered by featured status and the catalog's own sort order, so page boundaries are stable between calls.

An unknown category or a filter that matches nothing returns an empty page, not an error — the catalog changes over time, so treat "no items" as a normal outcome.

Read one product

GET /api/v1/products/{productId}

Returns the full product or a 404 problem (code: "not_found") when the id does not exist in your workspace's catalog.

The product shape

{
  "id": "0198d72d-99d6-75a6-9f12-971050ba7a5f",
  "type": "gift-card",
  "name": "Everyday Digital Gift Card",
  "countryCode": "US",
  "currency": "USD",
  "minimumValue": null,
  "maximumValue": null,
  "valueStep": null,
  "imageUrl": "/api/v1/catalog-product-images/img_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  "isAvailable": true,
  "variants": [
    { "id": "0198d72d-a15b-7cbf-ab69-b95410e98636", "displayValue": "$50", "price": 52.50, "currency": "USD" }
  ],
  "description": "A digital product.",
  "terms": "Terms apply.",
  "images": [
    {
      "id": "0198d9a6-40f8-7680-bbf8-054f5aff56ef",
      "publicReference": "img_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "url": "/api/v1/catalog-product-images/img_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "altText": "Product image",
      "width": 1200,
      "height": 800,
      "sortOrder": 0,
      "isPrimary": true
    }
  ],
  "sortOrder": 0,
  "isFeatured": false,
  "category": "retail"
}

Field semantics that matter for a correct integration:

  • type — the product kind: gift-card, esim, or phone-top-up. A phone top-up order needs a purchaseInput.phoneNumber at order time (see Orders).
  • Variants vs. value range. A product offers exactly one purchase style:
    • variants non-empty → quote with a variantId. Each variant's price is your final sell price.
    • minimumValue/maximumValue/valueStep set → quote with a value on that grid (e.g. min 5, max 100, step 5 permits 5, 10, 15, …). The final price comes back on the quote.
  • isAvailable — availability at display time. Availability is re-checked when you quote and again when you order, so treat this as advisory and handle a rejected quote gracefully.
  • currency — the sell currency for this product in your workspace. Quotes must use it.
  • Opaque identifiers. Product ids, variant ids, and image references have no derivable meaning. Never parse them, never construct URLs beyond what the API returns.

Product images

Image URLs in product bodies are relative, same-origin paths:

GET /api/v1/catalog-product-images/{publicReference}

Fetch them with the same X-Api-Key header (scope products.read) and serve them to your users from your own infrastructure (proxy or re-host). References are stable for the life of the image, so they are safe cache keys; the endpoint sets standard validation headers for revalidation.

Do not hotlink the API from end-user browsers — that would expose your key.

Keeping a local mirror fresh

If you mirror the catalog into your own database:

  • Re-walk GET /products (pages of 100) on a schedule that fits your business — hourly is plenty for most; the catalog itself refreshes a few times a day.
  • Upsert by product id; mark anything you did not see in a full walk as unavailable rather than deleting it, so in-flight carts fail soft.
  • Never cache prices beyond a quote's lifetime — the quote is the only binding price.

Did this page help you?