Overview / Requests & responses

Requests & responses

The same envelope, the same filters and the same errors on every endpoint. What you learn on one resource carries to all of them.

Response envelope

A single record:

{
    "data": { "id": "01hz…", "name": "Latte" }
}

A list, with pagination in meta:

{
    "data": [ … ],
    "meta": { "total": 42, "page": 1, "per_page": 25, "last_page": 2 }
}

An error — always these three keys, never anything else:

{
    "error": {
        "code": "insufficient_scope",
        "message": "This endpoint requires the scope \"products.write\", …",
        "details": { "missing_scopes": ["products.write"] }
    }
}

code is for machines and never changes. message is for people and may improve. Branch on the code.

Pagination

ParameterDescription
pagePage number, starting at 1.
per_pagePage size, default 25, maximum 100.

A per_page above the maximum is rejected, not silently clamped: quietly returning fewer rows than asked for makes an integration think it has synced everything when it has not.

Incremental sync

GET /products?updated_since=2026-01-01T00:00:00Z

Every list accepts updated_since. Store the timestamp of your last successful sync and pass it back to fetch only what changed since.

Filtering and sorting

ParameterDescription
activetrue or false. Omit it to get both.
sortColumn to sort by. Each endpoint lists what it accepts; an unknown column is rejected rather than ignored.
directionasc or desc.

Partial updates

PATCH changes only the fields you send; everything else is left alone. You never have to read a record, modify one field and write the whole thing back.

Lists of related ids (branch_ids, product_ids, addon_group_ids) are the exception: when present they replace the whole set. Omit them to leave the links untouched.

Language

GET /products?lang=en

Translatable fields (product name and description, category and addon names) come back in the requested language, falling back to the brand's default where a translation is missing. Only languages configured in the panel are accepted — an unknown code is an error, not a silent fallback.

Write endpoints take a translations object keyed by language code.

Money and dates

Amounts are JSON numbers in the brand's currency, rounded to two decimals. All timestamps are ISO-8601 with an offset; date-only fields are YYYY-MM-DD. Identifiers are ULIDs — sortable, 26 characters.

Rate limits

120 requests per minute per key, so one integration cannot eat another's quota. Unauthenticated requests are limited far more tightly per IP. Exceeding the limit returns rate_limited with a Retry-After header.