# Products & Steps

### Products

Products represent purchasable items or options within a step.

#### Key Concept: Signatures & Versioning

Products and steps are **versioned**. Every update creates a new version: the numeric `productId` / `stepId` changes, while the `signature` remains stable across versions. Always use the `signature` to reference a product or step in read/write operations, and re-read the entity after an update to get the new ID.

### `GET /menus/{menuId}/products` - Get All Products

Retrieve a list of all products associated with a specific menu. Supports name search and pagination.

#### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| menuId | integer | Yes | The unique identifier of the menu. |
| search | string | No | Filter products by name (query parameter). |
| step\_exclusion | integer | No | Exclude products already attached to the given step ID (query parameter). |
| limit | integer | No | Pagination limit (query parameter). |
| offset | integer | No | Pagination offset (query parameter). |
| order | string | No | Sort order (query parameter). |

#### Response

```json
{
  "status": 200,
  "code": "products_succeed",
  "message": "Products succeed.",
  "data": [
    {
      "productId": 464462,
      "menuId": 106477,
      "signature": "d41d8cd98f00b204",
      "name": "Cheeseburger",
      "price": 1200,
      "vat": 1000,
      "webAvailability": true,
      "kioskAvailability": true
    }
  ]
}
```

##### Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

| Property | Type | Example | Description |
| --- | --- | --- | --- |
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "products\_succeed" | Machine-readable application code for the result. |
| message | string | "Products succeed." | Human-readable result message. Do not use this value for program logic. |
| data | array | \[…\] | Endpoint-specific response payload. |
| data\[\] | object | {…} | Endpoint-specific response payload. |
| data\[\].productId | integer | 464462 | Identifier of the product. |
| data\[\].menuId | integer | 106477 | Identifier of the menu. |
| data\[\].signature | string | "d41d8cd98f00b204" | The signature value. |
| data\[\].name | string | "Cheeseburger" | The name value. |
| data\[\].price | integer | 1200 | The price value. |
| data\[\].vat | integer | 1000 | The vat value. |
| data\[\].webAvailability | boolean | true | The web availability value. |
| data\[\].kioskAvailability | boolean | true | The kiosk availability value. |

### `GET /menus/{menuId}/products/{signature}` - Get Product Details

Retrieve full details for a single product identified by its signature, including image, tags, categories, steps and external references.

#### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| menuId | integer | Yes | The unique identifier of the menu. |
| signature | string | Yes | The stable signature of the product. |

#### Response

```json
{
  "status": 200,
  "data": {
    "productId": 464462,
    "menuId": 106477,
    "signature": "d41d8cd98f00b204",
    "name": "Cheeseburger",
    "price": 1200,
    "vat": 1000,
    "tags": [],
    "productStep": []
  }
}
```

##### Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

| Property | Type | Example | Description |
| --- | --- | --- | --- |
| status | integer | 200 | HTTP status code returned by the API. |
| data | object | {…} | Endpoint-specific response payload. |
| data.productId | integer | 464462 | Identifier of the product. |
| data.menuId | integer | 106477 | Identifier of the menu. |
| data.signature | string | "d41d8cd98f00b204" | The signature value. |
| data.name | string | "Cheeseburger" | The name value. |
| data.price | integer | 1200 | The price value. |
| data.vat | integer | 1000 | The vat value. |
| data.tags | array | \[\] | List of tags entries. |
| data.productStep | array | \[\] | List of product step entries. |

### Create, Update & Delete Products

Prices are expressed in **cents** (1200 = 12.00) and VAT rates in **basis points** (1000 = 10%). If the restaurant is attached to an operation zone, `vatGroupId` (the VAT family) is mandatory when creating or updating a product. Valid VAT family IDs can be retrieved with `GET /api/v1/restaurants/{id}/vat-rates` (see Restaurant Ops → Zones): each entry of its `vatGroups` array carries the `vatGroupId` to send here, along with the family `code`, `name` and its per-consumption-mode rates (`eatIn`, `takeAway`, `delivery`) in basis points.

### `POST /menus/{menuId}/products` - Create Product

Create a new product in a menu. Providing a "sku" object creates (or reuses) the brand-level SKU and links it to the product. "tags" and "categories" reference existing tag/category IDs of the menu, and "productStep" lists the steps attached to the product.

#### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| menuId | integer | Yes | The unique identifier of the menu. |

#### Request Body

```json
{
  "name": "Cheeseburger",
  "price": 1200,
  "description": "Beef patty, cheddar, pickles",
  "vat": 1000,
  "vatSitIn": 1000,
  "vatTakeAway": 550,
  "vatDelivery": 1000,
  "vatGroupId": 12,
  "consumptionFlags": 7,
  "webAvailability": true,
  "kioskAvailability": true,
  "isSitInEnabled": true,
  "isTakeAwayEnabled": true,
  "isDeliveryEnabled": true,
  "measurementUnit": "unit",
  "imageId": 1001,
  "tags": [
    101,
    102
  ],
  "categories": [
    30085
  ],
  "sku": {
    "value": "BURGER-001"
  },
  "productStep": []
}
```

