# Menus

A menu is the root container of the catalog. It belongs to a brand or to a single restaurant, holds the whole product tree (categories, products, steps), and is assigned to sales channels (kiosk, web, POS, marketplaces) and POS devices. Changes only become visible to customers once the menu is **published** on each channel.

### Managing Menus

### `GET /v2/menus` - List Menus V2

Retrieve all menus for a brand or restaurant. At least one of brandId or restaurantId parameter is required.

#### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| brandId | integer | No | The ID of the brand. |
| restaurantId | integer | No | The ID of the restaurant. |

#### Response

```json
{
  "status": 200,
  "code": "menu_succeed",
  "message": "You can access these menus.",
  "data": [
    {
      "menuId": 106477,
      "name": "Main Menu",
      "channels": [
        {
          "channelId": 2,
          "name": "web"
        }
      ],
      "posDevices": [
        {
          "posDeviceId": 8063,
          "deviceName": "Main Register"
        }
      ],
      "isActive": 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 | "menu\_succeed" | Machine-readable application code for the result. |
| message | string | "You can access these menus." | 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\[\].menuId | integer | 106477 | Identifier of the menu. |
| data\[\].name | string | "Main Menu" | The name value. |
| data\[\].channels | array | \[…\] | List of channels entries. |
| data\[\].channels\[\] | object | {…} | Object containing channels fields. |
| data\[\].channels\[\].channelId | integer | 2 | Identifier of the associated channel. |
| data\[\].channels\[\].name | string | "web" | The name value. |
| data\[\].posDevices | array | \[…\] | List of pos devices entries. |
| data\[\].posDevices\[\] | object | {…} | Object containing pos devices fields. |
| data\[\].posDevices\[\].posDeviceId | integer | 8063 | Identifier of the associated pos device. |
| data\[\].posDevices\[\].deviceName | string | "Main Register" | The device name value. |
| data\[\].isActive | boolean | true | Whether this resource is active. |

### `POST /v2/menus` - Create Menu (V2)

Create a new menu with specific channel and device assignments. You can import an existing structure using the "import" field. A menu created on the kiosk channel deactivates the restaurant up-selling pages served by the previous kiosk menu; deactivatedUpsellingPagesCount reports how many.

#### Request Body

```json
{
  "name": "Summer Menu",
  "brandId": 3325,
  "restaurantId": 6801,
  "channels": [
    1,
    2,
    5
  ],
  "posDevices": [
    8063
  ],
  "isSyncFromPosEnabled": false,
  "import": {
    "categories": {},
    "products": {},
    "steps": {}
  }
}
```

##### 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 | "Summer Menu" | The name value. |
| brandId | integer | 3325 | Identifier of the brand. |
| restaurantId | integer | 6801 | Identifier of the restaurant. |
| channels | array | \[…\] | List of channels entries. |
| channels\[\] | integer | 1 | The channels value. |
| posDevices | array | \[…\] | List of pos devices entries. |
| posDevices\[\] | integer | 8063 | The pos devices value. |
| isSyncFromPosEnabled | boolean | false | Whether sync from pos enabled is enabled or applies. |
| import | object | {…} | Object containing import fields. |
| import.categories | object | {…} | Object containing categories fields. |
| import.products | object | {…} | Object containing products fields. |
| import.steps | object | {…} | Object containing steps fields. |

#### Response

```json
{
  "status": 201,
  "code": "menu_v2_creation_succeed",
  "message": "Menu has been successfully created.",
  "data": {
    "menuId": 107001,
    "name": "Summer Menu",
    "is_enabled": true,
    "created_at": "2025-11-28T10:00:00.000Z",
    "deactivatedUpsellingPagesCount": 0
  }
}
```

##### 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 | 201 | HTTP status code returned by the API. |
| code | string | "menu\_v2\_creation\_succeed" | Machine-readable application code for the result. |
| message | string | "Menu has been successfully created." | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.menuId | integer | 107001 | Identifier of the menu. |
| data.name | string | "Summer Menu" | The name value. |
| data.is\_enabled | boolean | true | Whether enabled is enabled or applies. |
| data.created\_at | string | "2025-11-28T10:00:00.000Z" | Timestamp when this resource was created. |
| data.deactivatedUpsellingPagesCount | integer | 0 | The deactivated upselling pages count value. |

### `PUT /v2/menus/{menuId}` - Update Menu (V2)

Update an existing menu. Taking the menu off the kiosk channel, or putting it on, deactivates the restaurant up-selling pages served by the kiosk menu; deactivatedUpsellingPagesCount reports how many.

#### Parameters

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

#### Request Body

```json
{
  "name": "Updated Menu Name",
  "channels": [
    2
  ],
  "isSyncFromPosEnabled": 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 | "Updated Menu Name" | The name value. |
| channels | array | \[…\] | List of channels entries. |
| channels\[\] | integer | 2 | The channels value. |
| isSyncFromPosEnabled | boolean | true | Whether sync from pos enabled is enabled or applies. |

#### Response

```json
{
  "status": 200,
  "code": "menu_update_succeed",
  "message": "Menu has been successfully updated.",
  "data": {
    "menuId": 106477,
    "name": "Updated Menu Name",
    "updated_at": "2025-11-29T10:00:00.000Z",
    "deactivatedUpsellingPagesCount": 0
  }
}
```

##### 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 | "Updated Menu Name" | The name value. |
| data.updated\_at | string | "2025-11-29T10:00:00.000Z" | Timestamp when this resource was last updated. |
| data.deactivatedUpsellingPagesCount | integer | 0 | The deactivated upselling pages count value. |

### `POST /v2/menus/{menuId}/channels/{channelId}/publish` - Publish Menu

Publish a specific menu to a sales channel.

#### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| menuId | integer | Yes | The unique identifier of the menu. |
| channelId | integer | Yes | The unique identifier of the channel (e.g., 1 for Kiosk, 2 for Web). |

#### Response

```json
{
  "status": 200,
  "code": "menu_publish_succeed",
  "message": "Menu has been successfully published.",
  "data": 106477
}
```

##### 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\_publish\_succeed" | Machine-readable application code for the result. |
| message | string | "Menu has been successfully published." | Human-readable result message. Do not use this value for program logic. |
| data | integer | 106477 | Endpoint-specific response payload. |

### `GET /v2/menus/operations` - Get Menu Operations

Retrieve expected menu imports and operations status.

#### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| brandId | integer | No | The ID of the brand. |
| menuId | integer | No | The ID of the menu. |
| restaurantId | integer | No | The ID of the restaurant. |
| limit | integer | No | Max results (default 10). |
| offset | integer | No | Results to skip (default 0). |
| sort | string | No | Sort order (ASC/DESC). |

#### Response

```json
{
  "status": 200,
  "code": "menu_operations_found",
  "message": "Menu operations have been successfully found.",
  "data": [
    {
      "menuOperationId": 12345,
      "name": "Import Menu",
      "status": "COMPLETED",
      "type": "IMPORT",
      "createdAt": "2025-11-28T10:00:00.000Z",
      "userId": 104848
    }
  ]
}
```

##### 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\_operations\_found" | Machine-readable application code for the result. |
| message | string | "Menu operations have been successfully 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\[\].menuOperationId | integer | 12345 | Identifier of the associated menu operation. |
| data\[\].name | string | "Import Menu" | The name value. |
| data\[\].status | string | "COMPLETED" | HTTP status code returned by the API. |
| data\[\].type | string | "IMPORT" | The type value. |
| data\[\].createdAt | string | "2025-11-28T10:00:00.000Z" | Timestamp when this resource was created. |
| data\[\].userId | integer | 104848 | Identifier of the user. |

### Classic Menu Endpoints

These established menu routes remain available alongside the V2 API. They require an authenticated Admin, Brand, or Restaurant user; creation and updates additionally require the relevant menu access flag.

### `GET /menus` - List Menus

Lists menus owned by one accessible brand or restaurant. At least one scope is required. When both are supplied, the current implementation resolves the brand scope.

#### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| brandId | integer | No | Brand scope. Required when restaurantId is omitted. |
| restaurantId | integer | No | Restaurant scope. Required when brandId is omitted. |

#### Response

```json
{
  "status": 200,
  "code": "menu_succeed",
  "message": "You can access these menus.",
  "data": [
    {
      "menuId": 106477,
      "name": "Main Menu",
      "brandId": 3325,
      "restaurantId": null,
      "imageId": 1001,
      "is_enabled": true,
      "isSyncFromPosEnabled": false,
      "hasInitModalBeenShowed": false,
      "created_at": "2026-06-01T08:30:00.000Z",
      "updated_at": "2026-06-02T10:15:00.000Z",
      "channels": [
        {
          "menuId": 106477,
          "channelId": 2,
          "created_at": "2026-06-01T08:30:00.000Z",
          "updated_at": "2026-06-01T08:30:00.000Z"
        }
      ],
      "isActive": 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 | "menu\_succeed" | Machine-readable application code for the result. |
| message | string | "You can access these menus." | 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\[\].menuId | integer | 106477 | Identifier of the menu. |
| data\[\].name | string | "Main Menu" | The name value. |
| data\[\].brandId | integer | 3325 | Identifier of the brand. |
| data\[\].restaurantId | null | null | Identifier of the restaurant. |
| data\[\].imageId | integer | 1001 | Identifier of the associated image. |
| data\[\].is\_enabled | boolean | true | Whether enabled is enabled or applies. |
| data\[\].isSyncFromPosEnabled | boolean | false | Whether sync from pos enabled is enabled or applies. |
| data\[\].hasInitModalBeenShowed | boolean | false | Whether init modal been showed is enabled or applies. |
| data\[\].created\_at | string | "2026-06-01T08:30:00.000Z" | Timestamp when this resource was created. |
| data\[\].updated\_at | string | "2026-06-02T10:15:00.000Z" | Timestamp when this resource was last updated. |
| data\[\].channels | array | \[…\] | List of channels entries. |
| data\[\].channels\[\] | object | {…} | Object containing channels fields. |
| data\[\].channels\[\].menuId | integer | 106477 | Identifier of the menu. |
| data\[\].channels\[\].channelId | integer | 2 | Identifier of the associated channel. |
| data\[\].channels\[\].created\_at | string | "2026-06-01T08:30:00.000Z" | Timestamp when this resource was created. |
| data\[\].channels\[\].updated\_at | string | "2026-06-01T08:30:00.000Z" | Timestamp when this resource was last updated. |
| data\[\].isActive | boolean | true | Whether this resource is active. |

### `POST /menus` - Create Menu

Creates a menu for a brand or restaurant. The server permits a long-running import window of up to 15 minutes and invalidates the corresponding web-ordering cache after creation.

#### Request Body

```json
{
  "name": "Summer Menu",
  "brandId": 3325,
  "channels": [
    2,
    5
  ],
  "imageId": 1001,
  "posDevices": [
    8063
  ],
  "nayaxDevices": [],
  "sourcePosId": 8063
}
```

##### 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 |
| --- | --- | --- | --- | --- |
| name | string | Yes | "Summer Menu" | Menu display name. |
| brandId | integer | No | 3325 | Brand owner. Supply one of brandId or restaurantId. |
| channels | array | Not specified | \[…\] | List of channels entries. |
| channels\[\] | integer | Yes | 2 | Sales channel identifiers assigned to the new menu. |
| imageId | integer | No | 1001 | Optional image-library identifier. |
| posDevices | array | Not specified | \[…\] | List of pos devices entries. |
| posDevices\[\] | integer | No | 8063 | Optional POS device identifiers assigned to the menu. |
| nayaxDevices | array | Not specified | \[\] | List of nayax devices entries. |
| sourcePosId | integer | No | 8063 | Optional source POS identifier used by the import flow. |
| restaurantId | integer | No | Not provided | Restaurant owner. Supply one of brandId or restaurantId. |
| nayaxDevices\[\] | integer | No | Not provided | Optional Nayax device identifiers assigned to the menu. |
| import | object | No | Not provided | Optional full MenuImportDTO snapshot. Its categories, products, steps, tags, relations, images, layout and POS data are imported into the new menu. |

#### Response

```json
{
  "status": 200,
  "code": "menu_create_succeed",
  "message": "Menu has been successfully created.",
  "data": {
    "menuId": 107001,
    "name": "Summer Menu",
    "brandId": 3325,
    "restaurantId": null,
    "imageId": 1001,
    "is_enabled": true,
    "isSyncFromPosEnabled": false,
    "hasInitModalBeenShowed": false,
    "created_at": "2026-06-02T10:15:00.000Z",
    "updated_at": "2026-06-02T10:15:00.000Z",
    "channels": [
      {
        "channelId": 2
      },
      {
        "channelId": 5
      }
    ],
    "posDevices": [
      {
        "posDeviceId": 8063,
        "deviceName": "Main Register"
      }
    ],
    "moduleMarketplaces": []
  }
}
```

##### 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\_create\_succeed" | Machine-readable application code for the result. |
| message | string | "Menu has been successfully created." | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.menuId | integer | 107001 | Identifier of the menu. |
| data.name | string | "Summer Menu" | The name value. |
| data.brandId | integer | 3325 | Identifier of the brand. |
| data.restaurantId | null | null | Identifier of the restaurant. |
| data.imageId | integer | 1001 | Identifier of the associated image. |
| data.is\_enabled | boolean | true | Whether enabled is enabled or applies. |
| data.isSyncFromPosEnabled | boolean | false | Whether sync from pos enabled is enabled or applies. |
| data.hasInitModalBeenShowed | boolean | false | Whether init modal been showed is enabled or applies. |
| data.created\_at | string | "2026-06-02T10:15:00.000Z" | Timestamp when this resource was created. |
| data.updated\_at | string | "2026-06-02T10:15:00.000Z" | Timestamp when this resource was last updated. |
| data.channels | array | \[…\] | List of channels entries. |
| data.channels\[\] | object | {…} | Object containing channels fields. |
| data.channels\[\].channelId | integer | 2 | Identifier of the associated channel. |
| data.posDevices | array | \[…\] | List of pos devices entries. |
| data.posDevices\[\] | object | {…} | Object containing pos devices fields. |
| data.posDevices\[\].posDeviceId | integer | 8063 | Identifier of the associated pos device. |
| data.posDevices\[\].deviceName | string | "Main Register" | The device name value. |
| data.moduleMarketplaces | array | \[\] | List of module marketplaces entries. |

### `POST /menus/import` - Import a Menu File

Imports a menu from a single multipart file. Send the file in the file field and pass the target scope in the query string. name and one target scope are required; channels is a numeric array encoded in the query string.

#### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| name | string | Yes | Name assigned to the imported menu. |
| brandId | integer | No | Target brand. Supply one of brandId or restaurantId. |
| restaurantId | integer | No | Target restaurant. Supply one of brandId or restaurantId. |
| channels | array&lt;integer&gt; | No | Numeric channel IDs, encoded as an array in the query string. |

#### Request Body

```json
multipart/form-data: file=[binary menu import file]
```

##### 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 |
| --- | --- | --- | --- | --- |
| file | binary | Yes | \[binary menu import file\] | The single menu import file. |

#### Response

```json
{
  "status": 201,
  "code": "menu_import_creation_succeed",
  "message": "The menu has been successfully imported.",
  "data": {
    "menuId": 107002,
    "name": "Imported Summer Menu",
    "brandId": 3325,
    "restaurantId": null,
    "imageId": null,
    "is_enabled": true,
    "isSyncFromPosEnabled": false,
    "hasInitModalBeenShowed": false,
    "created_at": "2026-06-02T10:20:00.000Z",
    "updated_at": "2026-06-02T10:20:00.000Z",
    "channels": [
      {
        "channelId": 2
      }
    ],
    "posDevices": [],
    "moduleMarketplaces": []
  }
}
```

##### 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 | 201 | HTTP status code returned by the API. |
| code | string | "menu\_import\_creation\_succeed" | Machine-readable application code for the result. |
| message | string | "The menu has been successfully imported." | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.menuId | integer | 107002 | Identifier of the menu. |
| data.name | string | "Imported Summer Menu" | The name value. |
| data.brandId | integer | 3325 | Identifier of the brand. |
| data.restaurantId | null | null | Identifier of the restaurant. |
| data.imageId | null | null | Identifier of the associated image. |
| data.is\_enabled | boolean | true | Whether enabled is enabled or applies. |
| data.isSyncFromPosEnabled | boolean | false | Whether sync from pos enabled is enabled or applies. |
| data.hasInitModalBeenShowed | boolean | false | Whether init modal been showed is enabled or applies. |
| data.created\_at | string | "2026-06-02T10:20:00.000Z" | Timestamp when this resource was created. |
| data.updated\_at | string | "2026-06-02T10:20:00.000Z" | Timestamp when this resource was last updated. |
| data.channels | array | \[…\] | List of channels entries. |
| data.channels\[\] | object | {…} | Object containing channels fields. |
| data.channels\[\].channelId | integer | 2 | Identifier of the associated channel. |
| data.posDevices | array | \[\] | List of pos devices entries. |
| data.moduleMarketplaces | array | \[\] | List of module marketplaces entries. |

### `GET /menus/restaurants` - Get Eligible Menus by Restaurant

Returns the menus available to each requested restaurant for at least one requested channel. It is intended for propagation and supports an optional POS-device filter.

#### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| restaurantIds | string | Yes | Comma-separated positive restaurant IDs, for example 6801,6802. |
| channelIds | string | Yes | Comma-separated channel IDs. A menu is returned when it matches at least one requested channel. |
| posDeviceId | integer | No | Optional POS device; when supplied, only menus linked to that device are returned. |

#### Response

```json
{
  "status": 200,
  "code": "menus_by_restaurants_succeed",
  "message": "Menus by restaurants retrieved successfully.",
  "data": [
    {
      "restaurantId": 6801,
      "menus": [
        {
          "menuId": 106477,
          "name": "Main Menu"
        }
      ]
    },
    {
      "restaurantId": 6802,
      "menus": []
    }
  ]
}
```

##### 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 | "menus\_by\_restaurants\_succeed" | Machine-readable application code for the result. |
| message | string | "Menus by restaurants retrieved successfully." | 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\[\].restaurantId | integer | 6801 | Identifier of the restaurant. |
| data\[\].menus | array | \[…\] | List of menus entries. |
| data\[\].menus\[\] | object | {…} | Object containing menus fields. |
| data\[\].menus\[\].menuId | integer | 106477 | Identifier of the menu. |
| data\[\].menus\[\].name | string | "Main Menu" | The name value. |

### `GET /menus/{menuId}/hash_map` - Get Full Menu Structure

Returns the complete menu tree through layout, categories, products, and steps in a compressed hash-map format. This is the primary endpoint used by front-ends to render the catalog. Image objects expose public metadata only; internal filesystem paths are omitted.

#### Parameters

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

#### Response

```json
{
  "status": 200,
  "code": "menu_hash_map_succeed",
  "message": "You can access these menus.",
  "data": {
    "layout": [
      30085
    ],
    "categories": {
      "30085": {
        "categoryId": 30085,
        "name": "Burgers",
        "products": [
          464462
        ]
      }
    },
    "products": {
      "464462": {
        "productId": 464462,
        "name": "Cheeseburger",
        "price": 1200,
        "steps": [
          554
        ]
      }
    },
    "steps": {
      "554": {
        "stepId": 554,
        "name": "Cooking",
        "products": [
          991,
          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 | "menu\_hash\_map\_succeed" | Machine-readable application code for the result. |
| message | string | "You can access these menus." | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.layout | array | \[…\] | List of layout entries. |
| data.layout\[\] | integer | 30085 | The layout value. |
| data.categories | object | {…} | Object containing categories fields. |
| data.categories.30085 | object | {…} | Object containing 30085 fields. |
| data.categories.30085.categoryId | integer | 30085 | Identifier of the category. |
| data.categories.30085.name | string | "Burgers" | The name value. |
| data.categories.30085.products | array | \[…\] | List of products entries. |
| data.categories.30085.products\[\] | integer | 464462 | The products value. |
| data.products | object | {…} | Object containing products fields. |
| data.products.464462 | object | {…} | Object containing 464462 fields. |
| data.products.464462.productId | integer | 464462 | Identifier of the product. |
| data.products.464462.name | string | "Cheeseburger" | The name value. |
| data.products.464462.price | integer | 1200 | The price value. |
| data.products.464462.steps | array | \[…\] | List of steps entries. |
| data.products.464462.steps\[\] | integer | 554 | The steps value. |
| data.steps | object | {…} | Object containing steps fields. |
| data.steps.554 | object | {…} | Object containing 554 fields. |
| data.steps.554.stepId | integer | 554 | Identifier of the associated step. |
| data.steps.554.name | string | "Cooking" | The name value. |
| data.steps.554.products | array | \[…\] | List of products entries. |
| data.steps.554.products\[\] | integer | 991 | The products value. |

### Menu Lifecycle

These endpoints manage the lifecycle of a single menu: read its configuration, export its full catalog, duplicate it to other restaurants, and delete it.

### `GET /menus/{menuId}` - Get Menu Configuration

Retrieve the full configuration of a menu: menu attributes, active channels (filtered to the channels visible to your user), associated POS devices, linked marketplace modules and cover image. This endpoint does NOT return the product catalog - use GET /menus/{menuId}/export or the /hash\_map endpoint for that.

#### Parameters

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

#### Response

```json
{
  "status": 200,
  "code": "menu_succeed",
  "message": "You can access these menus.",
  "data": {
    "menuId": 106477,
    "name": "Main Menu",
    "brandId": 3325,
    "restaurantId": 6801,
    "channels": [
      {
        "channelId": 2,
        "name": "web"
      }
    ],
    "posDevices": [
      {
        "posDeviceId": 8063,
        "deviceName": "Main Register"
      }
    ],
    "moduleMarketplaces": [],
    "isActive": true,
    "image": {
      "imageId": 1001,
      "fullUrl": "https://static.innovorder.fr/images/..."
    }
  }
}
```

##### 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\_succeed" | Machine-readable application code for the result. |
| message | string | "You can access these menus." | 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.brandId | integer | 3325 | Identifier of the brand. |
| data.restaurantId | integer | 6801 | Identifier of the restaurant. |
| data.channels | array | \[…\] | List of channels entries. |
| data.channels\[\] | object | {…} | Object containing channels fields. |
| data.channels\[\].channelId | integer | 2 | Identifier of the associated channel. |
| data.channels\[\].name | string | "web" | The name value. |
| data.posDevices | array | \[…\] | List of pos devices entries. |
| data.posDevices\[\] | object | {…} | Object containing pos devices fields. |
| data.posDevices\[\].posDeviceId | integer | 8063 | Identifier of the associated pos device. |
| data.posDevices\[\].deviceName | string | "Main Register" | The device name value. |
| data.moduleMarketplaces | array | \[\] | List of module marketplaces entries. |
| data.isActive | boolean | true | Whether this resource is active. |
| data.image | object | {…} | Object containing image fields. |
| data.image.imageId | integer | 1001 | Identifier of the associated image. |
| data.image.fullUrl | string | "https://static.innovorder.fr/images/..." | The full url value. |

##### Response Notes

-   `isActive` is derived: it is `true` when the menu has at least one active channel, it is not a stored attribute.
-   `image` is `{ imageId, fullUrl }`, or `null` if the menu references an image that no longer exists; the field is absent when the menu has no image.
-   Returns `404 data_not_found` if the menu does not exist or if its parent brand/restaurant cannot be found.

### `GET /menus/{menuId}/export` - Export Menu Catalog

Export the complete catalog snapshot of a menu in the flattened read-model format used by menu import and duplication: products, categories and steps hash maps, plus the POS layout items. Each product embeds a "pos" object (its POS-specific settings with the posLocalId) or null. Image objects retain their public URL for reimport, while internal filesystem paths are omitted.

#### Parameters

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

#### Response

```json
{
  "status": 200,
  "code": "menu_export_succeed",
  "message": "Menu has been successfully exported.",
  "data": {
    "products": {
      "464462": {
        "productId": 464462,
        "name": "Cheeseburger",
        "price": 1200,
        "pos": {
          "posLocalId": 789
        }
      }
    },
    "categories": {
      "30085": {
        "categoryId": 30085,
        "name": "Burgers"
      }
    },
    "steps": {
      "554": {
        "stepId": 554,
        "name": "Cooking Level"
      }
    },
    "posLayoutItems": []
  }
}
```

##### 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\_export\_succeed" | Machine-readable application code for the result. |
| message | string | "Menu has been successfully exported." | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.products | object | {…} | Object containing products fields. |
| data.products.464462 | object | {…} | Object containing 464462 fields. |
| data.products.464462.productId | integer | 464462 | Identifier of the product. |
| data.products.464462.name | string | "Cheeseburger" | The name value. |
| data.products.464462.price | integer | 1200 | The price value. |
| data.products.464462.pos | object | {…} | Object containing pos fields. |
| data.products.464462.pos.posLocalId | integer | 789 | Identifier of the associated pos local. |
| data.categories | object | {…} | Object containing categories fields. |
| data.categories.30085 | object | {…} | Object containing 30085 fields. |
| data.categories.30085.categoryId | integer | 30085 | Identifier of the category. |
| data.categories.30085.name | string | "Burgers" | The name value. |
| data.steps | object | {…} | Object containing steps fields. |
| data.steps.554 | object | {…} | Object containing 554 fields. |
| data.steps.554.stepId | integer | 554 | Identifier of the associated step. |
| data.steps.554.name | string | "Cooking Level" | The name value. |
| data.posLayoutItems | array | \[\] | List of pos layout items entries. |

### `PUT /menus/{menuId}` - Update Menu

Replaces the menu configuration and its channel, POS-device, and marketplace assignments. channels, posDevices, and moduleMarketplaces are full replacement lists; the caller needs the MENU\_EDIT access flag.

#### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| menuId | integer | Yes | Menu to update. |

#### Request Body

```json
{
  "name": "Updated Main Menu",
  "channels": [
    2,
    5
  ],
  "posDevices": [
    8063
  ],
  "moduleMarketplaces": [],
  "imageId": 1001,
  "isSyncFromPosEnabled": true,
  "hasInitModalBeenShowed": true,
  "externalId": "partner-menu-42"
}
```

##### 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 |
| --- | --- | --- | --- | --- |
| name | string | Yes | "Updated Main Menu" | Updated display name. |
| channels | array | Not specified | \[…\] | List of channels entries. |
| channels\[\] | integer | Yes | 2 | Complete replacement list of sales channel IDs. |
| posDevices | array | Not specified | \[…\] | List of pos devices entries. |
| posDevices\[\] | integer | Yes | 8063 | Complete replacement list of POS device IDs. |
| moduleMarketplaces | array | Not specified | \[\] | List of module marketplaces entries. |
| imageId | integer | No | 1001 | Image-library ID. Set to null to clear the image. |
| isSyncFromPosEnabled | boolean | No | true | Whether catalog synchronization from POS is enabled. |
| hasInitModalBeenShowed | boolean | No | true | Whether the menu initialization modal has already been acknowledged. |
| externalId | string | No | "partner-menu-42" | Optional external menu reference. It is stored through the external-ID mapping flow for restaurant menus. |
| moduleMarketplaces\[\] | string | Yes | Not provided | Complete replacement list of marketplace module IDs. |

#### Response

```json
{
  "status": 200,
  "code": "menu_update_succeed",
  "message": "Menu has been successfully updated.",
  "data": {
    "menuId": 106477,
    "name": "Updated Main Menu",
    "brandId": 3325,
    "restaurantId": 6801,
    "imageId": 1001,
    "is_enabled": true,
    "isSyncFromPosEnabled": true,
    "hasInitModalBeenShowed": true,
    "created_at": "2026-06-01T08:30:00.000Z",
    "updated_at": "2026-06-02T10:30:00.000Z",
    "channels": [
      {
        "channelId": 2
      },
      {
        "channelId": 5
      }
    ],
    "posDevices": [
      8063
    ],
    "moduleMarketplaces": []
  }
}
```

##### 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 | "Updated Main Menu" | The name value. |
| data.brandId | integer | 3325 | Identifier of the brand. |
| data.restaurantId | integer | 6801 | Identifier of the restaurant. |
| data.imageId | integer | 1001 | Identifier of the associated image. |
| data.is\_enabled | boolean | true | Whether enabled is enabled or applies. |
| data.isSyncFromPosEnabled | boolean | true | Whether sync from pos enabled is enabled or applies. |
| data.hasInitModalBeenShowed | boolean | true | Whether init modal been showed is enabled or applies. |
| data.created\_at | string | "2026-06-01T08:30:00.000Z" | Timestamp when this resource was created. |
| data.updated\_at | string | "2026-06-02T10:30:00.000Z" | Timestamp when this resource was last updated. |
| data.channels | array | \[…\] | List of channels entries. |
| data.channels\[\] | object | {…} | Object containing channels fields. |
| data.channels\[\].channelId | integer | 2 | Identifier of the associated channel. |
| data.posDevices | array | \[…\] | List of pos devices entries. |
| data.posDevices\[\] | integer | 8063 | The pos devices value. |
| data.moduleMarketplaces | array | \[\] | List of module marketplaces entries. |

Large Response

The export contains the entire catalog and can be very large. The `products`, `categories` and `steps` collections are **hash maps indexed by ID** (e.g. `products["464462"]`), not arrays - only `posLayoutItems` is an array.

### `GET /menus/{menuId}/products-export` - Export Products as Flat Rows

Exports every product in a menu as a flat JSON row for spreadsheet and partner synchronization use cases. Product, price, VAT, stock, category, tag, step, and scope columns are all present for every row.

#### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| menuId | integer | Yes | Menu whose products are exported. |

#### Response

```json
{
  "status": 200,
  "code": "products_succeed",
  "message": "Products found.",
  "data": [
    {
      "productId": 464462,
      "menuId": 106477,
      "sku": "BURGER-001",
      "barCode": "3760123456789",
      "billingCode": "BURGER",
      "restaurant": "Central Restaurant",
      "menuName": "Main Menu",
      "productName": "Cheeseburger",
      "ticketLabel": "Cheeseburger",
      "type": "Produit",
      "description": "Beef burger with cheese",
      "additionalInformation": "Contains gluten and milk",
      "tags": "Popular / Beef",
      "parentCategories": "Burgers",
      "directCategories": "Classic burgers",
      "claimOrder": 12,
      "priceTtc": 12,
      "contractPrice": 9.5,
      "vatDefault": 1,
      "vatSitIn": 1,
      "vatTakeAway": 1,
      "vatDelivery": 1,
      "subsidizable": "OUI",
      "admissible": "OUI",
      "discountable": "OUI",
      "currentQuantity": 24,
      "defaultQuantity": 30,
      "servingCount": 1,
      "isMenu": "NON",
      "belongsToSteps": "Choose a side (0)",
      "containsSteps": "",
      "alwaysAskForCustomization": "NON",
      "brandId": 3325,
      "restaurantId": 6801,
      "signature": "f1cb6b30-cfa3-4e4e-92c9-4ae1be615e81"
    }
  ]
}
```

##### 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 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\[\].productId | integer | 464462 | Identifier of the product. |
| data\[\].menuId | integer | 106477 | Identifier of the menu. |
| data\[\].sku | string | "BURGER-001" | The sku value. |
| data\[\].barCode | string | "3760123456789" | The bar code value. |
| data\[\].billingCode | string | "BURGER" | The billing code value. |
| data\[\].restaurant | string | "Central Restaurant" | The restaurant value. |
| data\[\].menuName | string | "Main Menu" | The menu name value. |
| data\[\].productName | string | "Cheeseburger" | The product name value. |
| data\[\].ticketLabel | string | "Cheeseburger" | The ticket label value. |
| data\[\].type | string | "Produit" | The type value. |
| data\[\].description | string | "Beef burger with cheese" | The description value. |
| data\[\].additionalInformation | string | "Contains gluten and milk" | The additional information value. |
| data\[\].tags | string | "Popular / Beef" | The tags value. |
| data\[\].parentCategories | string | "Burgers" | The parent categories value. |
| data\[\].directCategories | string | "Classic burgers" | The direct categories value. |
| data\[\].claimOrder | integer | 12 | The claim order value. |
| data\[\].priceTtc | integer | 12 | The price ttc value. |
| data\[\].contractPrice | number | 9.5 | The contract price value. |
| data\[\].vatDefault | integer | 1 | The vat default value. |
| data\[\].vatSitIn | integer | 1 | The vat sit in value. |
| data\[\].vatTakeAway | integer | 1 | The vat take away value. |
| data\[\].vatDelivery | integer | 1 | The vat delivery value. |
| data\[\].subsidizable | string | "OUI" | The subsidizable value. |
| data\[\].admissible | string | "OUI" | The admissible value. |
| data\[\].discountable | string | "OUI" | The discountable value. |
| data\[\].currentQuantity | integer | 24 | The current quantity value. |
| data\[\].defaultQuantity | integer | 30 | The default quantity value. |
| data\[\].servingCount | integer | 1 | The serving count value. |
| data\[\].isMenu | string | "NON" | Whether menu is enabled or applies. |
| data\[\].belongsToSteps | string | "Choose a side (0)" | The belongs to steps value. |
| data\[\].containsSteps | string | "" | The contains steps value. |
| data\[\].alwaysAskForCustomization | string | "NON" | The always ask for customization value. |
| data\[\].brandId | integer | 3325 | Identifier of the brand. |
| data\[\].restaurantId | integer | 6801 | Identifier of the restaurant. |
| data\[\].signature | string | "f1cb6b30-cfa3-4e4e-92c9-4ae1be615e81" | The signature value. |

### `POST /menus/{menuId}/duplicate` - Duplicate Menu

Duplicate an existing menu under a new name, to a brand or to one or several restaurants. The whole catalog (products, categories, steps, POS layout) is re-imported into the target menu(s). "name" is required, plus at least one of "restaurantId" (single target restaurant), "brandId" (brand-level menu) or "restaurantList" (several target restaurants) - otherwise 400 missing\_parameters. If "restaurantId" is provided, it takes precedence over "restaurantList". Requires the MENU\_CREATE\_OR\_DELETE access flag on your user.

#### Parameters

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

#### Request Body

```json
{
  "name": "Main Menu (Copy)",
  "restaurantList": [
    6801,
    6802
  ]
}
```

##### 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 | "Main Menu (Copy)" | The name value. |
| restaurantList | array | \[…\] | List of restaurant list entries. |
| restaurantList\[\] | integer | 6801 | The restaurant list value. |

#### Response

```json
{
  "status": 200,
  "code": "menu_update_succeed",
  "message": "Menu has been successfully updated.",
  "data": [
    {
      "menuId": 107002,
      "name": "Main Menu (Copy)",
      "restaurantId": 6801
    },
    {
      "menuId": 107003,
      "name": "Main Menu (Copy)",
      "restaurantId": 6802
    }
  ]
}
```

##### 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 | array | \[…\] | Endpoint-specific response payload. |
| data\[\] | object | {…} | Endpoint-specific response payload. |
| data\[\].menuId | integer | 107002 | Identifier of the menu. |
| data\[\].name | string | "Main Menu (Copy)" | The name value. |
| data\[\].restaurantId | integer | 6801 | Identifier of the restaurant. |

Duplicate Behavior

-   The **response shape depends on the request body**: a single menu object when `restaurantId` is provided, an array of created menus otherwise (one per restaurant of `restaurantList`, or a single element for `brandId`).
-   The response code is `menu_update_succeed` - there is no dedicated "duplicate" code.
-   Duplication is a long-running operation: the server allows up to **15 minutes**, so configure a large client-side timeout.
-   Returns `404 menu_not_found` if no usable target (`restaurantId`, `brandId` or `restaurantList`) can be resolved.

### `DELETE /menus/{menuId}` - Delete Menu

Delete a menu. This is a soft delete: the menu is disabled (is\_enabled set to false) and disappears from listings, but its data is kept. Requires the MENU\_CREATE\_OR\_DELETE access flag on your user. Menu caches (hash maps and web ordering initialization) are invalidated automatically.

#### Parameters

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

#### Response

```json
{
  "status": 200,
  "code": "menu_delete_succeed",
  "message": "Menu has been successfully deleted.",
  "data": {
    "menuId": 106477,
    "name": "Main Menu",
    "is_enabled": false
  }
}
```

##### 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\_delete\_succeed" | Machine-readable application code for the result. |
| message | string | "Menu has been successfully deleted." | 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.is\_enabled | boolean | false | Whether enabled is enabled or applies. |

Soft Delete Only

Deleting a menu never removes it physically: `is_enabled` is set to `false` and the menu no longer appears in listings. The returned `data` is the updated menu record.

#### Data Models

##### PostMenu Schema

```yaml
type: object
properties:
  name:
    type: string
  channels:
    type: array
    items: number
    description: 1=KIOSK, 2=WEB, 5=POS, 9=UBER_EATS, 10=DELIVEROO...
  posDevices:
    type: array
    items: number
  import:
    $ref: MenuImportDTO
  isSyncFromPosEnabled:
    type: boolean
  brandId:
    type: number
  restaurantId:
    type: number
```

##### MenuOperation

```yaml
type: object
properties:
  menuOperationId: integer
  name: string
  status: string
  type: string
  brandId: integer
  menuId: integer
  userId: integer
  createdAt: date-time
  updatedAt: date-time
```

##### MenuImportDTO

Used to populate the menu with existing items upon creation.

```yaml
type: object
properties:
  layout:
    type: array
    items: number
  categories:
    type: object
    additionalProperties: CategoryAttributes
  products:
    type: object
    additionalProperties: ProductAttributes
  steps:
    type: object
    additionalProperties: StepAttributes
```
