Skip to content

Products ​

Read the product catalog: names, descriptions, translations, prices, images, configurable options and sellable stock. All endpoints need the products:read scope.

GET /products ​

Lists products, paginated.

QueryDescription
updated_sinceISO 8601 timestamp with time zone. Only products changed after it; results are then sorted by updated_at (oldest first) — use it for incremental sync.
include_inactivetrue = also archived products and products not marked as sold (they come with active: false). Default: only active products that are sold.
typestock (physical items) or service.
page, limitPage from 1; limit default 100, max 200.
langDocument language: en, et, fi, sv, nb, da, lv, lt, de, pl, fr, es, nl. name, description and option names are returned in that language when translated, else in the company's own text. All translations are always in translations.
bash
curl "https://zerpy.app/api/v1/products?limit=100&lang=en" \
  -H "Authorization: Bearer $ZERPY_API_KEY"
json
{
  "data": [
    {
      "id": "37c8a1e2-…",
      "sku": "SAUNA-1",
      "name": "Barrel sauna",
      "description": "Thermowood barrel sauna, 4 persons",
      "translations": {
        "en": { "name": "Barrel sauna", "description": "Thermowood barrel sauna, 4 persons" },
        "fi": { "name": "Tynnyrisauna", "description": null }
      },
      "type": "stock",
      "unit": "pcs",
      "barcode": null,
      "weight_kg": "420",
      "category": "Saunas",
      "images": [
        {
          "id": "b51f…",
          "url": "https://…/product-images/…/a1b2.webp",
          "thumb_url": "https://…/product-images/…/a1b2-thumb.webp",
          "width": 1600,
          "height": 1200
        }
      ],
      "price": { "net": "8650.00", "vat_rate": "24", "gross": "10726.00", "currency": "EUR" },
      "options": [
        {
          "id": "16a6…",
          "name": "Heater",
          "translations": { "fi": { "name": "Kiuas" } },
          "required": true,
          "values": [
            {
              "id": "c105…",
              "name": "Harvia Legend",
              "translations": {},
              "price_delta": { "net": "0.00", "vat_rate": "24", "gross": "0.00", "currency": "EUR" }
            },
            {
              "id": "7d2e…",
              "name": "HUUM Hive",
              "translations": {},
              "price_delta": { "net": "650.00", "vat_rate": "24", "gross": "806.00", "currency": "EUR" }
            }
          ]
        }
      ],
      "stock": { "available": "2", "on_hand": "3" },
      "active": true,
      "updated_at": "2026-11-07T10:00:00.000Z"
    }
  ],
  "page": 1, "limit": 100, "total": 1, "has_more": false
}

GET /products/{sku} ​

One product by SKU, in the same shape, wrapped in { "data": { … } }. Accepts ?lang= and ?include_inactive=true like the list. Unknown SKU → 404 not_found.

bash
curl "https://zerpy.app/api/v1/products/SAUNA-1?lang=fi" \
  -H "Authorization: Bearer $ZERPY_API_KEY"

URL-encode SKUs that contain special characters. Same items as the list: an archived or not-sold product returns 404 unless you add ?include_inactive=true (then it comes with active: false; partner keys never see not-sold products).

Product object ​

FieldTypeDescription
idstring (UUID)ZERPY id
skustringProduct code — the key you use in orders
namestringIn ?lang= when translated, else the company's text
descriptionstring | nullSame language rule as name
translationsobjectAll translations: { "en": { "name", "description" } }. Empty object = not translated; a null field = that text falls back to the main text
typestock | serviceServices have no stock
unitstringe.g. pcs, m, kg, h
barcodestring | null
weight_kgstring | nullDecimal string
categorystring | nullCategory name
imagesarraySee Images
priceprice objectStandard sale price: { net, vat_rate, gross, currency } in the company currency. With a partner key: the partner's own price, null = price on request
optionsarrayOption groups of a configurable product, empty for normal products. See Options
stockobject | null{ available, on_hand } — see Stock. null for services
activebooleanfalse = archived or not sold
updated_atstringLast change of the product data

Partner keys get extra fields (retail_price, quantity_breaks, stock.consignment) — see Partner product feed.

What changes updated_at ​

  • Changes to the product itself (name, price, description, translations, category, …) and adding, removing or reordering images change updated_at and send the product.updated webhook.
  • Renaming or deleting a category touches its products' updated_at as well.
  • Stock changes do not change updated_at — use GET /stock or the stock.changed webhook.
  • Option changes (groups, values, prices, option translations) do not change updated_at. Re-read options in a periodic full sync.

Images ​

json
"images": [
  { "id": "b51f…", "url": "https://…/a1b2.webp", "thumb_url": "https://…/a1b2-thumb.webp", "width": 1600, "height": 1200 }
]
  • The first image is the main image.
  • WebP files: url is at most 1600 px, thumb_url at most 400 px. width / height are the size of the large image.
  • URLs are public and stable — safe to hotlink or to copy into your shop.

Translations and ?lang= ​

Product names and descriptions are written in the company's own language; other languages are added as translations in ZERPY.

  • Without ?lang= (company key): name / description are the company's own texts.
  • With ?lang=fi: the Finnish translation when it exists, otherwise the company's text.
  • translations always contains every language, so a multi-language shop can read everything in one call — or call once per language with ?lang=.
  • Option groups and values have translations too: { "fi": { "name": "Kiuas" } }.

Configurable products (options) ​

A configurable product (for example a sauna with a choice of heater and facade) lists its option groups in options:

FieldDescription
options[].idOption group id
options[].nameGroup name (language rule as above)
options[].requiredtrue = a value must be chosen when ordering
options[].values[].idOption value id — send it in POST /orders
options[].values[].nameValue name
options[].values[].price_deltaPrice object added to the product price when this value is chosen (with a partner key: in the partner's prices)

Choose one value per group. Your shop shows the options and computes the final price itself; the order line's unit_price must already include the option prices. See Orders → options.

GET /feed.csv ​

The whole catalog as one CSV file — for shop import plugins, Google Sheets (=IMPORTDATA(url)) and Excel.

QueryDescription
keyThe API key (alternative to the Authorization header — this is the only endpoint that accepts it in the URL)
include_inactivetrue = also archived products
langNames and descriptions in this language when translated
bash
curl -o products.csv "https://zerpy.app/api/v1/feed.csv?key=$ZERPY_API_KEY&lang=en"
  • Products that are sold; ordered by SKU; at most 10 000 rows.
  • UTF-8 with BOM, comma-separated, header row, CRLF line ends.
  • Text that a spreadsheet would treat as a formula (starting with =, +, -, @) is prefixed with '.
  • Columns, in this order:

sku, name, description, category, type, unit, barcode, weight_kg, price_net, vat_rate, price_gross, retail_price_net, retail_price_gross, currency, available, consignment, image_url, image_urls, active, updated_at, retail_currency

ColumnNotes
price_net, price_gross, vat_rate, currencyThe price (with a partner key: the partner's price). Empty price = price on request
retail_price_net, retail_price_gross, retail_currencyPartner keys only: your standard price
availableSellable stock; empty for services
consignmentPartner keys only: your stock at the partner
image_urlMain image; image_urls = all images separated by |
activetrue / false

WARNING

A URL with ?key= works like a password. It ends up in the logs of whoever fetches it — do not publish it. If it leaks, revoke the key and create a new one.