> ## Documentation Index
> Fetch the complete documentation index at: https://firespark.cloud/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Upsert menus

> Create or update composed menus from your POS or RMS in Fire spark.

Creates or updates composed menus for the authenticated merchant. Send catalog entities once at the top level, then reference them from up to 10 menu definitions in the same request.

Each menu in `menus` is matched by `id`. When no menu exists with that `id` for the merchant, Fire spark creates it. When one already exists, Fire spark updates the writable fields below. Omitted fields keep their current values on update.

Scope the upsert with optional query parameters. Fire spark resolves organization and merchant context from your access token.

<Note>
  Requires an access token with the `menus:write` scope. See
  [Token](/docs/integrations-api/oauth/token/post) to obtain a token.
</Note>

<Warning>
  Menu `id` values are immutable. You cannot change an existing menu's `id`
  through this endpoint — send the same `id` you use in your POS or RMS on every
  sync.
</Warning>

## Headers

| Header         | Required | Description                                                                                                                                                                                                                                                                                                                                   |
| -------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `x-release-id` | No       | Release identifier for this sync. Alphanumeric characters, `_`, and `-` only. 1–64 characters. When omitted, Fire spark generates a value like `v2026-07-10-21h`. Reuse the same value across scopes when rolling out one catalog version to many stores or channels. Each store, channel, and brand scope accepts a given release only once. |
| `x-force`      | No       | When `true` or `1`, menu changes in the dashboard and via API will be overwritten; otherwise, existing changes persist.                                                                                                                                                                                                                       |
| `x-publish`    | No       | Defaults to `true`. When `false` or `0`, the sync pauses before publishing so you can review and publish from the dashboard. With `x-force: true`, the pause happens before override pruning as well.                                                                                                                                         |

## Query parameters

| Parameter        | Required | Description                                                                                                                                                                                                         |
| ---------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `fulfillment_id` | No       | External fulfillment identifier. Alphanumeric characters, `_`, and `-` only. 1–64 characters. When omitted, the upsert applies to all fulfillment types. See [List fulfillment](/docs/integrations-api/fulfillment/get). |
| `store_id`       | No       | External store identifier. Alphanumeric characters, `_`, and `-` only. 1–64 characters. When omitted, the upsert applies to all stores.                                                                             |
| `channel_id`     | No       | External channel identifier. Alphanumeric characters, `_`, and `-` only. 1–64 characters. When omitted, the upsert applies to all channels.                                                                         |
| `brand_id`       | No       | External brand identifier. Alphanumeric characters, `_`, and `-` only. 1–64 characters. Omit or pass `__ANY__` for the merchant-wide default. When set, scopes the upsert to menus for this brand.                  |