##### Request Body Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

| Property | Type | Example | Description |
| --- | --- | --- | --- |
| name | string | "Cheeseburger" | The name value. |
| price | integer | 1200 | The price value. |
| description | string | "Beef patty, cheddar, pickles" | The description value. |
| vat | integer | 1000 | The vat value. |
| vatSitIn | integer | 1000 | The vat sit in value. |
| vatTakeAway | integer | 550 | The vat take away value. |
| vatDelivery | integer | 1000 | The vat delivery value. |
| vatGroupId | integer | 12 | Identifier of the associated vat group. |
| consumptionFlags | integer | 7 | The consumption flags value. |
| webAvailability | boolean | true | The web availability value. |
| kioskAvailability | boolean | true | The kiosk availability value. |
| isSitInEnabled | boolean | true | Whether sit in enabled is enabled or applies. |
| isTakeAwayEnabled | boolean | true | Whether take away enabled is enabled or applies. |
| isDeliveryEnabled | boolean | true | Whether delivery enabled is enabled or applies. |
| measurementUnit | string | "unit" | The measurement unit value. |
| imageId | integer | 1001 | Identifier of the associated image. |
| tags | array | \[…\] | List of tags entries. |
| tags\[\] | integer | 101 | The tags value. |
| categories | array | \[…\] | List of categories entries. |
| categories\[\] | integer | 30085 | The categories value. |
| sku | object | {…} | Object containing sku fields. |
| sku.value | string | "BURGER-001" | The value value. |
| productStep | array | \[\] | List of product step entries. |

#### Response

```json
{
  "status": 200,
  "code": "product_create_succeed",
  "data": {
    "productId": 464470,
    "menuId": 106477,
    "signature": "9b2f4c1a7e3d",
    "name": "Cheeseburger",
    "price": 1200
  }
}
```

##### Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

| Property | Type | Example | Description |
| --- | --- | --- | --- |
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "product\_create\_succeed" | Machine-readable application code for the result. |
| data | object | {…} | Endpoint-specific response payload. |
| data.productId | integer | 464470 | Identifier of the product. |
| data.menuId | integer | 106477 | Identifier of the menu. |
| data.signature | string | "9b2f4c1a7e3d" | The signature value. |
| data.name | string | "Cheeseburger" | The name value. |
| data.price | integer | 1200 | The price value. |

Tags Reset

When updating a product, always resend the **full** `tags` array: if `tags` is omitted, the new product version is created with **no tags at all**.

### `PUT /menus/{menuId}/products/{signature}` - Update Product

Update a product. This creates a new version: the productId changes while the signature remains stable. Most omitted fields keep their current value, but "tags" is an exception: if omitted, the new version is created with no tags - always resend the full tags array. When "productStep" is provided, the full list of steps attached to the product is replaced (recursion between products and steps is validated server-side).

#### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| menuId | integer | Yes | The unique identifier of the menu. |
| signature | string | Yes | The stable signature of the product. |

#### Request Body

```json
{
  "name": "Cheeseburger XL",
  "price": 1350,
  "vat": 1000,
  "vatGroupId": 12,
  "webAvailability": true,
  "kioskAvailability": true,
  "tags": [
    101
  ],
  "claimOrder": 10,
  "productStep": [
    {
      "stepId": 560,
      "menuId": 106477,
      "product_step": {
        "position": 1
      }
    },
    {
      "stepId": 561,
      "menuId": 106477,
      "product_step": {
        "position": 2
      }
    }
  ],
  "pos": {
    "ticketLabel": "CHZBRG XL",
    "discountable": true
  }
}
```

##### Request Body Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

| Property | Type | Example | Description |
| --- | --- | --- | --- |
| name | string | "Cheeseburger XL" | The name value. |
| price | integer | 1350 | The price value. |
| vat | integer | 1000 | The vat value. |
| vatGroupId | integer | 12 | Identifier of the associated vat group. |
| webAvailability | boolean | true | The web availability value. |
| kioskAvailability | boolean | true | The kiosk availability value. |
| tags | array | \[…\] | List of tags entries. |
| tags\[\] | integer | 101 | The tags value. |
| claimOrder | integer | 10 | The claim order value. |
| productStep | array | \[…\] | List of product step entries. |
| productStep\[\] | object | {…} | Object containing product step fields. |
| productStep\[\].stepId | integer | 560 | Identifier of the associated step. |
| productStep\[\].menuId | integer | 106477 | Identifier of the menu. |
| productStep\[\].product\_step | object | {…} | Object containing product step fields. |
| productStep\[\].product\_step.position | integer | 1 | The position value. |
| pos | object | {…} | Object containing pos fields. |
| pos.ticketLabel | string | "CHZBRG XL" | The ticket label value. |
| pos.discountable | boolean | true | The discountable value. |

