Products
Manage your menu — products, categories, option groups, variants, and pricing.
Overview
The Products API lets you manage your entire menu programmatically. Products support multi-language translations, multiple variants with per-currency pricing, option groups (modifiers/extras), ingredient tracking, and cross-selling.
Products
POST /products
Create a new product with variants, translations, pricing, and category assignments.
Auth: Bearer Token (write:products, Manager role or above)
curl -X POST https://api.ordeliya.com/products \
-H "Authorization: Bearer ord_live_sk_7f3a9b2c..." \
-H "Content-Type: application/json" \
-d '{
"slug": "margherita-pizza",
"translations": [
{
"locale": "da-DK",
"name": "Margherita Pizza",
"shortDesc": "Klassisk tomat og mozzarella",
"longDesc": "Frisk mozzarella, San Marzano tomater, frisk basilikum"
},
{
"locale": "en-GB",
"name": "Margherita Pizza",
"shortDesc": "Classic tomato and mozzarella"
}
],
"categoryIds": ["cat_p1z2a3"],
"variants": [
{
"translations": [{ "locale": "da-DK", "name": "Normal" }],
"prices": [
{ "currency": "DKK", "priceMinor": 8900 },
{ "currency": "EUR", "priceMinor": 1199 }
],
"isDefault": true
},
{
"translations": [{ "locale": "da-DK", "name": "Familiestørrelse" }],
"prices": [
{ "currency": "DKK", "priceMinor": 14900 },
{ "currency": "EUR", "priceMinor": 1999 }
]
}
],
"ingredients": [
{ "ingredientId": "ing_moz1", "rule": "REQUIRED" },
{ "ingredientId": "ing_bas1", "rule": "REMOVABLE" }
],
"isActive": true,
"isFeatured": false,
"sortOrder": 0
}'
| Field | Type | Required | Description |
|---|---|---|---|
slug | string | yes | URL-safe identifier (2-100 chars) |
translations | array | yes | At least one translation with locale and name |
categoryIds | string[] | yes | Category IDs to assign the product to |
variants | array | yes | At least one variant with translations and prices |
variants[].prices | array | yes | Per-currency pricing in minor units |
variants[].prices[].priceMinor | integer | yes | Price in minor units (8900 = 89.00 DKK) |
variants[].prices[].comparePriceMinor | integer | no | Original price for strikethrough display |
variants[].prices[].costMinor | integer | no | Cost price for margin calculation |
variants[].optionLinks | array | no | Link option groups to this variant |
ingredients | array | no | Ingredient associations with rules |
tagIds | string[] | no | Tag IDs for metadata |
crossSellTargetIds | string[] | no | Product IDs to suggest as cross-sell |
isActive | boolean | no | Default true |
isFeatured | boolean | no | Default false |
isPickupOnly | boolean | no | Default false |
sortOrder | integer | no | Display order (ascending) |
Response 201 Created
{
"success": true,
"data": {
"id": "prod_8kx2m4n7",
"storeId": "store_r4k7",
"slug": "margherita-pizza",
"isActive": true,
"isFeatured": false,
"sortOrder": 0,
"translations": [
{
"locale": "da-DK",
"name": "Margherita Pizza",
"shortDesc": "Klassisk tomat og mozzarella",
"longDesc": "Frisk mozzarella, San Marzano tomater, frisk basilikum"
}
],
"variants": [
{
"id": "var_a1b2c3",
"sku": null,
"isDefault": true,
"isActive": true,
"translations": [{ "locale": "da-DK", "name": "Normal" }],
"prices": [
{ "currency": "DKK", "priceMinor": 8900, "comparePriceMinor": null, "costMinor": null },
{ "currency": "EUR", "priceMinor": 1199, "comparePriceMinor": null, "costMinor": null }
]
},
{
"id": "var_d4e5f6",
"sku": null,
"isDefault": false,
"isActive": true,
"translations": [{ "locale": "da-DK", "name": "Familiestørrelse" }],
"prices": [
{ "currency": "DKK", "priceMinor": 14900, "comparePriceMinor": null, "costMinor": null }
]
}
],
"categories": [
{ "categoryId": "cat_p1z2a3", "category": { "id": "cat_p1z2a3", "name": "Pizzas" } }
],
"baseIngredients": [
{ "ingredientId": "ing_moz1", "rule": "REQUIRED", "ingredient": { "name": "Mozzarella" } },
{ "ingredientId": "ing_bas1", "rule": "REMOVABLE", "ingredient": { "name": "Basilikum" } }
],
"createdAt": "2026-03-15T10:00:00.000Z"
}
}
GET /products
List all products with filtering, search, and pagination.
Auth: Bearer Token or API Key (read:products)
curl "https://api.ordeliya.com/products?page=1&pageSize=20&search=pizza&isActive=true" \
-H "Authorization: Bearer ord_live_sk_7f3a9b2c..."
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number |
pageSize | integer | 10 | Items per page (max 100) |
search | string | — | Search in product names (case-insensitive) |
locale | string | en | Locale for translations |
isActive | boolean | — | Filter by active status |
categoryId | string | — | Filter by category |
tagId | string | — | Filter by tag |
isFeatured | boolean | — | Filter featured products |
includeDetails | boolean | false | Include ingredients, cross-sell in response |
Response 200 OK
{
"success": true,
"data": [
{
"id": "prod_8kx2m4n7",
"slug": "margherita-pizza",
"isActive": true,
"isFeatured": false,
"sortOrder": 0,
"translations": [{ "locale": "da-DK", "name": "Margherita Pizza" }],
"variants": [
{
"id": "var_a1b2c3",
"isDefault": true,
"prices": [{ "currency": "DKK", "priceMinor": 8900 }]
}
],
"categories": [
{ "categoryId": "cat_p1z2a3", "category": { "name": "Pizzas" } }
]
}
],
"meta": {
"total": 47,
"page": 1,
"pageSize": 20,
"totalPages": 3
}
}
GET /products/:id
Get full product detail including all variants, prices, ingredients, and cross-sell products.
Auth: Bearer Token or API Key (read:products)
curl https://api.ordeliya.com/products/prod_8kx2m4n7 \
-H "Authorization: Bearer ord_live_sk_7f3a9b2c..."
Returns the full product object (same shape as the POST response).
PATCH /products/:id
Update a product. Only provided fields are modified. Array fields (translations, variants, ingredients) use replace-all semantics — the old records are replaced entirely with the new array.
Auth: Bearer Token (write:products, Manager role or above)
curl -X PATCH https://api.ordeliya.com/products/prod_8kx2m4n7 \
-H "Authorization: Bearer ord_live_sk_7f3a9b2c..." \
-H "Content-Type: application/json" \
-d '{
"isActive": false,
"isFeatured": true
}'
Response 200 OK — Full product object with updated fields.
DELETE /products/:id
Permanently delete a product and all its variants, prices, and associations.
Auth: Bearer Token (write:products, Admin or Owner role)
curl -X DELETE https://api.ordeliya.com/products/prod_8kx2m4n7 \
-H "Authorization: Bearer ord_live_sk_7f3a9b2c..."
Response 200 OK
{
"success": true,
"data": {
"message": "Product deleted successfully"
}
}
PATCH /products/bulk
Perform bulk operations on multiple products at once.
Auth: Bearer Token (write:products, Manager role or above)
curl -X PATCH https://api.ordeliya.com/products/bulk \
-H "Authorization: Bearer ord_live_sk_7f3a9b2c..." \
-H "Content-Type: application/json" \
-d '{
"ids": ["prod_8kx2m4n7", "prod_3jn8v5q2", "prod_x9y0z1"],
"action": "deactivate"
}'
| Action | Description |
|---|---|
activate | Set all products to active |
deactivate | Set all products to inactive |
move_category | Move all to a new category (requires categoryId) |
delete | Permanently delete all |
Response 200 OK
{
"success": true,
"data": {
"message": "3 products deactivated",
"count": 3
}
}
PATCH /products/reorder
Update the display order of multiple products in a single request.
Auth: Bearer Token (write:products, Manager role or above)
curl -X PATCH https://api.ordeliya.com/products/reorder \
-H "Authorization: Bearer ord_live_sk_7f3a9b2c..." \
-H "Content-Type: application/json" \
-d '{
"items": [
{ "id": "prod_8kx2m4n7", "sortOrder": 0 },
{ "id": "prod_3jn8v5q2", "sortOrder": 1 },
{ "id": "prod_x9y0z1", "sortOrder": 2 }
]
}'
Response 200 OK
{
"success": true,
"data": {
"message": "Products reordered successfully",
"count": 3
}
}
Categories
Categories organize your menu. Products can belong to multiple categories.
POST /categories
curl -X POST https://api.ordeliya.com/categories \
-H "Authorization: Bearer ord_live_sk_7f3a9b2c..." \
-H "Content-Type: application/json" \
-d '{
"name": "Pizzas",
"slug": "pizzas",
"parentId": null,
"sortOrder": 0,
"isActive": true
}'
Response 201 Created
{
"success": true,
"data": {
"id": "cat_p1z2a3",
"storeId": "store_r4k7",
"name": "Pizzas",
"slug": "pizzas",
"parentId": null,
"sortOrder": 0,
"isActive": true,
"createdAt": "2026-03-15T10:00:00.000Z"
}
}
GET /categories
List all categories for the current store, including nested hierarchy.
curl https://api.ordeliya.com/categories \
-H "Authorization: Bearer ord_live_sk_7f3a9b2c..."
Response 200 OK
{
"success": true,
"data": [
{
"id": "cat_p1z2a3",
"name": "Pizzas",
"slug": "pizzas",
"parentId": null,
"sortOrder": 0,
"isActive": true,
"productCount": 12
},
{
"id": "cat_s1d2e3",
"name": "Sides",
"slug": "sides",
"parentId": null,
"sortOrder": 1,
"isActive": true,
"productCount": 8
}
]
}
PATCH /categories/:id
curl -X PATCH https://api.ordeliya.com/categories/cat_p1z2a3 \
-H "Authorization: Bearer ord_live_sk_7f3a9b2c..." \
-H "Content-Type: application/json" \
-d '{ "name": "Premium Pizzas", "sortOrder": 0 }'
DELETE /categories/:id
curl -X DELETE https://api.ordeliya.com/categories/cat_p1z2a3 \
-H "Authorization: Bearer ord_live_sk_7f3a9b2c..."
Response 204 No Content
PATCH /categories/reorder
Reorder categories and optionally change parent assignments.
curl -X PATCH https://api.ordeliya.com/categories/reorder \
-H "Authorization: Bearer ord_live_sk_7f3a9b2c..." \
-H "Content-Type: application/json" \
-d '{
"items": [
{ "id": "cat_p1z2a3", "sortOrder": 0, "parentId": null },
{ "id": "cat_s1d2e3", "sortOrder": 1, "parentId": null }
]
}'
Option Groups
Option groups define modifiers and extras (e.g., "Size", "Toppings", "Extra Cheese"). They are linked to product variants.
POST /option-groups
curl -X POST https://api.ordeliya.com/option-groups \
-H "Authorization: Bearer ord_live_sk_7f3a9b2c..." \
-H "Content-Type: application/json" \
-d '{
"name": "Extra Toppings",
"isRequired": false,
"allowMultiple": true,
"choices": [
{
"name": "Extra Cheese",
"prices": [{ "currency": "DKK", "priceMinor": 1500 }]
},
{
"name": "Pepperoni",
"prices": [{ "currency": "DKK", "priceMinor": 2000 }]
},
{
"name": "Mushrooms",
"prices": [{ "currency": "DKK", "priceMinor": 1500 }]
}
]
}'
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Group name (e.g., "Size", "Toppings") |
isRequired | boolean | no | Must the customer select at least one? Default false |
allowMultiple | boolean | no | Can the customer select more than one? Default false |
choices | array | yes | Available options within this group |
choices[].name | string | yes | Choice label (e.g., "Extra Cheese") |
choices[].prices | array | yes | Price delta per currency in minor units |
Response 201 Created
{
"success": true,
"data": {
"id": "og_t1o2p3",
"storeId": "store_r4k7",
"name": "Extra Toppings",
"isRequired": false,
"allowMultiple": true,
"choices": [
{
"id": "oc_c1h2e3",
"name": "Extra Cheese",
"prices": [{ "currency": "DKK", "priceMinor": 1500 }]
},
{
"id": "oc_p4e5p6",
"name": "Pepperoni",
"prices": [{ "currency": "DKK", "priceMinor": 2000 }]
},
{
"id": "oc_m7u8s9",
"name": "Mushrooms",
"prices": [{ "currency": "DKK", "priceMinor": 1500 }]
}
]
}
}
GET /option-groups
List all option groups for the current store.
curl https://api.ordeliya.com/option-groups \
-H "Authorization: Bearer ord_live_sk_7f3a9b2c..."
PATCH /option-groups/:id
Update an option group.
curl -X PATCH https://api.ordeliya.com/option-groups/og_t1o2p3 \
-H "Authorization: Bearer ord_live_sk_7f3a9b2c..." \
-H "Content-Type: application/json" \
-d '{ "isRequired": true }'
DELETE /option-groups/:id
curl -X DELETE https://api.ordeliya.com/option-groups/og_t1o2p3 \
-H "Authorization: Bearer ord_live_sk_7f3a9b2c..."
POST /option-groups/:id/choices
Add a new choice to an existing option group.
curl -X POST https://api.ordeliya.com/option-groups/og_t1o2p3/choices \
-H "Authorization: Bearer ord_live_sk_7f3a9b2c..." \
-H "Content-Type: application/json" \
-d '{
"name": "Jalapeños",
"prices": [{ "currency": "DKK", "priceMinor": 1000 }]
}'
PATCH /option-groups/:groupId/choices/:choiceId
Update a specific choice within an option group.
curl -X PATCH https://api.ordeliya.com/option-groups/og_t1o2p3/choices/oc_c1h2e3 \
-H "Authorization: Bearer ord_live_sk_7f3a9b2c..." \
-H "Content-Type: application/json" \
-d '{
"name": "Extra Mozzarella",
"prices": [{ "currency": "DKK", "priceMinor": 1800 }]
}'
DELETE /option-groups/:groupId/choices/:choiceId
Remove a choice from an option group.
curl -X DELETE https://api.ordeliya.com/option-groups/og_t1o2p3/choices/oc_c1h2e3 \
-H "Authorization: Bearer ord_live_sk_7f3a9b2c..."
Ingredients
Ingredients track pantry items and allergens. Link them to products with rules (REQUIRED, REMOVABLE).
POST /ingredients
curl -X POST https://api.ordeliya.com/ingredients \
-H "Authorization: Bearer ord_live_sk_7f3a9b2c..." \
-H "Content-Type: application/json" \
-d '{
"name": "Mozzarella",
"allergenIds": ["alg_dairy"]
}'
GET /ingredients
curl https://api.ordeliya.com/ingredients \
-H "Authorization: Bearer ord_live_sk_7f3a9b2c..."
Response 200 OK
{
"success": true,
"data": [
{
"id": "ing_moz1",
"name": "Mozzarella",
"allergens": [{ "id": "alg_dairy", "name": "Milk" }]
},
{
"id": "ing_bas1",
"name": "Basilikum",
"allergens": []
}
]
}
PATCH /ingredients/:id
curl -X PATCH https://api.ordeliya.com/ingredients/ing_moz1 \
-H "Authorization: Bearer ord_live_sk_7f3a9b2c..." \
-H "Content-Type: application/json" \
-d '{ "name": "Fresh Mozzarella" }'
DELETE /ingredients/:id
curl -X DELETE https://api.ordeliya.com/ingredients/ing_moz1 \
-H "Authorization: Bearer ord_live_sk_7f3a9b2c..."
Response 204 No Content