Omit scope query parameters for an optional merchant-wide default — useful when store, channel, or fulfillment is not chosen yet. Add `fulfillment_id`, `store_id`, `channel_id`, and/or `brand_id` to override that default for a specific selling context. Omit a scope parameter or pass `__ANY__` to store the wildcard for that dimension. See [Menu sync](/docs/integrations-api/guides/menu-sync#defaults-first-override-where-needed) for the recommended baseline-and-override workflow.

## Sync styles

The request body is a discriminated union on `style`. Fire spark validates each variant against a different schema.

| Style       | When to use                                                                                                                                                                    |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `DEFAULT`   | Fire spark native format — owned channels such as app, web, or kiosk.                                                                                                          |
| `UBER_EATS` | [Uber Eats Menu API v2](https://developer.uber.com/docs/eats/references/api/v2/put-eats-stores-storeid-menu) format — push menus to Uber Eats without mapping fields yourself. |

Send `"style": "DEFAULT"` or `"style": "UBER_EATS"` at the top level. Shared catalog entities and up to 10 menu definitions are sent in the same request for both styles.

## Request

Switch between **DEFAULT** and **UBER\_EATS** in the request examples panel to see both payload shapes. Both calls use `PUT` with scope query parameters (`fulfillment_id`, `store_id`, `channel_id`, `brand_id`).

<RequestExample>
  ```bash Default theme={null}
  curl --request PUT \
    --url 'https://firespark.cloud/api/integrations/v1/menus?fulfillment_id=delivery&store_id=downtown&channel_id=app&brand_id=0001' \
    -H "Authorization: Bearer ACCESS_TOKEN" \
    -H "Content-Type: application/json" \
    -H "x-release-id: 2026-07-03-baseline" \
    -d '{
      "style": "DEFAULT",
      "products": [
        {
          "id": "burger-classic",
          "name": { "en_us": "Classic burger" },
          "description": { "en_us": "Angus beef patty with lettuce and tomato." },
          "image_url": "https://cdn.example.com/burger-classic.jpg",
          "bundled_items": [],
          "pricing": {
            "minimum_quantity": 1,
            "maximum_quantity": 10,
            "is_tax_inclusive": true,
            "tax_rate": 0.15,
            "price": 12.99,
            "anchor_price": 14.99,
            "anchor_price_percentage": 0.13,
            "modifiers": [],
            "modifier_group_ids": ["choose-drink"]
          },
          "availability": {
            "status": "AVAILABLE",
            "schedules": null,
            "out_of_stock_until": null
          },
          "metadata": {}
        },
        {
          "id": "soda",
          "name": { "en_us": "Soda" },
          "description": null,
          "image_url": null,
          "bundled_items": [],
          "pricing": {
            "minimum_quantity": 0,
            "maximum_quantity": 1,
            "is_tax_inclusive": true,
            "tax_rate": 0.15,
            "price": 0,
            "anchor_price": 0,
            "anchor_price_percentage": 0,
            "modifiers": [],
            "modifier_group_ids": []
          },
          "availability": {
            "status": "AVAILABLE",
            "schedules": null,
            "out_of_stock_until": null
          },
          "metadata": {}
        },
        {
          "id": "cola",
          "name": { "en_us": "Cola" },
          "description": null,
          "image_url": null,
          "bundled_items": [],
          "pricing": {
            "minimum_quantity": 0,
            "maximum_quantity": 1,
            "is_tax_inclusive": true,
            "tax_rate": 0.15,
            "price": 0,
            "anchor_price": 0,
            "anchor_price_percentage": 0,
            "modifiers": [],
            "modifier_group_ids": []
          },
          "availability": {
            "status": "AVAILABLE",
            "schedules": null,
            "out_of_stock_until": null
          },
          "metadata": {}
        }
      ],
      "categories": [
        {
          "id": "mains",
          "name": { "en_us": "Mains" },
          "description": { "en_us": "Burgers and sandwiches" },
          "items": [{ "type": "PRODUCT", "id": "burger-classic" }],
          "availability": {
            "status": "ACTIVE",
            "schedules": null,
            "inactive_until": null
          },
          "metadata": {}
        }
      ],
      "modifier_groups": [
        {
          "id": "choose-drink",
          "title": { "en_us": "Choose a drink" },
          "description": null,
          "display_type": "LIST",
          "modifier_ids": ["cola", "soda"],
          "minimum_quantity": 0,
          "maximum_quantity": 1,
          "charge_above_quantity": null,
          "metadata": {}
        }
      ],
      "menus": [
        {
          "id": "lunch-delivery",
          "name": { "en_us": "Lunch delivery menu" },
          "description": { "en_us": "Weekday lunch items for app delivery" },
          "schedules": null,
          "metadata": {},
          "category_ids": ["mains"],
          "status": "ACTIVE"
        }
      ]
    }'
  ```

  ```bash Uber Eats theme={null}
  curl --request PUT \
    --url 'https://firespark.cloud/api/integrations/v1/menus?fulfillment_id=delivery&store_id=downtown&channel_id=uber-eats&brand_id=0001' \
    -H "Authorization: Bearer ACCESS_TOKEN" \
    -H "Content-Type: application/json" \
    -H "x-release-id: 2026-07-03-uber" \
    -d '{
      "style": "UBER_EATS",
          "display_options": {
            "disable_item_instructions": true
          },
          "items": [
            {
              "id": "Coffee",
              "title": { "translations": { "en_us": "Coffee" } },
              "description": { "translations": { "en_us": "Deliciously roasted beans" } },
              "modifier_group_ids": { "ids": ["Add-milk"] },
              "price_info": { "price": 300 },
              "tax_info": { "tax_rate": 8 },
              "quantity_info": {}
            },
            {
              "id": "Milk",
              "title": { "translations": { "en_us": "Milk" } },
              "quantity_info": {
                "overrides": [
                  {
                    "context_type": "MODIFIER_GROUP",
                    "context_value": "Add-milk",
                    "quantity": { "max_permitted": 1 }
                  }
                ]
              },
              "price_info": {
                "price": 0,
                "overrides": [
                  {
                    "context_type": "MODIFIER_GROUP",
                    "context_value": "Add-milk",
                    "price": 0
                  }
                ]
              },
              "tax_info": { "tax_rate": 8 }
            }
          ],
          "categories": [
            {
              "id": "Drinks",
              "title": { "translations": { "en_us": "Drinks" } },
              "entities": [{ "type": "ITEM", "id": "Coffee" }]
            }
          ],
          "modifier_groups": [
            {
              "id": "Add-milk",
              "title": { "translations": { "en_us": "Add milk" } },
              "modifier_options": [{ "type": "ITEM", "id": "Milk" }],
              "quantity_info": { "quantity": { "max_permitted": 1 } },
              "display_type": null
            }
          ],
          "menus": [
            {
              "id": "All-day",
              "title": { "translations": { "en_us": "All day" } },
              "category_ids": ["Drinks"],
              "service_availability": [
                {
                  "day_of_week": "monday",
                  "time_periods": [{ "start_time": "00:00", "end_time": "23:59" }]
                },
                {
                  "day_of_week": "tuesday",
                  "time_periods": [{ "start_time": "00:00", "end_time": "23:59" }]
                },
                {
                  "day_of_week": "wednesday",
                  "time_periods": [{ "start_time": "00:00", "end_time": "23:59" }]
                },
                {
                  "day_of_week": "thursday",
                  "time_periods": [{ "start_time": "00:00", "end_time": "23:59" }]
                },
                {
                  "day_of_week": "friday",
                  "time_periods": [{ "start_time": "00:00", "end_time": "23:59" }]
                },
                {
                  "day_of_week": "saturday",
                  "time_periods": [{ "start_time": "00:00", "end_time": "23:59" }]
                },
                {
                  "day_of_week": "sunday",
                  "time_periods": [{ "start_time": "00:00", "end_time": "23:59" }]
                }
              ]
            }
          ]
        }'
  ```
</RequestExample>

<Tabs>
  <Tab title="Default">
    Fire spark native payload. Products, categories, and modifier groups use Fire spark field names (`name`, `pricing`, `items`, and so on).
  </Tab>

  <Tab title="Uber Eats">
    Uber Eats Menu API v2 payload. Top-level products become `items`; localized strings use a `translations` object. Prices are integer cents in `price_info.price`.

    See Uber's [example menu payloads](https://developer.uber.com/docs/eats/references/api/v2/example-menu-payloads) for nested modifiers, bundled items, tax labels, and other advanced fields.
  </Tab>
</Tabs>

## Request body

<Tabs>
  <Tab title="Default">
    | Field             | Required | Description                                                                                               |
    | ----------------- | -------- | --------------------------------------------------------------------------------------------------------- |
    | `style`           | Yes      | Must be `"DEFAULT"`.                                                                                      |
    | `products`        | Yes      | Product definitions shared across menus in this request. At least 1 entry. At most 1000.                  |
    | `categories`      | Yes      | Category definitions shared across menus in this request. At least 1 entry. At most 100.                  |
    | `modifier_groups` | No       | Modifier group definitions. Omit or set `null` when unused. At most 1000 when provided.                   |
    | `recommendations` | No       | Recommendation catalog shared across menus in this request. At most 1000 when provided. Defaults to `[]`. |
    | `menus`           | Yes      | Menu definitions to create or update. At least 1 entry. At most 10.                                       |

    ### Menu fields

    Each object in `menus` accepts these writable fields:

    | Field          | Required on create | Description                                                                                                                                                                                           |
    | -------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `id`           | Yes                | External menu identifier. Upsert key. Immutable after creation.                                                                                                                                       |
    | `name`         | Yes                | Localized display name keyed by locale (for example `en_us`). Each value is 1–1024 characters.                                                                                                        |
    | `description`  | No                 | Localized description keyed by locale. `null` when omitted. Each value is 1–1024 characters.                                                                                                          |
    | `schedules`    | No                 | Weekly schedule entries. `null` or omitted for 24/7. When set, must include all 7 weekdays with non-overlapping periods. See [Validation rules](/docs/integrations-api/guides/menu-sync#validation-rules). |
    | `metadata`     | No                 | Partner-specific metadata. Defaults to `{}`.                                                                                                                                                          |
    | `category_ids` | Yes                | Category ids to include in this menu. At least 1 entry. At most 100.                                                                                                                                  |
    | `status`       | Yes                | `ACTIVE` or `INACTIVE`.                                                                                                                                                                               |

    <AccordionGroup>
      <Accordion title="products">
        Each product in the request body:

        | Field | Required | Type | Description |
        | ----- | -------- | ---- | ----------- |

        \| `id` | Yes | string | External product identifier. |
        \| `name` | Yes | object | Localized display name keyed by locale (for example `en_us`). |
        \| `description` | No | object | null | Localized description. `null` when omitted. |
        \| `image_url` | No | string | Product image URL. `null` when omitted. |
        \| `bundled_items` | No | array | Items included in a bundle or combo. Defaults to `[]`. |
        \| `pricing` | Yes | object | Price, tax, quantity limits, and modifier configuration. See **pricing**. |
        \| `availability` | Yes | object | Product availability. See fields below. |
        \| `labels` | No | string\[] | Short labels for Dashboard filters and quick selection. At most 64 strings (1–128 characters each). Defaults to `[]`. |
        \| `tags` | No | object | Key/value tags for advanced custom rules. Values may be boolean, string, or number. Defaults to `{}`. See [Custom rules](/docs/integrations-api/guides/custom-rules). |
        \| `metadata` | No | object | Partner-specific metadata. Defaults to `{}`. |

        Each `availability` object:

        | Field                | Required | Type          | Description                                                                                                                                                                        |
        | -------------------- | -------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
        | `status`             | Yes      | string        | `AVAILABLE`, `UNAVAILABLE`, or `OUT_OF_STOCK`.                                                                                                                                     |
        | `schedules`          | No       | array \| null | Weekly schedule entries. `null` or omitted for no restriction. When set, must include all 7 weekdays. See [Validation rules](/docs/integrations-api/guides/menu-sync#validation-rules). |
        | `out_of_stock_until` | No       | string        | Optional ISO 8601 datetime when the product returns to stock.                                                                                                                      |

        Each `bundled_items` entry:

        | Field | Required | Type | Description |
        | ----- | -------- | ---- | ----------- |

        \| `id` | Yes | string | External identifier of the bundled product. |
        \| `refund_price` | Yes | number | Refund amount when the customer removes this item from the bundle. ≥ 0. |
        \| `included_quantity` | Yes | number | Units included by default. ≥ 0. |

        ```json Example product theme={null}
        {
          "id": "burger-classic",
          "name": { "en_us": "Classic burger" },
          "description": { "en_us": "Angus beef patty with lettuce and tomato." },
          "image_url": "https://cdn.example.com/burger-classic.jpg",
          "bundled_items": [],
          "pricing": {
            "minimum_quantity": 1,
            "maximum_quantity": 10,
            "is_tax_inclusive": true,
            "tax_rate": 0.15,
            "price": 12.99,
            "anchor_price": 14.99,
            "anchor_price_percentage": 0.13,
            "modifiers": [],
            "modifier_group_ids": ["choose-drink"]
          },
          "availability": {
            "status": "AVAILABLE",
            "schedules": null,
            "out_of_stock_until": null
          },
          "metadata": {}
        }
        ```
      </Accordion>

      <Accordion title="categories">
        Each category in the request body:

        | Field | Required | Type | Description |
        | ----- | -------- | ---- | ----------- |

        \| `id` | Yes | string | External category identifier. |
        \| `name` | Yes | object | Localized display name keyed by locale. |
        \| `description` | No | object | null | Localized description. `null` when omitted. |
        \| `items` | Yes | array | Ordered category entries. At least 1 entry. At most 1000. |
        \| `availability` | Yes | object | Category availability. See fields below. |
        \| `labels` | No | string\[] | Short labels for Dashboard filters and quick selection. At most 64 strings (1–128 characters each). Defaults to `[]`. |
        \| `tags` | No | object | Key/value tags for advanced custom rules. Values may be boolean, string, or number. Defaults to `{}`. See [Custom rules](/docs/integrations-api/guides/custom-rules). |
        \| `metadata` | No | object | Partner-specific metadata. Defaults to `{}`. |

        Each entry in `items`:

        | Field  | Required | Type   | Description                              |
        | ------ | -------- | ------ | ---------------------------------------- |
        | `type` | Yes      | string | `CATEGORY` or `PRODUCT`.                 |
        | `id`   | Yes      | string | External category or product identifier. |

        Each `availability` object:

        | Field            | Required | Type          | Description                                                                                                                                                                        |
        | ---------------- | -------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
        | `status`         | Yes      | string        | `ACTIVE`, `INACTIVE`, or `HIDDEN`.                                                                                                                                                 |
        | `schedules`      | No       | array \| null | Weekly schedule entries. `null` or omitted for no restriction. When set, must include all 7 weekdays. See [Validation rules](/docs/integrations-api/guides/menu-sync#validation-rules). |
        | `inactive_until` | No       | string        | Optional ISO 8601 datetime when the category becomes active again.                                                                                                                 |

        ```json Example category theme={null}
        {
          "id": "mains",
          "name": { "en_us": "Mains" },
          "description": { "en_us": "Burgers and sandwiches" },
          "items": [{ "type": "PRODUCT", "id": "burger-classic" }],
          "availability": {
            "status": "ACTIVE",
            "schedules": null,
            "inactive_until": null
          },
          "metadata": {}
        }
        ```
      </Accordion>

      <Accordion title="modifier_groups">
        Each modifier group in the request body:

        | Field | Required | Type | Description |
        | ----- | -------- | ---- | ----------- |

        \| `id` | Yes | string | External modifier group identifier. Referenced from `pricing.modifier_group_ids`. |
        \| `title` | Yes | object | Localized group title keyed by locale. |
        \| `description` | No | object | null | Localized description. `null` when omitted. |
        \| `display_type` | No | string | `GRID` or `LIST`. |
        \| `modifier_ids` | Yes | array | Product UUIDs for modifiers in this group. At least 2 entries. |
        \| `minimum_quantity` | Yes | number | Minimum selections required. ≥ 0. |
        \| `maximum_quantity` | Yes | number | Maximum selections allowed. 0–1,000,000. |
        \| `charge_above_quantity` | No | number | Number of free selections before additional charges apply. Optional. |
        \| `metadata` | No | object | Partner-specific metadata. Defaults to `{}`. |

        ```json Example modifier group theme={null}
        {
          "id": "choose-drink",
          "title": { "en_us": "Choose a drink" },
          "description": null,
          "display_type": "LIST",
          "modifier_ids": ["cola", "soda"],
          "minimum_quantity": 0,
          "maximum_quantity": 1,
          "charge_above_quantity": null,
          "metadata": {}
        }
        ```
      </Accordion>

      <Accordion title="recommendations">
        Each recommendation in the top-level catalog:

        | Field               | Required | Type           | Description                                                        |
        | ------------------- | -------- | -------------- | ------------------------------------------------------------------ |
        | `id`                | Yes      | string         | External recommendation identifier. Must be unique in the request. |
        | `title`             | Yes      | object         | Localized display name keyed by locale.                            |
        | `description`       | No       | object \| null | Localized description. `null` when omitted.                        |
        | `type`              | Yes      | string         | `UPSELL`, `CROSS_SELL`, or `DOWNSELL`.                             |
        | `target_product_id` | Yes      | string         | Product `id` to recommend. Must exist in `products`.               |
        | `metadata`          | No       | object         | Partner-specific metadata. Defaults to `{}`.                       |

        ```json Example recommendation theme={null}
        {
          "id": "upsell-fries",
          "type": "UPSELL",
          "title": { "en_us": "Add fries" },
          "description": { "en_us": "Side of seasoned fries." },
          "target_product_id": "fries",
          "metadata": {}
        }
        ```
      </Accordion>

      <Accordion title="pricing">
        Product pricing in the request body:

        | Field | Required | Type | Description |
        | ----- | -------- | ---- | ----------- |

        \| `minimum_quantity` | Yes | number | Minimum units per order. ≥ 0. |
        \| `maximum_quantity` | Yes | number | Maximum units per order. 0–1,000,000. |
        \| `is_tax_inclusive` | Yes | boolean | Whether `price` includes tax. |
        \| `tax_rate` | Yes | number | Tax rate as a decimal (for example `0.15` for 15%). 0–1. |
        \| `price` | Yes | number | List price. ≥ 0. |
        \| `anchor_price` | Yes | number | Reference price for strikethrough or comparison. ≥ 0. |
        \| `anchor_price_percentage` | Yes | number | Discount shown relative to `anchor_price`. 0–1. |
        \| `modifiers` | No | array | Per-group pricing and selection overrides. Defaults to `[]`. |
        \| `modifier_group_ids` | No | array | External modifier group identifiers attached to this product. Defaults to `[]`. |
        \| `recommendations_ids` | No | array | Recommendation ids to surface on this product. Each id must exist in top-level `recommendations`. Defaults to `[]`. |
        \| `recommendations` | No | array | Per-recommendation display settings on this product. Defaults to `[]`. |

        Each object in `recommendations` (inside `pricing`) links one catalog recommendation to the parent product:

        | Field               | Required | Type   | Description                                                                                                                                         |
        | ------------------- | -------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------- |
        | `recommendation_id` | Yes      | string | Must match an `id` from top-level `recommendations`.                                                                                                |
        | `default_quantity`  | No       | number | Pre-selected quantity for the recommended product on this parent line. Defaults to `0`. Independent from `default_quantity` on `pricing.modifiers`. |

        Each object in `modifiers` configures one modifier group referenced in `modifier_group_ids`. It accepts the same pricing fields as the parent product, plus modifier-specific fields:

        | Field | Required | Type | Description |
        | ----- | -------- | ---- | ----------- |

        \| `modifier_group_id` | Yes | string | External modifier group identifier. Must match a group in `modifier_group_ids`. |
        \| `minimum_quantity` | Yes | number | Minimum units per order for modifiers in this group. ≥ 0. |
        \| `maximum_quantity` | Yes | number | Maximum units per order for modifiers in this group. 0–1,000,000. |
        \| `is_tax_inclusive` | Yes | boolean | Whether `price` includes tax for modifiers in this group. |
        \| `tax_rate` | Yes | number | Tax rate as a decimal (for example `0.15` for 15%). 0–1. |
        \| `price` | Yes | number | List price for modifiers in this group. ≥ 0. |
        \| `anchor_price` | Yes | number | Reference price for strikethrough or comparison. ≥ 0. |
        \| `anchor_price_percentage` | Yes | number | Discount shown relative to `anchor_price`. 0–1. |
        \| `default_quantity` | Yes | number | Pre-selected quantity for modifiers in this group. ≥ 0. |
        \| `charge_above_quantity` | No | number | Free selections before charges apply. Optional. |
        \| `display_type` | No | string | How modifiers render in the UI: `RADIO`, `CHECKBOX`, or `QUANTITY`. Optional. |

        ```json Example modifiers entry theme={null}
        {
          "modifier_group_id": "choose-drink",
          "minimum_quantity": 0,
          "maximum_quantity": 1,
          "is_tax_inclusive": true,
          "tax_rate": 0.15,
          "price": 0,
          "anchor_price": 0,
          "anchor_price_percentage": 0,
          "default_quantity": 1,
          "charge_above_quantity": 0,
          "display_type": "RADIO"
        }
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>

  <Tab title="Uber Eats">
    | Field             | Required | Description                                                                                                                  |
    | ----------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------- |
    | `style`           | Yes      | Must be `"UBER_EATS"`.                                                                                                       |
    | `items`           | Yes      | Items and modifier options. Products and modifiers share this array. At least 1 entry. At most 1000.                         |
    | `categories`      | Yes      | Category definitions with `entities` referencing item IDs. At least 1 entry. At most 100.                                    |
    | `modifier_groups` | No       | Modifier group definitions. Omit or set `null` when no groups are used. At most 1000 when provided.                          |
    | `recommendations` | No       | Recommendation catalog. Same shape as `DEFAULT` after mapping. At most 1000 when provided. Defaults to `[]`.                 |
    | `menus`           | Yes      | Menu definitions to create or update. At least 1 entry. At most 10.                                                          |
    | `menu_type`       | No       | `MENU_TYPE_FULFILLMENT_DELIVERY`, `MENU_TYPE_FULFILLMENT_PICK_UP`, or `MENU_TYPE_FULFILLMENT_DINE_IN`. Defaults to delivery. |
    | `display_options` | No       | Store-level display settings such as `disable_item_instructions`.                                                            |

    ### Menu fields

    Each object in `menus` uses the Uber Eats Menu shape:

    | Field                  | Required on create | Description                                                          |
    | ---------------------- | ------------------ | -------------------------------------------------------------------- |
    | `id`                   | Yes                | External menu identifier. Upsert key. Immutable after creation.      |
    | `title`                | Yes                | `MultiLanguageText` with a `translations` map (for example `en_us`). |
    | `subtitle`             | No                 | Optional subtitle as `MultiLanguageText`.                            |
    | `service_availability` | Yes                | Weekly availability with `day_of_week` and `time_periods` (`HH:MM`). |
    | `category_ids`         | Yes                | Category IDs active while this menu is available.                    |

    ### MultiLanguageText

    Localized strings wrap values in `translations`:

    ```json theme={null}
    {
      "translations": {
        "en_us": "Coffee"
      }
    }
    ```

    <AccordionGroup>
      <Accordion title="items">
        Each item in the request body. Top-level products and modifier options are both items.

        | Field | Required | Type | Description |
        | ----- | -------- | ---- | ----------- |

        \| `id` | Yes | string | Unique item identifier from your POS or RMS. |
        \| `title` | Yes | object | Required `MultiLanguageText`. |
        \| `description` | No | object | Optional `MultiLanguageText`. |
        \| `image_url` | No | string | Public image URL. Optional. |
        \| `external_data` | No | string | Partner metadata (max 1024 characters). Optional. |
        \| `price_info` | No | object | Price in cents plus optional context overrides. See **price\_info**. |
        \| `quantity_info` | No | object | Quantity constraints and modifier-context overrides. Optional. |
        \| `modifier_group_ids` | No | object | `{ "ids": [...] }` with optional context overrides. Optional. |
        \| `tax_info` | No | object | `tax_rate`, `vat_rate_percentage`, or `mx_ieps_rate`. Optional. |
        \| `suspension_info` | No | object | Out-of-stock suspension rules. Optional. |
        \| `bundled_items` | No | array | Items always included in a combo. Optional. |
        \| `dish_info` | No | object | Dietary labels and classifications. Optional. |
        \| `visibility_info` | No | object | Time windows when the item is visible. Optional. |
        \| `tax_label_info` | No | object | Tax classification labels. Optional. |
        \| `product_info` | No | object | GTIN, PLU, or merchant product identifiers. Optional. |
        \| `selling_info` | No | object | Sold-by-weight or sold-by-unit configuration. Optional. |

        ```json Example item theme={null}
        {
          "id": "Coffee",
          "title": { "translations": { "en_us": "Coffee" } },
          "description": { "translations": { "en_us": "Deliciously roasted beans" } },
          "modifier_group_ids": { "ids": ["Add-milk"] },
          "price_info": { "price": 300 },
          "tax_info": { "tax_rate": 8 },
          "quantity_info": {}
        }
        ```
      </Accordion>

      <Accordion title="categories">
        Each category groups items for display on a menu.

        | Field | Required | Type | Description |
        | ----- | -------- | ---- | ----------- |

        \| `id` | Yes | string | Unique category identifier. |
        \| `title` | Yes | object | Required `MultiLanguageText`. |
        \| `subtitle` | No | object | Optional `MultiLanguageText`. |
        \| `entities` | Yes | array | Menu entities, usually `{ "type": "ITEM", "id": "..." }`. |

        ```json Example category theme={null}
        {
          "id": "Drinks",
          "title": { "translations": { "en_us": "Drinks" } },
          "entities": [{ "type": "ITEM", "id": "Coffee" }]
        }
        ```
      </Accordion>

      <Accordion title="modifier_groups">
        Modifier groups attach selectable items to a parent item.

        | Field | Required | Type | Description |
        | ----- | -------- | ---- | ----------- |

        \| `id` | Yes | string | Unique modifier group identifier. |
        \| `title` | Yes | object | Required `MultiLanguageText`. |
        \| `external_data` | No | string | Partner metadata (max 1024 characters). Optional. |
        \| `modifier_options` | Yes | array | `{ "type": "ITEM", "id": "..." }` entries. All must be items. |
        \| `quantity_info` | No | object | Group-level min/max, charge-above, and unique selection rules. Optional. |
        \| `display_type` | No | string | `expanded` or `collapsed`. Optional. |

        ```json Example modifier group theme={null}
        {
          "id": "Add-milk",
          "title": { "translations": { "en_us": "Add milk" } },
          "modifier_options": [{ "type": "ITEM", "id": "Milk" }],
          "quantity_info": { "quantity": { "max_permitted": 1 } },
          "display_type": null
        }
        ```
      </Accordion>

      <Accordion title="price_info">
        Item pricing follows the Uber Eats Menu API. Amounts are integer cents.

        | Field | Required | Type | Description |
        | ----- | -------- | ---- | ----------- |

        \| `price` | Yes | integer | List price in cents. Required when `price_info` is present. |
        \| `core_price` | No | integer | Intrinsic value used for refunds on bundled modifier options. Optional. |
        \| `in_store_price` | No | integer | In-store price before discounts. Optional. |
        \| `container_deposit` | No | integer | Deposit metadata for returnable containers. Optional. |
        \| `overrides` | No | array | Context-specific price overrides (`MENU`, `ITEM`, `MODIFIER_GROUP`). Optional. |
        \| `priced_by_unit` | No | object | Unit the price is based on (count, weight, volume, or length). Optional. |

        Each override entry:

        | Field | Required | Type | Description |
        | ----- | -------- | ---- | ----------- |

        \| `context_type` | Yes | string | `MENU`, `ITEM`, or `MODIFIER_GROUP`. |
        \| `context_value` | Yes | string | ID of the context entity. |
        \| `price` | Yes | integer | Override price in cents. |
        \| `core_price` | No | integer | Override intrinsic value in cents. Optional. |
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>

## Response

The endpoint returns asynchronously. Fire spark starts a background menu sync and responds immediately.

| Status | When                                                                      | Body                                                                                                                                                                          |
| ------ | ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `201`  | Sync accepted and queued                                                  | `{ "message": "Sync accepted: …" }` — counts of products, menus, categories, and modifier groups received                                                                     |
| `202`  | A sync is already running for the same store, channel, and brand          | `{ "message": "Sync already in progress for this store/channel.", "runId": "…" }` — `runId` identifies the active workflow run                                                |
| `409`  | A sync is already in progress or completed for the same release and scope | `{ "message": "Sync already exists for this release and scope.", "menuSyncId": "…" }` — retry with the same `x-release-id` only if the prior sync `FAILED` or was `CANCELLED` |

Treat `202` as success: the catalog is already being processed. Retry only after the current sync finishes or fails.

Treat `409` as success when replaying a release that already completed successfully. If the prior sync for that release and scope `FAILED` or was `CANCELLED`, send the same `x-release-id` again.

## Rate limits

`GET` and `PUT /api/integrations/v1/menus` share a limit of **10 requests per 10 seconds per merchant**. The limit key is the `merchant_id` from your access token.

Send **one request at a time** per merchant — wait for each response before starting the next. Do not parallelize menu upserts or reads for the same merchant.

Every response includes rate limit headers so you know when you can send the next request:

| Header                  | Description                                                       |
| ----------------------- | ----------------------------------------------------------------- |
| `X-RateLimit-Limit`     | Maximum requests allowed per window (`10`)                        |
| `X-RateLimit-Reset`     | Seconds until the current window resets (`10`)                    |
| `X-RateLimit-Remaining` | Requests remaining in the current window — present on `429` (`0`) |
| `Retry-After`           | Seconds to wait before retrying — present only on `429` responses |

When you exceed the limit, the API returns `429 Too Many Requests`. Wait `Retry-After` seconds (or `X-RateLimit-Reset`) before sending another request for that merchant.

## Error responses

\| Status | Description                                                                                                                                                                      |
\| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --- |
\| `400`  | Validation error — invalid field values, missing required fields, or invalid weekly schedule shape. See [Validation rules](/docs/integrations-api/guides/menu-sync#validation-rules). |
\| `401`  | Missing or invalid access token.                                                                                                                                                 |
\| `403`  | Token does not include the `menus:write` scope.                                                                                                                                  |
\| `409`  | Referenced store, channel, or fulfillment does not exist for this merchant, or a sync already exists for this release and scope.                                                 |
\| `429`  | Rate limit exceeded — wait for `Retry-After` / `X-RateLimit-Reset` before retrying. See [Rate limits](#rate-limits).                                                             |     |


## OpenAPI

````yaml PUT /menus
openapi: 3.0.1
info:
  title: Fire spark Integrations API
  description: POS and RMS integration endpoints for Fire spark.
  version: 1.0.0
servers:
  - url: https://firespark.cloud/api/integrations/v1
security:
  - bearerAuth: []
paths:
  /menus:
    put:
      summary: Upsert menus
      parameters:
        - in: header
          name: x-release-id
          required: false
          schema:
            type: string
            minLength: 1
            maxLength: 64
            pattern: ^[a-zA-Z0-9_-]+$
          description: >-
            Optional release identifier for this sync. Alphanumeric characters,
            _, and - only. 1–64 characters. When omitted, Fire spark generates a
            value like `v2026-07-10-21h`. Reuse the same value across scopes in
            a multi-scope rollout. Each scope accepts a given release only once
            while a sync is processing or after it succeeds; failed or cancelled
            syncs do not block retries.
        - in: header
          name: x-force
          required: false
          schema:
            type: string
            enum:
              - 'true'
              - 'false'
              - '1'
              - '0'
          description: >-
            When true or 1, menu changes in the dashboard and via API will be
            overwritten; otherwise, existing changes persist.
        - in: header
          name: x-publish
          required: false
          schema:
            type: string
            enum:
              - 'true'
              - 'false'
              - '1'
              - '0'
            default: 'true'
          description: >-
            Defaults to true. When false or 0, the sync pauses before publishing
            so you can review and publish from the dashboard. With x-force true,
            the pause happens before override pruning as well.
        - in: query
          name: fulfillment_id
          required: false
          schema:
            type: string
            minLength: 1
            maxLength: 64
            pattern: ^[a-zA-Z0-9_-]+$
          description: >-
            External fulfillment identifier used to scope the upsert. Omit or
            pass __ANY__ for all fulfillment types.
        - in: query
          name: store_id
          required: false
          schema:
            type: string
            minLength: 1
            maxLength: 64
            pattern: ^[a-zA-Z0-9_-]+$
          description: External store identifier used to scope the upsert.
        - in: query
          name: channel_id
          required: false
          schema:
            type: string
            minLength: 1
            maxLength: 64
            pattern: ^[a-zA-Z0-9_-]+$
          description: External channel identifier used to scope the upsert.
        - in: query
          name: brand_id
          required: false
          schema:
            type: string
            minLength: 1
            maxLength: 64
            pattern: ^[a-zA-Z0-9_-]+$
          description: >-
            Scope the upsert to menus for this brand. Omit or pass __ANY__ for
            the merchant-wide default.
      requestBody:
        required: true
        description: Composed menu payload. Use style DEFAULT or UBER_EATS.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MenusUpsertBody'
      responses:
        '200':
          description: Ok
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/MenuDefinitionUpsert'
components:
  schemas:
    MenusUpsertBody:
      oneOf:
        - $ref: '#/components/schemas/MenusDefaultUpsertBody'
        - $ref: '#/components/schemas/MenusUberEatsUpsertBody'
    MenuDefinitionUpsert:
      type: object
      required:
        - id
        - name
        - status
      properties:
        id:
          type: string
          minLength: 1
          maxLength: 64
          pattern: ^[a-zA-Z0-9_-]+$
        name:
          $ref: '#/components/schemas/MultiLanguageText'
        description:
          allOf:
            - $ref: '#/components/schemas/MultiLanguageText'
          nullable: true
        schedules:
          type: array
          items:
            type: object
          nullable: true
        metadata:
          type: object
          additionalProperties: true
        cms_template_id:
          type: string
          format: uuid
          nullable: true
        category_ids:
          type: array
          items:
            type: string
            minLength: 1
            maxLength: 64
            pattern: ^[a-zA-Z0-9_-]+$
        status:
          type: string
          enum:
            - ACTIVE
            - INACTIVE
    MenusDefaultUpsertBody:
      type: object
      required:
        - style
        - products
        - categories
        - menus
      properties:
        style:
          type: string
          enum:
            - DEFAULT
        products:
          type: array
          minItems: 1
          maxItems: 1000
          items:
            $ref: '#/components/schemas/MenuProductUpsert'
        categories:
          type: array
          minItems: 1
          maxItems: 100
          items:
            $ref: '#/components/schemas/MenuCategoryUpsert'
        modifier_groups:
          type: array
          maxItems: 100
          items:
            $ref: '#/components/schemas/MenuModifierGroupUpsert'
          nullable: true
        menus:
          type: array
          minItems: 1
          maxItems: 10
          items:
            $ref: '#/components/schemas/MenuDefinitionUpsert'
    MenusUberEatsUpsertBody:
      type: object
      required:
        - style
        - items
        - categories
        - menus
      properties:
        style:
          type: string
          enum:
            - UBER_EATS
        items:
          type: array
          minItems: 1
          maxItems: 1000
          items:
            type: object
        categories:
          type: array
          minItems: 1
          maxItems: 100
          items:
            type: object
        modifier_groups:
          type: array
          maxItems: 100
          items:
            type: object
          nullable: true
        menus:
          type: array
          minItems: 1
          maxItems: 10
          items:
            type: object
    MultiLanguageText:
      type: object
      additionalProperties:
        type: string
      example:
        en_us: Example
    MenuProductUpsert:
      type: object
      required:
        - id
        - name
        - status
        - pricing
        - availability
      properties:
        id:
          type: string
          minLength: 1
          maxLength: 64
          pattern: ^[a-zA-Z0-9_-]+$
        name:
          $ref: '#/components/schemas/MultiLanguageText'
        description:
          allOf:
            - $ref: '#/components/schemas/MultiLanguageText'
          nullable: true
        image_url:
          type: string
          format: uri
          nullable: true
        cms_template_id:
          type: string
          format: uuid
          nullable: true
        status:
          type: string
          enum:
            - ACTIVE
            - INACTIVE
        bundled_items:
          type: array
          items:
            type: object
        pricing:
          type: object
        availability:
          type: object
        labels:
          type: array
          items:
            type: string
            minLength: 1
            maxLength: 128
          maxItems: 64
          default: []
        tags:
          type: object
          additionalProperties:
            oneOf:
              - type: boolean
              - type: string
              - type: number
          default: {}
        metadata:
          type: object
          additionalProperties: true
    MenuCategoryUpsert:
      type: object
      required:
        - id
        - name
        - status
        - availability
      properties:
        id:
          type: string
          minLength: 1
          maxLength: 64
          pattern: ^[a-zA-Z0-9_-]+$
        name:
          $ref: '#/components/schemas/MultiLanguageText'
        description:
          allOf:
            - $ref: '#/components/schemas/MultiLanguageText'
          nullable: true
        cms_template_id:
          type: string
          format: uuid
          nullable: true
        status:
          type: string
          enum:
            - ACTIVE
            - INACTIVE
        items:
          type: array
          items:
            $ref: '#/components/schemas/CategoryItem'
        availability:
          type: object
        labels:
          type: array
          items:
            type: string
            minLength: 1
            maxLength: 128
          maxItems: 64
          default: []
        tags:
          type: object
          additionalProperties:
            oneOf:
              - type: boolean
              - type: string
              - type: number
          default: {}
        metadata:
          type: object
          additionalProperties: true
    MenuModifierGroupUpsert:
      type: object
      required:
        - id
        - title
        - modifier_ids
        - minimum_quantity
        - maximum_quantity
      properties:
        id:
          type: string
          minLength: 1
          maxLength: 64
          pattern: ^[a-zA-Z0-9_-]+$
        title:
          $ref: '#/components/schemas/MultiLanguageText'
        description:
          allOf:
            - $ref: '#/components/schemas/MultiLanguageText'
          nullable: true
        display_type:
          type: string
          enum:
            - GRID
            - LIST
          nullable: true
        modifier_ids:
          type: array
          minItems: 2
          items:
            type: string
            minLength: 1
            maxLength: 64
            pattern: ^[a-zA-Z0-9_-]+$
        minimum_quantity:
          type: integer
          minimum: 0
        maximum_quantity:
          type: integer
          minimum: 0
        charge_above_quantity:
          type: integer
          minimum: 0
          nullable: true
        metadata:
          type: object
          additionalProperties: true
    CategoryItem:
      type: object
      required:
        - type
        - id
      properties:
        type:
          type: string
          enum:
            - CATEGORY
            - PRODUCT
        id:
          type: string
          minLength: 1
          maxLength: 64
          pattern: ^[a-zA-Z0-9_-]+$
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer

````