#### Response

```json
{
  "status": 200,
  "code": "product_update_succeed",
  "message": "Product updated.",
  "data": {
    "productId": 464471,
    "menuId": 106477,
    "signature": "9b2f4c1a7e3d",
    "name": "Cheeseburger XL",
    "price": 1350
  }
}
```

##### Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

| Property | Type | Example | Description |
| --- | --- | --- | --- |
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "product\_update\_succeed" | Machine-readable application code for the result. |
| message | string | "Product updated." | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.productId | integer | 464471 | Identifier of the product. |
| data.menuId | integer | 106477 | Identifier of the menu. |
| data.signature | string | "9b2f4c1a7e3d" | The signature value. |
| data.name | string | "Cheeseburger XL" | The name value. |
| data.price | integer | 1350 | The price value. |

#### Workflow: Product with Steps

To build a composite product (e.g. a burger with a "Cooking Level" choice):

1.  Create the option products (e.g. "Rare", "Medium") with `POST /menus/{menuId}/products`.
2.  Create the step listing them with `POST /menus/{menuId}/steps` and its `stepProduct` array.
3.  Attach the step to the parent product via `productStep` on create or update. Each entry references an existing step of the menu by its **current numeric `stepId`** (not its signature), with an optional `product_step.position` (defaults to the array order): `{ "stepId": 560, "menuId": 106477, "product_step": { "position": 1 } }`.

Remember that `productStep` replaces the full list of attached steps, and that updating a step changes its `stepId` - re-read the step to get the new ID before re-attaching it.

### `PATCH /menus/{menuId}/products` - Mass Update Products

Update several products of a menu at once, targeted by their signatures. The "data" object contains the fields to apply to all targeted products. The "overwrite" object controls whether categories, steps, tags and consumption modes are replaced or merged.

#### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| menuId | integer | Yes | The unique identifier of the menu. |

#### Request Body

```json
{
  "signatures": [
    "d41d8cd98f00b204",
    "9b2f4c1a7e3d"
  ],
  "data": {
    "price": 1300,
    "vat": 1000,
    "kioskAvailability": true,
    "tags": [
      101
    ],
    "categories": [
      30085
    ]
  },
  "overwrite": {
    "categories": false,
    "steps": false,
    "tags": false,
    "consumptionMode": {
      "MODE_SIT_IN": false,
      "MODE_TAKE_AWAY": false,
      "MODE_DELIVERY": false,
      "MODE_DRIVE": false
    }
  }
}
```

##### Request Body Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

| Property | Type | Example | Description |
| --- | --- | --- | --- |
| signatures | array | \[…\] | List of signatures entries. |
| signatures\[\] | string | "d41d8cd98f00b204" | The signatures value. |
| data | object | {…} | Endpoint-specific response payload. |
| data.price | integer | 1300 | The price value. |
| data.vat | integer | 1000 | The vat value. |
| data.kioskAvailability | boolean | true | The kiosk availability value. |
| data.tags | array | \[…\] | List of tags entries. |
| data.tags\[\] | integer | 101 | The tags value. |
| data.categories | array | \[…\] | List of categories entries. |
| data.categories\[\] | integer | 30085 | The categories value. |
| overwrite | object | {…} | Object containing overwrite fields. |
| overwrite.categories | boolean | false | The categories value. |
| overwrite.steps | boolean | false | The steps value. |
| overwrite.tags | boolean | false | The tags value. |
| overwrite.consumptionMode | object | {…} | Object containing consumption mode fields. |
| overwrite.consumptionMode.MODE\_SIT\_IN | boolean | false | The mode sit in value. |
| overwrite.consumptionMode.MODE\_TAKE\_AWAY | boolean | false | The mode take away value. |
| overwrite.consumptionMode.MODE\_DELIVERY | boolean | false | The mode delivery value. |
| overwrite.consumptionMode.MODE\_DRIVE | boolean | false | The mode drive value. |

#### Response

```json
{
  "status": 200,
  "code": "menu_update_succeed",
  "message": "Menu has been successfully updated.",
  "data": {
    "menuId": 106477,
    "name": "Main Menu",
    "channels": []
  }
}
```

##### Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

| Property | Type | Example | Description |
| --- | --- | --- | --- |
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "menu\_update\_succeed" | Machine-readable application code for the result. |
| message | string | "Menu has been successfully updated." | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.menuId | integer | 106477 | Identifier of the menu. |
| data.name | string | "Main Menu" | The name value. |
| data.channels | array | \[\] | List of channels entries. |

### `PUT /menus/{menuId}/products/{signature}/quantity` - Update Product Stock

Update only the stock quantity (currentQuantity) of a product. When stock management is enabled, the stock movement is audited and the product can be automatically disabled when reaching zero. The product is also removed from in-progress carts.

#### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| menuId | integer | Yes | The unique identifier of the menu. |
| signature | string | Yes | The stable signature of the product. |

#### Request Body

```json
{
  "quantity": 25
}
```

