Products
PUT /products
Creates or updates products and modifier groups. Send only products that changed, and send them before pushing that day's transactions.
Authentication: API key (x-functions-key)
Request
Request body
| Field |
Type |
Required |
Description |
| batch_id |
UUID |
Yes |
Idempotency key |
| products |
array of Product |
Yes |
0–500 products. May be empty when you only update modifier groups. |
| modifier_groups |
array of ModifierGroup |
No |
Modifier groups that changed; upserted by group_id |
Product object
| Field |
Type |
Required |
Description |
| product_id |
string |
Yes |
Your product code |
| store_id |
string | null |
No |
Set for a store-specific product; null or omitted means all stores |
| sku |
string |
No |
Barcode or SKU |
| name |
localized text |
Yes |
Product name, at least one language |
| category_id |
string |
No |
Category code |
| category_name |
localized text |
No |
Category name |
| unit_price |
decimal string |
Yes |
Selling price. Tax-inclusive or not according to the store's prices_include_tax. |
| currency |
string |
Yes |
ISO 4217 |
| tax_category |
enum |
No |
See tax categories |
| status |
enum |
Yes |
ACTIVE / INACTIVE |
| effective_from |
timestamp |
No |
When the price takes effect |
| version |
integer |
Yes |
Increase by 1 on every change |
| last_modified_at |
timestamp |
Yes |
Last change time |
| product_type |
enum |
Yes |
SINGLE (à la carte) / COMBO (set meal) / SIDE / DRINK / MODIFIER (add-on) |
| modifier_group_ids |
array of string |
No |
group_ids of the modifier groups available for this product |
| combo_components |
array of ComboComponent |
No |
COMBO only: items included in the combo |
ComboComponent object
| Field |
Type |
Required |
Description |
| product_id |
string |
Yes |
product_id of the included item |
| quantity |
decimal string |
Yes |
Quantity |
| is_substitutable |
boolean |
Yes |
Whether the customer can swap this item |
| substitute_group_id |
string |
Conditional |
Required when is_substitutable is true: the group_id listing the allowed substitutes |
ModifierGroup object
| Field |
Type |
Required |
Description |
| group_id |
string |
Yes |
Modifier group code |
| name |
localized text |
Yes |
Group name |
| min_select |
integer |
Yes |
Minimum selections (0 = optional) |
| max_select |
integer |
Yes |
Maximum selections (≥ min_select) |
| options |
array of ModifierOption |
Yes |
At least 1 option |
ModifierOption object
| Field |
Type |
Required |
Description |
| option_id |
string |
Yes |
Option code. If it equals a product_id (e.g. the drink upgrade DRK-BOBA), choosing it means choosing that product. |
| name |
localized text |
Yes |
Option name |
| price_delta |
decimal string |
Yes |
Extra charge (may be "0.00") |
| is_default |
boolean |
No |
Whether this is the default option. Default false. |
Example
{
"batch_id": "9c4b2a77-0d1e-4f3a-b5c6-7d8e9f0a1b2c",
"products": [
{
"product_id": "COMBO-02",
"store_id": null,
"name": { "en-US": "Master Jimbo Combo" },
"category_id": "COMBO",
"unit_price": "14.49",
"currency": "USD",
"tax_category": "STANDARD",
"status": "ACTIVE",
"product_type": "COMBO",
"modifier_group_ids": ["FLAVOR_JIMBO", "COMBO_DRINK"],
"combo_components": [
{ "product_id": "SGL-JIMBO", "quantity": "1", "is_substitutable": false },
{ "product_id": "SIDE-FRIES", "quantity": "1", "is_substitutable": false },
{ "product_id": "DRK-FOUNTAIN", "quantity": "1", "is_substitutable": true, "substitute_group_id": "COMBO_DRINK" }
],
"version": 1,
"last_modified_at": "2026-10-14T10:00:00-07:00"
},
{
"product_id": "SIDE-FRIES",
"name": { "en-US": "Fries" },
"unit_price": "4.49",
"currency": "USD",
"tax_category": "STANDARD",
"status": "ACTIVE",
"product_type": "SIDE",
"modifier_group_ids": ["FRIES_EXTRA_FLAVOR"],
"version": 1,
"last_modified_at": "2026-10-14T10:00:00-07:00"
}
],
"modifier_groups": [
{
"group_id": "FRIES_EXTRA_FLAVOR",
"name": { "en-US": "Add Extra Flavor" },
"min_select": 0,
"max_select": 2,
"options": [
{ "option_id": "SEAWEED", "name": { "en-US": "Seaweed" }, "price_delta": "0.75" },
{ "option_id": "CHEESE_POWDER", "name": { "en-US": "Cheese Powder" }, "price_delta": "0.75" }
]
}
]
}
A combo drink group where upgrades are other products:
{
"group_id": "COMBO_DRINK",
"name": { "en-US": "Combo Drink" },
"min_select": 1, "max_select": 1,
"options": [
{ "option_id": "DRK-FOUNTAIN", "name": { "en-US": "Fountain Drinks" }, "price_delta": "0.00", "is_default": true },
{ "option_id": "DRK-TW-LEMONADE", "name": { "en-US": "Taiwan Signature Lemonade" }, "price_delta": "2.50" },
{ "option_id": "DRK-KUMQUAT-LEMONADE", "name": { "en-US": "Kumquat Lemonade" }, "price_delta": "2.50" },
{ "option_id": "DRK-BOBA", "name": { "en-US": "Taiwan Boba Tea" }, "price_delta": "3.50" }
]
}
Response
202 Accepted — record_count is the number of products.
{ "batch_id": "9c4b2a77-0d1e-4f3a-b5c6-7d8e9f0a1b2c", "status": "ACCEPTED", "record_count": 2, "warnings": [] }
Errors
| HTTP |
code |
When |
| 400 |
INVALID_PAYLOAD |
Invalid type, missing field, invalid enum, wrong decimal places |
| 400 |
BATCH_TOO_LARGE |
More products than your batch limit |
| 403 |
STORE_NOT_ALLOWED |
A product's store_id is not registered for your account |
| 413 |
PAYLOAD_TOO_LARGE |
Body larger than 5 MB |
Notes
- How combos and modifiers appear in transactions is described in Recording Rules.
- If a transaction arrives before its product, it is still accepted and matched once the product arrives.