# Menu Layouts

Menu structure and organization

## GET /api/v1/menu-layouts

List menu layouts

List menu layouts with optional filtering by location, schedule, and channels

**Permissions:** 🔵 `menu_layouts.read`

Query parameters:
- `locationId` (number, optional) — Filter by location ID
- `page` (number, optional) — Page number
- `limit` (number, optional) — Items per page
- `availableOnly` (string, optional) — Filter by schedule availability (ISO timestamp or "true")
- `channels` (string, optional) — Filter by channels (comma-separated channel types)
- `templatesOnly` ("true" | "false", optional) — Filter to only return templates
- `excludeTemplates` ("true" | "false", optional) — Exclude templates from results
- `resolveReferences` ("true" | "false", optional) — Resolve linked template references in the context
- `excludeUnmapped` ("true" | "false", optional) — Drop product nodes whose item lacks a POS integration_resource_mappings row. Used by storefront to hide items that would silently fail at the POS. No-op when the location has no active POS integration.

Response 200 — Default Response

```ts
{
  status: "success"
  data: {
    layouts: Array<{
        id: string
        locationId: number
        context: { … }
        createdAt: string // date-time
        updatedAt?: string | null // date-time
        sortOrder?: number | null
        isTemplate?: boolean | null
        templateName?: string | null
        usageCount?: number
        scheduleRules?: unknown[]
        channelAvailability?: { … }
        checkoutSwitchEligible?: boolean
        isAvailableNow?: boolean
      }>
    pagination: {
      page: number
      limit: number
      total: number
      totalPages: number
    }
  }
  meta: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## POST /api/v1/menu-layouts

Create menu layout

Create a new menu layout

**Permissions:** 🟢 `menu_layouts.create`

Request body:

```ts
{
  locationId: number
  context: {
    id?: unknown // Editor-generated node ID (UUID, occasionally prefixed like `layout-<uuid>`). Not the menu layout row ID.
    type?: unknown // Node kind. Known values: `layout` (category/folder with children in `data`), `product` (product button), `reference` (l…
    title?: unknown // Display name shown on the button/category
    color?: unknown // Background color — hex code or `transparent`
    data?: unknown // Array of child nodes, each the same shape as this one — this is where the tree recurses. Populated on `layout` nodes an…
    gridWidth?: unknown // Grid columns this tile spans
    gridHeight?: unknown // Grid rows this tile spans
    gridRow?: unknown // Grid row position of this tile
    gridCol?: unknown // Grid column position of this tile
    gridCols?: unknown // Number of columns in THIS layout's own grid (2-6). Layout/root nodes only.
    hideTitle?: unknown // Hide the title in storefront top navigation, landing page and grid (top-level groups)
    hideImage?: unknown // Hide the product image and show the background color instead
    resetToRoot?: unknown // Navigate back to the root layout after adding a product from this layout
    fontSize?: unknown // Font size for `text` nodes: `default` or `large`
    storefrontStyle?: unknown // Storefront/kiosk rendering style for a category. Known values: `default_listing`, `small_card`, `large_card`, `card_car…
    productRef?: unknown // Inventory item ID (`items.id`) as a string — `product` nodes only. A few legacy nodes store it as a number, so read it …
    presetVariantItemIds?: unknown // Array of pre-configured variant item IDs (numbers) for a product card. When set, tapping the card auto-applies these va…
    referenceId?: unknown // Menu layout row ID (UUID) of the template being referenced — `reference` nodes only
    linked?: unknown // Boolean. true (or omitted) = live reference that follows template updates and is expanded by `resolveReferences=true`; …
    actionType?: unknown // POS action for `action` nodes. Known values: Discount, Quantity, Upsell, Close, Print, Move, Note, TableName, Home, Cou…
    quantityValue?: unknown // Preset quantity (number) for the Quantity action (e.g. 3 = x3); skips the calculator dialog
    coursingValue?: unknown // Preset course (number) for the Coursing action; when unset the action opens the course selector
    voucherCode?: unknown // Preset voucher code for the Voucher action; when unset the action opens the voucher picker
    voucherTitle?: unknown // Display-title snapshot for the preset voucher, captured at save time
    targetItemId?: unknown // Target inventory item ID (number) for the VariantFilter action
    customUrl?: unknown // URL opened in a new window by the CustomUrl action
    customUrlTitle?: unknown // Display label for the CustomUrl action button
    noteText?: unknown // Preset note text for the Note action; when unset the action opens the notes drawer
    reportScope?: unknown // SalesReport action — which sales the report may show: `costCenter` (the device's own, the default), `user` (the signed-…
    reportDateMode?: unknown // SalesReport action — `today` (the location's current business day) or `ask` (opens a date picker)
    reportPeriodMode?: unknown // SalesReport action — `day` (the whole business day, the default) or `shift` (one of the location's configured shift win…
    reportShiftId?: unknown // SalesReport action — pinned shift id for `reportPeriodMode: shift`; when unset the action asks. Only the id is stored: …
    targetPrinterGroupId?: unknown // StationMessage / SalesReport action — where the print goes: a preparation group id, the sentinel `__device__` (SalesRep…
    targetStationTitle?: unknown // Display-title snapshot of `targetPrinterGroupId`, captured at save time; clients prefer the live printer name. Absent f…
    isDynamicGroup?: unknown // Boolean. Layout node whose children are resolved live from inventory tags at render time; `data` stays empty
    dynamicTagKey?: unknown // Tag category key for a dynamic group, e.g. `CATEGORY_PRIMARY`, `CUSTOM`, `ALLERGEN`, `PREFERENCE`
    dynamicTagValue?: unknown // Tag value within `dynamicTagKey`, e.g. `DRINKS`
    scheduleRules?: unknown // Schedule rules for this layout node, or null — attached by the API on read, ignored on write. Same entries as the top-l…
    channelAvailability?: unknown // Channel availability for this layout node, or null — attached by the API on read, ignored on write. Keys: `byChannel`, …
    _isResolvedReference?: unknown // Boolean set by the API when `resolveReferences=true` expanded a reference node into this layout node
    _referenceId?: unknown // Template row ID a resolved reference was expanded from
    _originalReferenceNodeId?: unknown // Node ID of the original reference node
    _referenceNotFound?: unknown // Boolean set by the API when a reference points at a template that no longer exists
    actionBars?: unknown // Per-menu POS action bars (persist across sub-layout navigation). Root node only. Each side holds an array of menu layou…
    detachedLayouts?: unknown // Array of layouts not placed in the main grid, used only as targets for action-bar references or other links. Root node …
    actionBar?: unknown // Deprecated — use `actionBars`. Legacy single action bar (array of nodes).
    actionBarPosition?: unknown // Deprecated — use `actionBars`. Position of the legacy `actionBar`: left, right, top or bottom.
  }
  isTemplate?: boolean
  templateName?: string
}
```

Response 201 — Default Response

```ts
{
  status: "success"
  data: {
    id: string
    locationId: number
    context: {
      id?: unknown // Editor-generated node ID (UUID, occasionally prefixed like `layout-<uuid>`). Not the menu layout row ID.
      type?: unknown // Node kind. Known values: `layout` (category/folder with children in `data`), `product` (product button), `reference` (l…
      title?: unknown // Display name shown on the button/category
      color?: unknown // Background color — hex code or `transparent`
      data?: unknown // Array of child nodes, each the same shape as this one — this is where the tree recurses. Populated on `layout` nodes an…
      gridWidth?: unknown // Grid columns this tile spans
      gridHeight?: unknown // Grid rows this tile spans
      gridRow?: unknown // Grid row position of this tile
      gridCol?: unknown // Grid column position of this tile
      gridCols?: unknown // Number of columns in THIS layout's own grid (2-6). Layout/root nodes only.
      hideTitle?: unknown // Hide the title in storefront top navigation, landing page and grid (top-level groups)
      hideImage?: unknown // Hide the product image and show the background color instead
      resetToRoot?: unknown // Navigate back to the root layout after adding a product from this layout
      fontSize?: unknown // Font size for `text` nodes: `default` or `large`
      storefrontStyle?: unknown // Storefront/kiosk rendering style for a category. Known values: `default_listing`, `small_card`, `large_card`, `card_car…
      productRef?: unknown // Inventory item ID (`items.id`) as a string — `product` nodes only. A few legacy nodes store it as a number, so read it …
      presetVariantItemIds?: unknown // Array of pre-configured variant item IDs (numbers) for a product card. When set, tapping the card auto-applies these va…
      referenceId?: unknown // Menu layout row ID (UUID) of the template being referenced — `reference` nodes only
      linked?: unknown // Boolean. true (or omitted) = live reference that follows template updates and is expanded by `resolveReferences=true`; …
      actionType?: unknown // POS action for `action` nodes. Known values: Discount, Quantity, Upsell, Close, Print, Move, Note, TableName, Home, Cou…
      quantityValue?: unknown // Preset quantity (number) for the Quantity action (e.g. 3 = x3); skips the calculator dialog
      coursingValue?: unknown // Preset course (number) for the Coursing action; when unset the action opens the course selector
      voucherCode?: unknown // Preset voucher code for the Voucher action; when unset the action opens the voucher picker
      voucherTitle?: unknown // Display-title snapshot for the preset voucher, captured at save time
      targetItemId?: unknown // Target inventory item ID (number) for the VariantFilter action
      customUrl?: unknown // URL opened in a new window by the CustomUrl action
      customUrlTitle?: unknown // Display label for the CustomUrl action button
      noteText?: unknown // Preset note text for the Note action; when unset the action opens the notes drawer
      reportScope?: unknown // SalesReport action — which sales the report may show: `costCenter` (the device's own, the default), `user` (the signed-…
      reportDateMode?: unknown // SalesReport action — `today` (the location's current business day) or `ask` (opens a date picker)
      reportPeriodMode?: unknown // SalesReport action — `day` (the whole business day, the default) or `shift` (one of the location's configured shift win…
      reportShiftId?: unknown // SalesReport action — pinned shift id for `reportPeriodMode: shift`; when unset the action asks. Only the id is stored: …
      targetPrinterGroupId?: unknown // StationMessage / SalesReport action — where the print goes: a preparation group id, the sentinel `__device__` (SalesRep…
      targetStationTitle?: unknown // Display-title snapshot of `targetPrinterGroupId`, captured at save time; clients prefer the live printer name. Absent f…
      isDynamicGroup?: unknown // Boolean. Layout node whose children are resolved live from inventory tags at render time; `data` stays empty
      dynamicTagKey?: unknown // Tag category key for a dynamic group, e.g. `CATEGORY_PRIMARY`, `CUSTOM`, `ALLERGEN`, `PREFERENCE`
      dynamicTagValue?: unknown // Tag value within `dynamicTagKey`, e.g. `DRINKS`
      scheduleRules?: unknown // Schedule rules for this layout node, or null — attached by the API on read, ignored on write. Same entries as the top-l…
      channelAvailability?: unknown // Channel availability for this layout node, or null — attached by the API on read, ignored on write. Keys: `byChannel`, …
      _isResolvedReference?: unknown // Boolean set by the API when `resolveReferences=true` expanded a reference node into this layout node
      _referenceId?: unknown // Template row ID a resolved reference was expanded from
      _originalReferenceNodeId?: unknown // Node ID of the original reference node
      _referenceNotFound?: unknown // Boolean set by the API when a reference points at a template that no longer exists
      actionBars?: unknown // Per-menu POS action bars (persist across sub-layout navigation). Root node only. Each side holds an array of menu layou…
      detachedLayouts?: unknown // Array of layouts not placed in the main grid, used only as targets for action-bar references or other links. Root node …
      actionBar?: unknown // Deprecated — use `actionBars`. Legacy single action bar (array of nodes).
      actionBarPosition?: unknown // Deprecated — use `actionBars`. Position of the legacy `actionBar`: left, right, top or bottom.
    }
    createdAt: string // date-time
    updatedAt?: string | null // date-time
    sortOrder?: number | null
    isTemplate?: boolean | null
    templateName?: string | null
    usageCount?: number
    scheduleRules?: Array<{
        id?: string
        dayOfWeek?: number
        startTime?: string
        endTime?: string
        active?: boolean
      }>
    channelAvailability?: {
      byChannel?: {
        KIOSK?: boolean
        POS?: boolean
        ONLINE?: boolean
        TAKEAWAY?: boolean
        WOLT?: boolean
        FOODORA?: boolean
        UBER_EATS?: boolean
      }
      online?: boolean
      qr?: boolean
      kiosk?: boolean
      pos?: boolean
      wolt?: boolean
      foodora?: boolean
      uberEats?: boolean
    }
    checkoutSwitchEligible?: boolean
    isAvailableNow?: boolean
  }
  meta: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## GET /api/v1/menu-layouts/{id}

Get menu layout

Get a single menu layout by ID. Use resolveReferences=true to expand linked template references.

**Permissions:** 🔵 `menu_layouts.read`

Query parameters:
- `resolveReferences` ("true" | "false", optional) — Resolve linked template references in the context
- `excludeUnmapped` ("true" | "false", optional) — Drop product nodes whose item lacks a POS integration_resource_mappings row. No-op when the location has no active POS integration.

Path parameters:
- `id` (string, required) — Menu layout ID (UUID)

Response 200 — Default Response

```ts
{
  status: "success"
  data: {
    id: string
    locationId: number
    context: {
      id?: unknown // Editor-generated node ID (UUID, occasionally prefixed like `layout-<uuid>`). Not the menu layout row ID.
      type?: unknown // Node kind. Known values: `layout` (category/folder with children in `data`), `product` (product button), `reference` (l…
      title?: unknown // Display name shown on the button/category
      color?: unknown // Background color — hex code or `transparent`
      data?: unknown // Array of child nodes, each the same shape as this one — this is where the tree recurses. Populated on `layout` nodes an…
      gridWidth?: unknown // Grid columns this tile spans
      gridHeight?: unknown // Grid rows this tile spans
      gridRow?: unknown // Grid row position of this tile
      gridCol?: unknown // Grid column position of this tile
      gridCols?: unknown // Number of columns in THIS layout's own grid (2-6). Layout/root nodes only.
      hideTitle?: unknown // Hide the title in storefront top navigation, landing page and grid (top-level groups)
      hideImage?: unknown // Hide the product image and show the background color instead
      resetToRoot?: unknown // Navigate back to the root layout after adding a product from this layout
      fontSize?: unknown // Font size for `text` nodes: `default` or `large`
      storefrontStyle?: unknown // Storefront/kiosk rendering style for a category. Known values: `default_listing`, `small_card`, `large_card`, `card_car…
      productRef?: unknown // Inventory item ID (`items.id`) as a string — `product` nodes only. A few legacy nodes store it as a number, so read it …
      presetVariantItemIds?: unknown // Array of pre-configured variant item IDs (numbers) for a product card. When set, tapping the card auto-applies these va…
      referenceId?: unknown // Menu layout row ID (UUID) of the template being referenced — `reference` nodes only
      linked?: unknown // Boolean. true (or omitted) = live reference that follows template updates and is expanded by `resolveReferences=true`; …
      actionType?: unknown // POS action for `action` nodes. Known values: Discount, Quantity, Upsell, Close, Print, Move, Note, TableName, Home, Cou…
      quantityValue?: unknown // Preset quantity (number) for the Quantity action (e.g. 3 = x3); skips the calculator dialog
      coursingValue?: unknown // Preset course (number) for the Coursing action; when unset the action opens the course selector
      voucherCode?: unknown // Preset voucher code for the Voucher action; when unset the action opens the voucher picker
      voucherTitle?: unknown // Display-title snapshot for the preset voucher, captured at save time
      targetItemId?: unknown // Target inventory item ID (number) for the VariantFilter action
      customUrl?: unknown // URL opened in a new window by the CustomUrl action
      customUrlTitle?: unknown // Display label for the CustomUrl action button
      noteText?: unknown // Preset note text for the Note action; when unset the action opens the notes drawer
      reportScope?: unknown // SalesReport action — which sales the report may show: `costCenter` (the device's own, the default), `user` (the signed-…
      reportDateMode?: unknown // SalesReport action — `today` (the location's current business day) or `ask` (opens a date picker)
      reportPeriodMode?: unknown // SalesReport action — `day` (the whole business day, the default) or `shift` (one of the location's configured shift win…
      reportShiftId?: unknown // SalesReport action — pinned shift id for `reportPeriodMode: shift`; when unset the action asks. Only the id is stored: …
      targetPrinterGroupId?: unknown // StationMessage / SalesReport action — where the print goes: a preparation group id, the sentinel `__device__` (SalesRep…
      targetStationTitle?: unknown // Display-title snapshot of `targetPrinterGroupId`, captured at save time; clients prefer the live printer name. Absent f…
      isDynamicGroup?: unknown // Boolean. Layout node whose children are resolved live from inventory tags at render time; `data` stays empty
      dynamicTagKey?: unknown // Tag category key for a dynamic group, e.g. `CATEGORY_PRIMARY`, `CUSTOM`, `ALLERGEN`, `PREFERENCE`
      dynamicTagValue?: unknown // Tag value within `dynamicTagKey`, e.g. `DRINKS`
      scheduleRules?: unknown // Schedule rules for this layout node, or null — attached by the API on read, ignored on write. Same entries as the top-l…
      channelAvailability?: unknown // Channel availability for this layout node, or null — attached by the API on read, ignored on write. Keys: `byChannel`, …
      _isResolvedReference?: unknown // Boolean set by the API when `resolveReferences=true` expanded a reference node into this layout node
      _referenceId?: unknown // Template row ID a resolved reference was expanded from
      _originalReferenceNodeId?: unknown // Node ID of the original reference node
      _referenceNotFound?: unknown // Boolean set by the API when a reference points at a template that no longer exists
      actionBars?: unknown // Per-menu POS action bars (persist across sub-layout navigation). Root node only. Each side holds an array of menu layou…
      detachedLayouts?: unknown // Array of layouts not placed in the main grid, used only as targets for action-bar references or other links. Root node …
      actionBar?: unknown // Deprecated — use `actionBars`. Legacy single action bar (array of nodes).
      actionBarPosition?: unknown // Deprecated — use `actionBars`. Position of the legacy `actionBar`: left, right, top or bottom.
    }
    createdAt: string // date-time
    updatedAt?: string | null // date-time
    sortOrder?: number | null
    isTemplate?: boolean | null
    templateName?: string | null
    usageCount?: number
    scheduleRules?: Array<{
        id?: string
        dayOfWeek?: number
        startTime?: string
        endTime?: string
        active?: boolean
      }>
    channelAvailability?: {
      byChannel?: {
        KIOSK?: boolean
        POS?: boolean
        ONLINE?: boolean
        TAKEAWAY?: boolean
        WOLT?: boolean
        FOODORA?: boolean
        UBER_EATS?: boolean
      }
      online?: boolean
      qr?: boolean
      kiosk?: boolean
      pos?: boolean
      wolt?: boolean
      foodora?: boolean
      uberEats?: boolean
    }
    checkoutSwitchEligible?: boolean
    isAvailableNow?: boolean
  }
  meta: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## PUT /api/v1/menu-layouts/{id}