##### Request Body Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

| Property | Type | Example | Description |
| --- | --- | --- | --- |
| quantity | integer | 25 | The quantity value. |

#### Response

```json
{
  "status": 200,
  "code": "product_quantity_succeed",
  "message": "Products quantity was update successfully.",
  "data": {
    "productId": 464471,
    "signature": "9b2f4c1a7e3d",
    "currentQuantity": 25
  }
}
```

##### Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

| Property | Type | Example | Description |
| --- | --- | --- | --- |
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "product\_quantity\_succeed" | Machine-readable application code for the result. |
| message | string | "Products quantity was update successfully." | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.productId | integer | 464471 | Identifier of the product. |
| data.signature | string | "9b2f4c1a7e3d" | The signature value. |
| data.currentQuantity | integer | 25 | The current quantity value. |

### `POST /menus/{menuId}/products/{signature}/duplicate` - Duplicate Product

Duplicate a product within the same menu. The copy gets a new signature. No request body is needed.

#### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| menuId | integer | Yes | The unique identifier of the menu. |
| signature | string | Yes | The stable signature of the product to duplicate. |

#### Response

```json
{
  "status": 200,
  "code": "product_duplicate_succeed",
  "data": {
    "productId": 464480,
    "signature": "3c7e9a1b5d2f",
    "name": "Cheeseburger XL"
  }
}
```

##### Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

| Property | Type | Example | Description |
| --- | --- | --- | --- |
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "product\_duplicate\_succeed" | Machine-readable application code for the result. |
| data | object | {…} | Endpoint-specific response payload. |
| data.productId | integer | 464480 | Identifier of the product. |
| data.signature | string | "3c7e9a1b5d2f" | The signature value. |
| data.name | string | "Cheeseburger XL" | The name value. |

### `DELETE /menus/{menuId}/products/{signature}` - Delete Product

Delete a product from the menu. Associated external ID mappings and external references are cleaned up.

#### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| menuId | integer | Yes | The unique identifier of the menu. |
| signature | string | Yes | The stable signature of the product to delete. |

#### Response

```json
{
  "status": 200,
  "code": "product_delete_succeed"
}
```

##### Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

| Property | Type | Example | Description |
| --- | --- | --- | --- |
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "product\_delete\_succeed" | Machine-readable application code for the result. |

#### Product Attributes

**ProductAttributes Model**

```typescript
export interface ProductAttributes {
  productId: number;         // Changes on every update (versioning)
  signature: string;         // Stable identifier across versions
  menuId: number;
  name: string;
  description: string | null;
  additionalInformation: string | null;
  price: number;             // in cents (e.g. 1200 = 12.00)
  purchasePrice: number | null;
  vat: number;               // basis points (e.g. 1000 = 10%)
  vatSitIn: number;
  vatTakeAway: number;
  vatDelivery: number;
  vatGroupId: number | null; // VAT family (required with operation zones)
  consumptionFlags: number;  // Bitmask for consumption modes
  webAvailability: boolean;
  kioskAvailability: boolean;
  isSitInEnabled: boolean;
  isTakeAwayEnabled: boolean;
  isDeliveryEnabled: boolean;
  isFinalProduct: boolean;
  isUpselling: boolean;
  isProducible: boolean;
  isEligibleLuncheon: boolean;
  measurementUnit: 'unit' | 'kg';
  servingCount: number;
  currentQuantity: number | null; // Stock level
  defaultQuantity: number | null;
  claimOrder: number | null;      // 0-99
  billingCode: string | null;
  machineCode: string | null;
  imageId: number | null;
  skuId: number | null;
  tags: TagAttributes[];
  productStep: StepAttributes[];  // Steps attached to the product
  pos: object;                    // POS-specific settings
}
```

### Steps

Steps (or Option Groups) define modifiers for products, such as "Cooking Level" or "Sauces". A step has a `type`: `1` (CUSTOMIZATION) or `2` (CROSS\_SELLING). Like products, steps are versioned: updates create a new `stepId` while the `signature` remains stable.

### `GET /menus/{menuId}/steps` - Get Steps

Retrieve all steps for a specific menu, with their step products. Supports name search and pagination.

#### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| menuId | integer | Yes | The unique identifier of the menu. |
| search | string | No | Filter steps by name (query parameter). |
| limit | integer | No | Pagination limit (query parameter). |
| offset | integer | No | Pagination offset (query parameter). |
| order | string | No | Sort order (query parameter). |

#### Response

```json
{
  "status": 200,
  "code": "steps_succeed",
  "data": [
    {
      "stepId": 554,
      "signature": "7f3a9c1e5b2d",
      "name": "Cooking Level",
      "type": 1,
      "minChoice": 1,
      "maxChoice": 1,
      "stepProduct": [
        {
          "name": "Rare",
          "productId": 991
        },
        {
          "name": "Medium",
          "productId": 992
        }
      ]
    }
  ]
}
```

##### Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

| Property | Type | Example | Description |
| --- | --- | --- | --- |
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "steps\_succeed" | Machine-readable application code for the result. |
| data | array | \[…\] | Endpoint-specific response payload. |
| data\[\] | object | {…} | Endpoint-specific response payload. |
| data\[\].stepId | integer | 554 | Identifier of the associated step. |
| data\[\].signature | string | "7f3a9c1e5b2d" | The signature value. |
| data\[\].name | string | "Cooking Level" | The name value. |
| data\[\].type | integer | 1 | The type value. |
| data\[\].minChoice | integer | 1 | The min choice value. |
| data\[\].maxChoice | integer | 1 | The max choice value. |
| data\[\].stepProduct | array | \[…\] | List of step product entries. |
| data\[\].stepProduct\[\] | object | {…} | Object containing step product fields. |
| data\[\].stepProduct\[\].name | string | "Rare" | The name value. |
| data\[\].stepProduct\[\].productId | integer | 991 | Identifier of the product. |

### `GET /menus/{menuId}/steps/{signature}` - Get Step Details

Retrieve a single step by its signature, including its step products.

#### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| menuId | integer | Yes | The unique identifier of the menu. |
| signature | string | Yes | The stable signature of the step. |

#### Response

```json
{
  "status": 200,
  "data": {
    "stepId": 554,
    "signature": "7f3a9c1e5b2d",
    "name": "Cooking Level",
    "type": 1,
    "minChoice": 1,
    "maxChoice": 1,
    "stepProduct": [
      {
        "name": "Rare",
        "productId": 991
      },
      {
        "name": "Medium",
        "productId": 992
      }
    ]
  }
}
```

##### Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

| Property | Type | Example | Description |
| --- | --- | --- | --- |
| status | integer | 200 | HTTP status code returned by the API. |
| data | object | {…} | Endpoint-specific response payload. |
| data.stepId | integer | 554 | Identifier of the associated step. |
| data.signature | string | "7f3a9c1e5b2d" | The signature value. |
| data.name | string | "Cooking Level" | The name value. |
| data.type | integer | 1 | The type value. |
| data.minChoice | integer | 1 | The min choice value. |
| data.maxChoice | integer | 1 | The max choice value. |
| data.stepProduct | array | \[…\] | List of step product entries. |
| data.stepProduct\[\] | object | {…} | Object containing step product fields. |
| data.stepProduct\[\].name | string | "Rare" | The name value. |
| data.stepProduct\[\].productId | integer | 991 | Identifier of the product. |

### Create, Update & Delete Steps

### `POST /menus/{menuId}/steps` - Create Step

Create a step in a menu with its step products. The "stepProduct" array is required and lists existing products of the menu with their per-step settings. Products tagged with a customizable price/label, or sold by weight, cannot be added to a step. If provided, "stepRef" must be unique within the menu.

#### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| menuId | integer | Yes | The unique identifier of the menu. |

#### Request Body

```json
{
  "name": "Cooking Level",
  "type": 1,
  "minChoice": 1,
  "maxChoice": 1,
  "ticketLabel": "COOK",
  "webAvailability": true,
  "kioskAvailability": true,
  "stepProduct": [
    {
      "productId": 991,
      "step_product": {
        "price": 0,
        "position": 1
      }
    },
    {
      "productId": 992,
      "step_product": {
        "price": 0,
        "position": 2
      }
    }
  ]
}
```

##### Request Body Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

| Property | Type | Example | Description |
| --- | --- | --- | --- |
| name | string | "Cooking Level" | The name value. |
| type | integer | 1 | The type value. |
| minChoice | integer | 1 | The min choice value. |
| maxChoice | integer | 1 | The max choice value. |
| ticketLabel | string | "COOK" | The ticket label value. |
| webAvailability | boolean | true | The web availability value. |
| kioskAvailability | boolean | true | The kiosk availability value. |
| stepProduct | array | \[…\] | List of step product entries. |
| stepProduct\[\] | object | {…} | Object containing step product fields. |
| stepProduct\[\].productId | integer | 991 | Identifier of the product. |
| stepProduct\[\].step\_product | object | {…} | Object containing step product fields. |
| stepProduct\[\].step\_product.price | integer | 0 | The price value. |
| stepProduct\[\].step\_product.position | integer | 1 | The position value. |

#### Response

```json
{
  "status": 200,
  "code": "step_create_succeed",
  "data": {
    "stepId": 560,
    "signature": "1e8b4d7a2c9f",
    "name": "Cooking Level",
    "type": 1
  }
}
```

##### Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

| Property | Type | Example | Description |
| --- | --- | --- | --- |
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "step\_create\_succeed" | Machine-readable application code for the result. |
| data | object | {…} | Endpoint-specific response payload. |
| data.stepId | integer | 560 | Identifier of the associated step. |
| data.signature | string | "1e8b4d7a2c9f" | The signature value. |
| data.name | string | "Cooking Level" | The name value. |
| data.type | integer | 1 | The type value. |