Update menu layout

Update an existing menu layout

**Permissions:** 🟠 `menu_layouts.update`

Path parameters:
- `id` (string, required) — Menu layout ID (UUID)

Request body:

```ts
{
  context?: {
    id?: unknown // Editor-generated node ID (UUID, occasionally prefixed like `layout-<uuid>`). Not the menu layout row ID.
    type?: unknown // Node kind. Known values: `layout` (category/folder with children in `data`), `product` (product button), `reference` (l…
    title?: unknown // Display name shown on the button/category
    color?: unknown // Background color — hex code or `transparent`
    data?: unknown // Array of child nodes, each the same shape as this one — this is where the tree recurses. Populated on `layout` nodes an…
    gridWidth?: unknown // Grid columns this tile spans
    gridHeight?: unknown // Grid rows this tile spans
    gridRow?: unknown // Grid row position of this tile
    gridCol?: unknown // Grid column position of this tile
    gridCols?: unknown // Number of columns in THIS layout's own grid (2-6). Layout/root nodes only.
    hideTitle?: unknown // Hide the title in storefront top navigation, landing page and grid (top-level groups)
    hideImage?: unknown // Hide the product image and show the background color instead
    resetToRoot?: unknown // Navigate back to the root layout after adding a product from this layout
    fontSize?: unknown // Font size for `text` nodes: `default` or `large`
    storefrontStyle?: unknown // Storefront/kiosk rendering style for a category. Known values: `default_listing`, `small_card`, `large_card`, `card_car…
    productRef?: unknown // Inventory item ID (`items.id`) as a string — `product` nodes only. A few legacy nodes store it as a number, so read it …
    presetVariantItemIds?: unknown // Array of pre-configured variant item IDs (numbers) for a product card. When set, tapping the card auto-applies these va…
    referenceId?: unknown // Menu layout row ID (UUID) of the template being referenced — `reference` nodes only
    linked?: unknown // Boolean. true (or omitted) = live reference that follows template updates and is expanded by `resolveReferences=true`; …
    actionType?: unknown // POS action for `action` nodes. Known values: Discount, Quantity, Upsell, Close, Print, Move, Note, TableName, Home, Cou…
    quantityValue?: unknown // Preset quantity (number) for the Quantity action (e.g. 3 = x3); skips the calculator dialog
    coursingValue?: unknown // Preset course (number) for the Coursing action; when unset the action opens the course selector
    voucherCode?: unknown // Preset voucher code for the Voucher action; when unset the action opens the voucher picker
    voucherTitle?: unknown // Display-title snapshot for the preset voucher, captured at save time
    targetItemId?: unknown // Target inventory item ID (number) for the VariantFilter action
    customUrl?: unknown // URL opened in a new window by the CustomUrl action
    customUrlTitle?: unknown // Display label for the CustomUrl action button
    noteText?: unknown // Preset note text for the Note action; when unset the action opens the notes drawer
    reportScope?: unknown // SalesReport action — which sales the report may show: `costCenter` (the device's own, the default), `user` (the signed-…
    reportDateMode?: unknown // SalesReport action — `today` (the location's current business day) or `ask` (opens a date picker)
    reportPeriodMode?: unknown // SalesReport action — `day` (the whole business day, the default) or `shift` (one of the location's configured shift win…
    reportShiftId?: unknown // SalesReport action — pinned shift id for `reportPeriodMode: shift`; when unset the action asks. Only the id is stored: …
    targetPrinterGroupId?: unknown // StationMessage / SalesReport action — where the print goes: a preparation group id, the sentinel `__device__` (SalesRep…
    targetStationTitle?: unknown // Display-title snapshot of `targetPrinterGroupId`, captured at save time; clients prefer the live printer name. Absent f…
    isDynamicGroup?: unknown // Boolean. Layout node whose children are resolved live from inventory tags at render time; `data` stays empty
    dynamicTagKey?: unknown // Tag category key for a dynamic group, e.g. `CATEGORY_PRIMARY`, `CUSTOM`, `ALLERGEN`, `PREFERENCE`
    dynamicTagValue?: unknown // Tag value within `dynamicTagKey`, e.g. `DRINKS`
    scheduleRules?: unknown // Schedule rules for this layout node, or null — attached by the API on read, ignored on write. Same entries as the top-l…
    channelAvailability?: unknown // Channel availability for this layout node, or null — attached by the API on read, ignored on write. Keys: `byChannel`, …
    _isResolvedReference?: unknown // Boolean set by the API when `resolveReferences=true` expanded a reference node into this layout node
    _referenceId?: unknown // Template row ID a resolved reference was expanded from
    _originalReferenceNodeId?: unknown // Node ID of the original reference node
    _referenceNotFound?: unknown // Boolean set by the API when a reference points at a template that no longer exists
    actionBars?: unknown // Per-menu POS action bars (persist across sub-layout navigation). Root node only. Each side holds an array of menu layou…
    detachedLayouts?: unknown // Array of layouts not placed in the main grid, used only as targets for action-bar references or other links. Root node …
    actionBar?: unknown // Deprecated — use `actionBars`. Legacy single action bar (array of nodes).
    actionBarPosition?: unknown // Deprecated — use `actionBars`. Position of the legacy `actionBar`: left, right, top or bottom.
  }
  isTemplate?: boolean
  templateName?: string
}
```

Response 200 — Default Response

```ts
{
  status: "success"
  data: {
    id: string
    locationId: number
    context: {
      id?: unknown // Editor-generated node ID (UUID, occasionally prefixed like `layout-<uuid>`). Not the menu layout row ID.
      type?: unknown // Node kind. Known values: `layout` (category/folder with children in `data`), `product` (product button), `reference` (l…
      title?: unknown // Display name shown on the button/category
      color?: unknown // Background color — hex code or `transparent`
      data?: unknown // Array of child nodes, each the same shape as this one — this is where the tree recurses. Populated on `layout` nodes an…
      gridWidth?: unknown // Grid columns this tile spans
      gridHeight?: unknown // Grid rows this tile spans
      gridRow?: unknown // Grid row position of this tile
      gridCol?: unknown // Grid column position of this tile
      gridCols?: unknown // Number of columns in THIS layout's own grid (2-6). Layout/root nodes only.
      hideTitle?: unknown // Hide the title in storefront top navigation, landing page and grid (top-level groups)
      hideImage?: unknown // Hide the product image and show the background color instead
      resetToRoot?: unknown // Navigate back to the root layout after adding a product from this layout
      fontSize?: unknown // Font size for `text` nodes: `default` or `large`
      storefrontStyle?: unknown // Storefront/kiosk rendering style for a category. Known values: `default_listing`, `small_card`, `large_card`, `card_car…
      productRef?: unknown // Inventory item ID (`items.id`) as a string — `product` nodes only. A few legacy nodes store it as a number, so read it …
      presetVariantItemIds?: unknown // Array of pre-configured variant item IDs (numbers) for a product card. When set, tapping the card auto-applies these va…
      referenceId?: unknown // Menu layout row ID (UUID) of the template being referenced — `reference` nodes only
      linked?: unknown // Boolean. true (or omitted) = live reference that follows template updates and is expanded by `resolveReferences=true`; …
      actionType?: unknown // POS action for `action` nodes. Known values: Discount, Quantity, Upsell, Close, Print, Move, Note, TableName, Home, Cou…
      quantityValue?: unknown // Preset quantity (number) for the Quantity action (e.g. 3 = x3); skips the calculator dialog
      coursingValue?: unknown // Preset course (number) for the Coursing action; when unset the action opens the course selector
      voucherCode?: unknown // Preset voucher code for the Voucher action; when unset the action opens the voucher picker
      voucherTitle?: unknown // Display-title snapshot for the preset voucher, captured at save time
      targetItemId?: unknown // Target inventory item ID (number) for the VariantFilter action
      customUrl?: unknown // URL opened in a new window by the CustomUrl action
      customUrlTitle?: unknown // Display label for the CustomUrl action button
      noteText?: unknown // Preset note text for the Note action; when unset the action opens the notes drawer
      reportScope?: unknown // SalesReport action — which sales the report may show: `costCenter` (the device's own, the default), `user` (the signed-…
      reportDateMode?: unknown // SalesReport action — `today` (the location's current business day) or `ask` (opens a date picker)
      reportPeriodMode?: unknown // SalesReport action — `day` (the whole business day, the default) or `shift` (one of the location's configured shift win…
      reportShiftId?: unknown // SalesReport action — pinned shift id for `reportPeriodMode: shift`; when unset the action asks. Only the id is stored: …
      targetPrinterGroupId?: unknown // StationMessage / SalesReport action — where the print goes: a preparation group id, the sentinel `__device__` (SalesRep…
      targetStationTitle?: unknown // Display-title snapshot of `targetPrinterGroupId`, captured at save time; clients prefer the live printer name. Absent f…
      isDynamicGroup?: unknown // Boolean. Layout node whose children are resolved live from inventory tags at render time; `data` stays empty
      dynamicTagKey?: unknown // Tag category key for a dynamic group, e.g. `CATEGORY_PRIMARY`, `CUSTOM`, `ALLERGEN`, `PREFERENCE`
      dynamicTagValue?: unknown // Tag value within `dynamicTagKey`, e.g. `DRINKS`
      scheduleRules?: unknown // Schedule rules for this layout node, or null — attached by the API on read, ignored on write. Same entries as the top-l…
      channelAvailability?: unknown // Channel availability for this layout node, or null — attached by the API on read, ignored on write. Keys: `byChannel`, …
      _isResolvedReference?: unknown // Boolean set by the API when `resolveReferences=true` expanded a reference node into this layout node
      _referenceId?: unknown // Template row ID a resolved reference was expanded from
      _originalReferenceNodeId?: unknown // Node ID of the original reference node
      _referenceNotFound?: unknown // Boolean set by the API when a reference points at a template that no longer exists
      actionBars?: unknown // Per-menu POS action bars (persist across sub-layout navigation). Root node only. Each side holds an array of menu layou…
      detachedLayouts?: unknown // Array of layouts not placed in the main grid, used only as targets for action-bar references or other links. Root node …
      actionBar?: unknown // Deprecated — use `actionBars`. Legacy single action bar (array of nodes).
      actionBarPosition?: unknown // Deprecated — use `actionBars`. Position of the legacy `actionBar`: left, right, top or bottom.
    }
    createdAt: string // date-time
    updatedAt?: string | null // date-time
    sortOrder?: number | null
    isTemplate?: boolean | null
    templateName?: string | null
    usageCount?: number
    scheduleRules?: Array<{
        id?: string
        dayOfWeek?: number
        startTime?: string
        endTime?: string
        active?: boolean
      }>
    channelAvailability?: {
      byChannel?: {
        KIOSK?: boolean
        POS?: boolean
        ONLINE?: boolean
        TAKEAWAY?: boolean
        WOLT?: boolean
        FOODORA?: boolean
        UBER_EATS?: boolean
      }
      online?: boolean
      qr?: boolean
      kiosk?: boolean
      pos?: boolean
      wolt?: boolean
      foodora?: boolean
      uberEats?: boolean
    }
    checkoutSwitchEligible?: boolean
    isAvailableNow?: boolean
  }
  meta: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## DELETE /api/v1/menu-layouts/{id}

Delete menu layout

Delete a menu layout. If the layout is a template that is being used, set force=true to delete anyway.

**Permissions:** 🔴 `menu_layouts.delete`

Query parameters:
- `force` ("true" | "false", optional) — Force delete a template even if it is being used in menus

Path parameters:
- `id` (string, required) — Menu layout ID (UUID)

Response 200 — Menu layout successfully deleted

```ts
{
  status?: "success"
  data?: {
    deleted?: boolean
  }
  meta?: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## POST /api/v1/menu-layouts/{id}/restore

Restore a deleted menu layout

Restore a soft-deleted menu layout under its original ID, which re-links the channel assignments and schedules that outlived the delete. Returns 404 if the layout does not exist or is not deleted.

**Permissions:** 🟠 `menu_layouts.update`

Path parameters:
- `id` (string, required) — Menu layout ID (UUID)

Response 200 — Default Response

```ts
{
  status: "success"
  data: {
    id: string
    locationId: number
    context: {
      id?: unknown // Editor-generated node ID (UUID, occasionally prefixed like `layout-<uuid>`). Not the menu layout row ID.
      type?: unknown // Node kind. Known values: `layout` (category/folder with children in `data`), `product` (product button), `reference` (l…
      title?: unknown // Display name shown on the button/category
      color?: unknown // Background color — hex code or `transparent`
      data?: unknown // Array of child nodes, each the same shape as this one — this is where the tree recurses. Populated on `layout` nodes an…
      gridWidth?: unknown // Grid columns this tile spans
      gridHeight?: unknown // Grid rows this tile spans
      gridRow?: unknown // Grid row position of this tile
      gridCol?: unknown // Grid column position of this tile
      gridCols?: unknown // Number of columns in THIS layout's own grid (2-6). Layout/root nodes only.
      hideTitle?: unknown // Hide the title in storefront top navigation, landing page and grid (top-level groups)
      hideImage?: unknown // Hide the product image and show the background color instead
      resetToRoot?: unknown // Navigate back to the root layout after adding a product from this layout
      fontSize?: unknown // Font size for `text` nodes: `default` or `large`
      storefrontStyle?: unknown // Storefront/kiosk rendering style for a category. Known values: `default_listing`, `small_card`, `large_card`, `card_car…
      productRef?: unknown // Inventory item ID (`items.id`) as a string — `product` nodes only. A few legacy nodes store it as a number, so read it …
      presetVariantItemIds?: unknown // Array of pre-configured variant item IDs (numbers) for a product card. When set, tapping the card auto-applies these va…
      referenceId?: unknown // Menu layout row ID (UUID) of the template being referenced — `reference` nodes only
      linked?: unknown // Boolean. true (or omitted) = live reference that follows template updates and is expanded by `resolveReferences=true`; …
      actionType?: unknown // POS action for `action` nodes. Known values: Discount, Quantity, Upsell, Close, Print, Move, Note, TableName, Home, Cou…
      quantityValue?: unknown // Preset quantity (number) for the Quantity action (e.g. 3 = x3); skips the calculator dialog
      coursingValue?: unknown // Preset course (number) for the Coursing action; when unset the action opens the course selector
      voucherCode?: unknown // Preset voucher code for the Voucher action; when unset the action opens the voucher picker
      voucherTitle?: unknown // Display-title snapshot for the preset voucher, captured at save time
      targetItemId?: unknown // Target inventory item ID (number) for the VariantFilter action
      customUrl?: unknown // URL opened in a new window by the CustomUrl action
      customUrlTitle?: unknown // Display label for the CustomUrl action button
      noteText?: unknown // Preset note text for the Note action; when unset the action opens the notes drawer
      reportScope?: unknown // SalesReport action — which sales the report may show: `costCenter` (the device's own, the default), `user` (the signed-…
      reportDateMode?: unknown // SalesReport action — `today` (the location's current business day) or `ask` (opens a date picker)
      reportPeriodMode?: unknown // SalesReport action — `day` (the whole business day, the default) or `shift` (one of the location's configured shift win…
      reportShiftId?: unknown // SalesReport action — pinned shift id for `reportPeriodMode: shift`; when unset the action asks. Only the id is stored: …
      targetPrinterGroupId?: unknown // StationMessage / SalesReport action — where the print goes: a preparation group id, the sentinel `__device__` (SalesRep…
      targetStationTitle?: unknown // Display-title snapshot of `targetPrinterGroupId`, captured at save time; clients prefer the live printer name. Absent f…
      isDynamicGroup?: unknown // Boolean. Layout node whose children are resolved live from inventory tags at render time; `data` stays empty
      dynamicTagKey?: unknown // Tag category key for a dynamic group, e.g. `CATEGORY_PRIMARY`, `CUSTOM`, `ALLERGEN`, `PREFERENCE`
      dynamicTagValue?: unknown // Tag value within `dynamicTagKey`, e.g. `DRINKS`
      scheduleRules?: unknown // Schedule rules for this layout node, or null — attached by the API on read, ignored on write. Same entries as the top-l…
      channelAvailability?: unknown // Channel availability for this layout node, or null — attached by the API on read, ignored on write. Keys: `byChannel`, …
      _isResolvedReference?: unknown // Boolean set by the API when `resolveReferences=true` expanded a reference node into this layout node
      _referenceId?: unknown // Template row ID a resolved reference was expanded from
      _originalReferenceNodeId?: unknown // Node ID of the original reference node
      _referenceNotFound?: unknown // Boolean set by the API when a reference points at a template that no longer exists
      actionBars?: unknown // Per-menu POS action bars (persist across sub-layout navigation). Root node only. Each side holds an array of menu layou…
      detachedLayouts?: unknown // Array of layouts not placed in the main grid, used only as targets for action-bar references or other links. Root node …
      actionBar?: unknown // Deprecated — use `actionBars`. Legacy single action bar (array of nodes).
      actionBarPosition?: unknown // Deprecated — use `actionBars`. Position of the legacy `actionBar`: left, right, top or bottom.
    }
    createdAt: string // date-time
    updatedAt?: string | null // date-time
    sortOrder?: number | null
    isTemplate?: boolean | null
    templateName?: string | null
    usageCount?: number
    scheduleRules?: Array<{
        id?: string
        dayOfWeek?: number
        startTime?: string
        endTime?: string
        active?: boolean
      }>
    channelAvailability?: {
      byChannel?: {
        KIOSK?: boolean
        POS?: boolean
        ONLINE?: boolean
        TAKEAWAY?: boolean
        WOLT?: boolean
        FOODORA?: boolean
        UBER_EATS?: boolean
      }
      online?: boolean
      qr?: boolean
      kiosk?: boolean
      pos?: boolean
      wolt?: boolean
      foodora?: boolean
      uberEats?: boolean
    }
    checkoutSwitchEligible?: boolean
    isAvailableNow?: boolean
  }
  meta: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## GET /api/v1/menu-layouts/{id}/usage

Get template usage

Get information about which menus are using a template

**Permissions:** 🔵 `menu_layouts.read`

Path parameters:
- `id` (string, required) — Menu layout ID (UUID)

Response 200 — Default Response

```ts
{
  status: "success"
  data: {
    templateId: string
    usageCount: number
    usedIn: Array<{
        menuId?: string
        menuTitle?: string
        linked?: boolean
      }>
  }
  meta: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## POST /api/v1/menu-layouts/reorder

Reorder menu layouts

Reorder menu layouts by updating their sort order

**Permissions:** 🟠 `menu_layouts.update`

Request body:

```ts
{
  layoutIds: string[]
}
```

Response 200 — Menu layouts successfully reordered

```ts
{
  status?: "success"
  data?: {
    success?: boolean
    layoutIds?: string[]
  }
  meta?: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```