### `PUT /menus/{menuId}/steps/{signature}` - Update Step

Update a step. This creates a new version: the stepId changes while the signature remains stable. The "stepProduct" array is required and replaces the full list of step products. For CROSS\_SELLING steps, step product prices are forced to null (the price comes from the product itself). Recursion between products and steps is validated server-side.

#### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| menuId | integer | Yes | The unique identifier of the menu. |
| signature | string | Yes | The stable signature of the step. |

#### Request Body

```json
{
  "name": "Cooking Level",
  "type": 1,
  "minChoice": 1,
  "maxChoice": 1,
  "webAvailability": true,
  "kioskAvailability": true,
  "stepProduct": [
    {
      "productId": 991,
      "step_product": {
        "price": 0,
        "position": 1
      }
    },
    {
      "productId": 992,
      "step_product": {
        "price": 0,
        "position": 2
      }
    },
    {
      "productId": 993,
      "step_product": {
        "price": 100,
        "position": 3
      }
    }
  ]
}
```

##### Request Body Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

| Property | Type | Example | Description |
| --- | --- | --- | --- |
| name | string | "Cooking Level" | The name value. |
| type | integer | 1 | The type value. |
| minChoice | integer | 1 | The min choice value. |
| maxChoice | integer | 1 | The max choice value. |
| webAvailability | boolean | true | The web availability value. |
| kioskAvailability | boolean | true | The kiosk availability value. |
| stepProduct | array | \[…\] | List of step product entries. |
| stepProduct\[\] | object | {…} | Object containing step product fields. |
| stepProduct\[\].productId | integer | 991 | Identifier of the product. |
| stepProduct\[\].step\_product | object | {…} | Object containing step product fields. |
| stepProduct\[\].step\_product.price | integer | 0 | The price value. |
| stepProduct\[\].step\_product.position | integer | 1 | The position value. |

#### Response

```json
{
  "status": 200,
  "code": "step_update_succeed",
  "data": {
    "stepId": 561,
    "signature": "7f3a9c1e5b2d",
    "name": "Cooking Level",
    "type": 1
  }
}
```

##### Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

| Property | Type | Example | Description |
| --- | --- | --- | --- |
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "step\_update\_succeed" | Machine-readable application code for the result. |
| data | object | {…} | Endpoint-specific response payload. |
| data.stepId | integer | 561 | Identifier of the associated step. |
| data.signature | string | "7f3a9c1e5b2d" | The signature value. |
| data.name | string | "Cooking Level" | The name value. |
| data.type | integer | 1 | The type value. |

### `POST /menus/{menuId}/steps/convert` - Convert Steps

Convert steps in bulk between the CUSTOMIZATION and CROSS\_SELLING types. All targeted steps must currently be of the same type, otherwise the request is rejected.

#### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| menuId | integer | Yes | The unique identifier of the menu. |

#### Request Body

```json
{
  "signatures": [
    "7f3a9c1e5b2d",
    "1e8b4d7a2c9f"
  ]
}
```

##### Request Body Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

| Property | Type | Example | Description |
| --- | --- | --- | --- |
| signatures | array | \[…\] | List of signatures entries. |
| signatures\[\] | string | "7f3a9c1e5b2d" | The signatures value. |

#### Response

```json
{
  "status": 200,
  "data": [
    {
      "stepId": 562,
      "signature": "7f3a9c1e5b2d",
      "type": 2
    },
    {
      "stepId": 563,
      "signature": "1e8b4d7a2c9f",
      "type": 2
    }
  ]
}
```

##### Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

| Property | Type | Example | Description |
| --- | --- | --- | --- |
| status | integer | 200 | HTTP status code returned by the API. |
| data | array | \[…\] | Endpoint-specific response payload. |
| data\[\] | object | {…} | Endpoint-specific response payload. |
| data\[\].stepId | integer | 562 | Identifier of the associated step. |
| data\[\].signature | string | "7f3a9c1e5b2d" | The signature value. |
| data\[\].type | integer | 2 | The type value. |

### `POST /menus/{menuId}/steps/{signature}/duplicate` - Duplicate Step

Duplicate a step within the same menu, including its step products. No request body is needed.

#### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| menuId | integer | Yes | The unique identifier of the menu. |
| signature | string | Yes | The stable signature of the step to duplicate. |

#### Response

```json
{
  "status": 200,
  "code": "step_duplicate_succeed",
  "data": {
    "stepId": 570,
    "signature": "5a2d8f1c4e7b",
    "name": "Cooking Level"
  }
}
```

##### Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

| Property | Type | Example | Description |
| --- | --- | --- | --- |
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "step\_duplicate\_succeed" | Machine-readable application code for the result. |
| data | object | {…} | Endpoint-specific response payload. |
| data.stepId | integer | 570 | Identifier of the associated step. |
| data.signature | string | "5a2d8f1c4e7b" | The signature value. |
| data.name | string | "Cooking Level" | The name value. |

### `DELETE /menus/{menuId}/steps/{signature}` - Delete Step

Delete a step from the menu.

#### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| menuId | integer | Yes | The unique identifier of the menu. |
| signature | string | Yes | The stable signature of the step to delete. |

#### Response

```json
{
  "status": 200,
  "code": "step_delete_succeed"
}
```

##### Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

| Property | Type | Example | Description |
| --- | --- | --- | --- |
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "step\_delete\_succeed" | Machine-readable application code for the result. |

#### Data Models

##### StepAttributes

```typescript
interface StepAttributes {
  stepId: number;          // Changes on every update (versioning)
  signature: string;       // Stable identifier across versions
  menuId: number;
  name: string;
  type: number;            // 1 = CUSTOMIZATION, 2 = CROSS_SELLING
  minChoice: number;
  maxChoice: number;
  stepRef: string | null;  // Optional stable reference, unique per menu
  ticketLabel: string | null;
  webAvailability: boolean;
  kioskAvailability: boolean;
  isExcessAllowed: boolean;
  isGroupEnabled: boolean;
  isDescriptionDisplayEnabled: boolean;
  isImageDisplayEnabled: boolean;
  isQuantitySelectionEnabled: boolean;
  isWeightStrategyEnabled: boolean;
  posType: 'SINGLE' | 'MULTIPLE' | null;
  posName: string | null;
  stepProduct: StepProductAttributes[];
}
```

##### StepProductAttributes

```typescript
interface StepProductAttributes {
  productId: number;
  step_product: {
    price: number | null;       // in cents; overrides the product price
                                // (forced to null for CROSS_SELLING steps)
    position: number;
    exceedPrice: number | null; // price applied beyond the free quota
    maxProductQuantity: number | null;
    groupName: string | null;
    weight: number | null;
    posDefaultProductQuantity: number | null;
    posMinProductQuantity: number | null;
    posIsSelectedByDefault: boolean;
    posIsPriceModificationEnabled: boolean;
    posIsQuantityModificationEnabled: boolean;
  };
}
```

### SKU Management

SKUs (Stock Keeping Units) are brand-level identifiers used to map internal product IDs to external references. Each SKU can carry per-consumption-mode VAT rates (sit-in, take-away, delivery, drive).

### `GET /brands/{brandId}/sku` - List SKUs

Retrieve all SKUs for a given brand.

#### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| brandId | integer | Yes | The ID of the brand. |

#### Response

```json
{
  "status": 200,
  "code": "sku_list_success",
  "message": "Sku list linked to this brand has been found",
  "data": [
    {
      "skuId": 101,
      "brandId": 3325,
      "value": "BURGER-001",
      "deliveryVat": 1000,
      "driveVat": 1000,
      "takeAwayVat": 550,
      "sitInVat": 1000
    }
  ]
}
```

##### Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

| Property | Type | Example | Description |
| --- | --- | --- | --- |
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "sku\_list\_success" | Machine-readable application code for the result. |
| message | string | "Sku list linked to this brand has been found" | Human-readable result message. Do not use this value for program logic. |
| data | array | \[…\] | Endpoint-specific response payload. |
| data\[\] | object | {…} | Endpoint-specific response payload. |
| data\[\].skuId | integer | 101 | Identifier of the associated sku. |
| data\[\].brandId | integer | 3325 | Identifier of the brand. |
| data\[\].value | string | "BURGER-001" | The value value. |
| data\[\].deliveryVat | integer | 1000 | The delivery vat value. |
| data\[\].driveVat | integer | 1000 | The drive vat value. |
| data\[\].takeAwayVat | integer | 550 | The take away vat value. |
| data\[\].sitInVat | integer | 1000 | The sit in vat value. |

### `POST /brands/{brandId}/sku` - Create SKU

Create a new SKU for a brand. VAT rates default to 1000 (10%) if not specified.

#### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| brandId | integer | Yes | The ID of the brand. |

#### Request Body

```json
{
  "value": "DRINK-042",
  "deliveryVat": 550,
  "driveVat": 550,
  "takeAwayVat": 550,
  "sitInVat": 1000
}
```

##### Request Body Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

| Property | Type | Example | Description |
| --- | --- | --- | --- |
| value | string | "DRINK-042" | The value value. |
| deliveryVat | integer | 550 | The delivery vat value. |
| driveVat | integer | 550 | The drive vat value. |
| takeAwayVat | integer | 550 | The take away vat value. |
| sitInVat | integer | 1000 | The sit in vat value. |

#### Response

```json
{
  "status": 200,
  "code": "sku_created",
  "message": "The SKU has been created",
  "data": {
    "skuId": 102,
    "brandId": 3325,
    "value": "DRINK-042",
    "deliveryVat": 550,
    "sitInVat": 1000
  }
}
```

##### Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

| Property | Type | Example | Description |
| --- | --- | --- | --- |
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "sku\_created" | Machine-readable application code for the result. |
| message | string | "The SKU has been created" | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.skuId | integer | 102 | Identifier of the associated sku. |
| data.brandId | integer | 3325 | Identifier of the brand. |
| data.value | string | "DRINK-042" | The value value. |
| data.deliveryVat | integer | 550 | The delivery vat value. |
| data.sitInVat | integer | 1000 | The sit in vat value. |

### `PATCH /brands/{brandId}/skus/{skuId}` - Update SKU

Update the value of an existing SKU.

#### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| brandId | integer | Yes | The ID of the brand. |
| skuId | integer | Yes | The ID of the SKU to update. |

#### Request Body

```json
{
  "value": "DRINK-042-V2"
}
```

##### Request Body Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

| Property | Type | Example | Description |
| --- | --- | --- | --- |
| value | string | "DRINK-042-V2" | The value value. |

#### Response

```json
{
  "status": 200,
  "code": "sku_updated",
  "message": "The SKU has been updated",
  "data": {
    "skuId": 102,
    "value": "DRINK-042-V2"
  }
}
```

##### Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

| Property | Type | Example | Description |
| --- | --- | --- | --- |
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "sku\_updated" | Machine-readable application code for the result. |
| message | string | "The SKU has been updated" | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.skuId | integer | 102 | Identifier of the associated sku. |
| data.value | string | "DRINK-042-V2" | The value value. |

### `POST /brands/{brandId}/skus/bulk_delete` - Bulk Delete Unused SKUs

Hard-deletes SKU rows only when no live product of the brand still references them. Use either an explicit list of up to 200 IDs or orphansOnly=true, never both. Referenced rows are preserved and reported in skippedInUse; IDs outside the brand are indistinguishable from missing IDs and are reported in notFoundSkuIds. Requires the CAN\_CREATE\_SKU access flag.

#### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| brandId | integer | Yes | Brand that owns every SKU to inspect and delete. |

#### Request Body

```json
{
  "skuIds": [
    101,
    102
  ]
}
```

##### Request Body Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

| Property | Type | Required | Example | Description |
| --- | --- | --- | --- | --- |
| skuIds | array | Not specified | \[…\] | List of sku ids entries. |
| skuIds\[\] | integer | No | 101 | Explicit SKU IDs to inspect. Supply between 1 and 200 IDs; mutually exclusive with orphansOnly. |
| orphansOnly | boolean | No | Not provided | Must be true when supplied. Deletes every unreferenced SKU in the brand; mutually exclusive with skuIds. |

#### Response

```json
{
  "status": 200,
  "code": "skus_deleted",
  "message": "The SKUs have been deleted",
  "data": {
    "brandId": 3325,
    "deleted": [
      {
        "skuId": 101,
        "value": "BURGER-001",
        "deliveryVat": 1000,
        "driveVat": 1000,
        "takeAwayVat": 1000,
        "sitInVat": 1000
      }
    ],
    "skippedInUse": [
      {
        "skuId": 102,
        "value": "DRINK-042",
        "productCount": 3
      }
    ],
    "notFoundSkuIds": [
      999
    ]
  }
}
```

##### Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

| Property | Type | Example | Description |
| --- | --- | --- | --- |
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "skus\_deleted" | Machine-readable application code for the result. |
| message | string | "The SKUs have been deleted" | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.brandId | integer | 3325 | Identifier of the brand. |
| data.deleted | array | \[…\] | List of deleted entries. |
| data.deleted\[\] | object | {…} | Object containing deleted fields. |
| data.deleted\[\].skuId | integer | 101 | Identifier of the associated sku. |
| data.deleted\[\].value | string | "BURGER-001" | The value value. |
| data.deleted\[\].deliveryVat | integer | 1000 | The delivery vat value. |
| data.deleted\[\].driveVat | integer | 1000 | The drive vat value. |
| data.deleted\[\].takeAwayVat | integer | 1000 | The take away vat value. |
| data.deleted\[\].sitInVat | integer | 1000 | The sit in vat value. |
| data.skippedInUse | array | \[…\] | List of skipped in use entries. |
| data.skippedInUse\[\] | object | {…} | Object containing skipped in use fields. |
| data.skippedInUse\[\].skuId | integer | 102 | Identifier of the associated sku. |
| data.skippedInUse\[\].value | string | "DRINK-042" | The value value. |
| data.skippedInUse\[\].productCount | integer | 3 | The product count value. |
| data.notFoundSkuIds | array | \[…\] | List of not found sku ids entries. |
| data.notFoundSkuIds\[\] | integer | 999 | The not found sku ids value. |

#### Data Models

##### SkuAttributes

```typescript
interface SkuAttributes {
  skuId: number;
  brandId: number;
  value: string;        // The SKU identifier string
  deliveryVat: number;  // VAT rate for delivery (e.g. 1000 = 10%)
  driveVat: number;     // VAT rate for drive-through
  takeAwayVat: number;  // VAT rate for take-away
  sitInVat: number;     // VAT rate for sit-in
}
```
