> ## 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 menús

> Crea o actualiza menús compuestos desde tu POS o RMS en Fire spark.

Crea o actualiza menús compuestos para el comercio autenticado. Envía las entidades del catálogo una sola vez en el nivel superior y referencia hasta 10 definiciones de menú en la misma solicitud.

Cada menú en `menus` se empareja por `id`. Si no existe un menú con ese `id` para el comercio, Fire spark lo crea. Si ya existe, Fire spark actualiza los campos editables indicados abajo. Los campos omitidos conservan su valor actual en una actualización.

Acota el upsert con parámetros de consulta opcionales. Fire spark resuelve el contexto de organización y comercio desde tu access token.

> [!NOTE]
>
> Requiere un access token con el scope `menus:write`. Consulta
> [Token](https://firespark.cloud/docs/es/integrations-api/oauth/token/post.md) para obtener un token.

> [!WARNING]
>
> Los valores de `id` del menú son inmutables. No puedes cambiar el `id` de un
> menú existente mediante este endpoint — envía el mismo `id` que usas en tu POS
> o RMS en cada sincronización.

## Headers

| Header | Requerido | Descripción |
| - | - | - |
| `x-release-id` | No | Identificador de release para este sync. Solo alfanuméricos, `_` y `-`. 1–64 caracteres. Si se omite, Fire spark genera un valor como `v2026-07-10-21h`. Reutiliza el mismo valor en varios scopes cuando despliegas una versión del catálogo a varias tiendas o canales. Cada scope de tienda, canal y marca acepta un release solo una vez. |
| `x-force` | No | Si es `true` o `1`, los cambios de menús en el dashboard y vía API se sobrescribirán; de lo contrario, los cambios existentes persisten. |
| `x-publish` | No | Por defecto es `true`. Si es `false` o `0`, la sincronización se pausa antes de publicar para que puedas revisar y publicar desde el dashboard. Con `x-force: true`, la pausa ocurre también antes de podar overrides. |

## Parámetros de consulta

| Parámetro | Requerido | Descripción |
| - | - | - |
| `fulfillment_id` | No | Identificador externo de fulfillment. Solo alfanuméricos, `_` y `-`. 1–64 caracteres. Si se omite, el upsert aplica a todos los tipos de fulfillment. Ver [Listar fulfillment](https://firespark.cloud/docs/es/integrations-api/fulfillment/get.md). |
| `store_id` | No | Identificador externo de tienda. Solo alfanuméricos, `_` y `-`. 1–64 caracteres. Si se omite, el upsert aplica a todas las tiendas. |
| `channel_id` | No | Identificador externo de canal. Solo alfanuméricos, `_` y `-`. 1–64 caracteres. Si se omite, el upsert aplica a todos los canales. |
| `brand_id` | No | Identificador externo de marca. Solo alfanuméricos, `_` y `-`. 1–64 caracteres. Omite o envía `__ANY__` para el default del comercio. Si se envía, acota el upsert a menús de esta marca. |

Omite los parámetros de scope para un default opcional a nivel comercio — útil cuando aún no se eligió tienda, canal o fulfillment. Añade `fulfillment_id`, `store_id`, `channel_id` y/o `brand_id` para sobreescribir ese default en un contexto concreto. Omite un parámetro de scope o envía `__ANY__` para persistir el wildcard en esa dimensión. Consulta [Sincronización de menús](https://firespark.cloud/docs/es/integrations-api/guides/menu-sync.md#defaults-primero-override-solo-donde-haga-falta) para el flujo baseline y overrides recomendado.

## Estilos de sync

El cuerpo de la solicitud es una unión discriminada por `style`. Fire spark valida cada variante con un esquema distinto.

| Style | Cuándo usarlo |
| - | - |
| `DEFAULT` | Formato nativo de Fire spark — canales propios como app, web o kiosco. |
| `UBER_EATS` | Formato de la [Uber Eats Menu API v2](https://developer.uber.com/docs/eats/references/api/v2/put-eats-stores-storeid-menu) — envía menús a Uber Eats sin mapear campos manualmente. |

Envía `"style": "DEFAULT"` o `"style": "UBER_EATS"` en el nivel superior. En ambos estilos se envían las entidades del catálogo y hasta 10 definiciones de menú en la misma solicitud.

## Request

Alterna entre **DEFAULT** y **UBER\_EATS** en el panel de ejemplos de solicitud para ver ambas formas de payload. Ambas llamadas usan `PUT` con parámetros de scope (`fulfillment_id`, `store_id`, `channel_id`, `brand_id`).

```bash Default theme={null}
curl --request PUT \
  --url 'https://firespark.cloud/api/integrations/v1/menus?fulfillment_id=delivery&store_id=centro&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": "hamburguesa-clasica",
        "name": { "en_us": "Hamburguesa clásica" },
        "description": { "en_us": "Carne Angus con lechuga y tomate." },
        "image_url": "https://cdn.example.com/hamburguesa-clasica.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": ["elige-bebida"]
        },
        "availability": {
          "status": "AVAILABLE",
          "schedules": null,
          "out_of_stock_until": null
        },
        "metadata": {}
      },
      {
        "id": "refresco",
        "name": { "en_us": "Refresco" },
        "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": "principales",
        "name": { "en_us": "Platos fuertes" },
        "description": { "en_us": "Hamburguesas y sándwiches" },
        "image_url": "https://cdn.example.com/mains.jpg",
        "items": [{ "type": "PRODUCT", "id": "hamburguesa-clasica" }],
        "availability": {
          "status": "ACTIVE",
          "schedules": null,
          "inactive_until": null
        },
        "metadata": {}
      }
    ],
    "modifier_groups": [
      {
        "id": "elige-bebida",
        "title": { "en_us": "Elige una bebida" },
        "description": null,
        "display_type": "LIST",
        "modifier_ids": ["cola", "refresco"],
        "minimum_quantity": 0,
        "maximum_quantity": 1,
        "charge_above_quantity": null,
        "metadata": {}
      }
    ],
    "menus": [
      {
        "id": "almuerzo-delivery",
        "name": { "en_us": "Menú almuerzo delivery" },
        "description": { "en_us": "Ítems de almuerzo entre semana para delivery" },
        "schedules": null,
        "metadata": {},
        "category_ids": ["principales"],
        "status": "ACTIVE"
      }
    ]
  }'
```

```bash Uber Eats theme={null}
curl --request PUT \
  --url 'https://firespark.cloud/api/integrations/v1/menus?fulfillment_id=delivery&store_id=centro&channel_id=uber-eats&brand_id=0001' \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "x-release-id: 2026-07-03-baseline" \
  -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" }]
              }
            ]
          }
        ]
      }'
```

> [tab] Default
>
> Payload nativo de Fire spark. Productos, categorías y grupos de modificadores usan nombres de campo de Fire spark (`name`, `pricing`, `items`, etc.).

> [tab] Uber Eats
>
> Payload de Uber Eats Menu API v2. Los productos de nivel superior son `items`; los textos localizados usan un objeto `translations`. Los precios son enteros en centavos en `price_info.price`.
>
> Consulta los [example menu payloads](https://developer.uber.com/docs/eats/references/api/v2/example-menu-payloads) de Uber para modificadores anidados, bundled items, tax labels y otros campos avanzados.

## Cuerpo de la solicitud

> [tab] Default
>
> | Campo | Requerido | Descripción |
> | - | - | - |
> | `style` | Sí | Debe ser `"DEFAULT"`. |
> | `products` | Sí | Definiciones de producto compartidas entre menús en esta solicitud. Al menos 1 entrada. Como máximo 1000. |
> | `categories` | Sí | Definiciones de categoría compartidas entre menús en esta solicitud. Al menos 1 entrada. Como máximo 100. |
> | `modifier_groups` | No | Definiciones de grupos de modificadores. Omite o envía `null` si no hay grupos. Como máximo 1000 si se envía. |
> | `variant_groups` | No | Definiciones de grupos de variantes (preguntas que resuelven el id del producto). Como máximo 1000. Por defecto `[]`. Mira [Variantes](https://firespark.cloud/docs/es/integrations-api/examples/menu-sync/variants.md). |
> | `recommendations` | No | Catálogo de recomendaciones compartido entre menús en esta solicitud. Como máximo 1000 si se envía. Por defecto `[]`. |
> | `menus` | Sí | Definiciones de menú a crear o actualizar. Al menos 1 entrada. Como máximo 10. |
>
> ### Campos del menú
>
> Cada objeto en `menus` acepta estos campos editables:
>
> | Campo | Requerido al crear | Descripción |
> | - | - | - |
> | `id` | Sí | Identificador externo del menú. Clave del upsert. Inmutable tras la creación. |
> | `name` | Sí | Nombre visible localizado por locale (por ejemplo `en_us`). Cada valor tiene 1–1024 caracteres. |
> | `description` | No | Descripción localizada por locale. `null` si se omite. Cada valor tiene 1–1024 caracteres. |
> | `schedules` | No | Entradas de horario semanal. `null` u omitido para 24/7. Si se envía, deben incluir los 7 días sin periodos solapados. Ver [Reglas de validación](https://firespark.cloud/docs/es/integrations-api/guides/menu-sync.md#reglas-de-validación). |
> | `metadata` | No | Metadatos del partner. Por defecto `{}`. |
> | `category_ids` | Sí | IDs de categorías incluidas en este menú. Al menos 1 entrada. Como máximo 100. |
> | `status` | Sí | `ACTIVE` o `INACTIVE`. |
>
> > [accordion] products
> >
> > Cada producto en el cuerpo de la solicitud:
> >
> > | Campo | Requerido | Tipo | Descripción |
> > | - | - | - | - |
> >
> > \| `id` | Sí | string | Identificador externo del producto. |
> > \| `name` | Sí | object | Nombre visible localizado por locale (por ejemplo `en_us`). |
> > \| `description` | No | object | null | Descripción localizada. `null` si se omite. |
> > \| `image_url` | No | string | URL de imagen del producto. JPG, PNG o WebP. Fire spark la copia a almacenamiento público y la sirve como URL de render para que agregues `width`, `height` y `resize`. `null` si se omite. |
> > \| `bundled_items` | No | array | Ítems incluidos en un bundle o combo. Por defecto `[]`. |
> > \| `pricing` | Sí | object | Precio, impuestos, límites de cantidad y configuración de modificadores. Ver **pricing**. |
> > \| `availability` | Sí | object | Disponibilidad del producto. Ver campos abajo. |
> > \| `labels` | No | string\[] | Etiquetas cortas para filtros y selección rápida en el Dashboard. Como máximo 64 cadenas (1–128 caracteres cada una). Por defecto `[]`. |
> > \| `tags` | No | object | Tags clave/valor para reglas personalizadas avanzadas. Los valores pueden ser boolean, string o number. Por defecto `{}`. Ver [Reglas personalizadas](https://firespark.cloud/docs/es/integrations-api/guides/custom-rules). |
> > \| `metadata` | No | object | Metadatos del partner. Por defecto `{}`. |
> >
> > Cada objeto `availability`:
> >
> > | Campo | Requerido | Tipo | Descripción |
> > | - | - | - | - |
> > | `status` | Sí | string | `AVAILABLE`, `HIDDEN` u `OUT_OF_STOCK`. |
> > | `schedules` | No | array \| null | Entradas de horario semanal. `null` u omitido sin restricción. Si se envía, deben incluir los 7 días. Ver [Reglas de validación](https://firespark.cloud/docs/es/integrations-api/guides/menu-sync.md#reglas-de-validación). |
> > | `out_of_stock_until` | No | string | Fecha y hora ISO 8601 opcional cuando el producto vuelve a stock. |
> >
> > Cada entrada de `bundled_items`:
> >
> > | Campo | Requerido | Tipo | Descripción |
> > | - | - | - | - |
> >
> > \| `id` | Sí | string | Identificador externo del producto incluido. |
> > \| `refund_price` | Sí | number | Monto de reembolso si el cliente quita este ítem del bundle. ≥ 0. |
> > \| `included_quantity` | Sí | number | Unidades incluidas por defecto. ≥ 0. |
> >
> > ```json Ejemplo de producto theme={null}
> > {
> >   "id": "hamburguesa-clasica",
> >   "name": { "en_us": "Hamburguesa clásica" },
> >   "description": { "en_us": "Carne Angus con lechuga y tomate." },
> >   "image_url": "https://cdn.example.com/hamburguesa-clasica.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": ["elige-bebida"]
> >   },
> >   "availability": {
> >     "status": "AVAILABLE",
> >     "schedules": null,
> >     "out_of_stock_until": null
> >   },
> >   "metadata": {}
> > }
> > ```
>
> > [accordion] categories
> >
> > Cada categoría en el cuerpo de la solicitud:
> >
> > | Campo | Requerido | Tipo | Descripción |
> > | - | - | - | - |
> >
> > \| `id` | Sí | string | Identificador externo de la categoría. |
> > \| `name` | Sí | object | Nombre visible localizado por locale. |
> > \| `description` | No | object | null | Descripción localizada. `null` si se omite. |
> > \| `image_url` | No | string | URL de imagen de la categoría. Debe ser JPG, PNG o WebP. `null` si se omite. |
> > \| `items` | Sí | array | Entradas ordenadas de la categoría. Al menos 1 entrada. Como máximo 1000. |
> > \| `availability` | Sí | object | Disponibilidad de la categoría. Ver campos abajo. |
> > \| `labels` | No | string\[] | Etiquetas cortas para filtros y selección rápida en el Dashboard. Como máximo 64 cadenas (1–128 caracteres cada una). Por defecto `[]`. |
> > \| `tags` | No | object | Tags clave/valor para reglas personalizadas avanzadas. Los valores pueden ser boolean, string o number. Por defecto `{}`. Ver [Reglas personalizadas](https://firespark.cloud/docs/es/integrations-api/guides/custom-rules). |
> > \| `metadata` | No | object | Metadatos del partner. Por defecto `{}`. |
> >
> > Cada entrada en `items`:
> >
> > | Campo | Requerido | Tipo | Descripción |
> > | - | - | - | - |
> > | `type` | Sí | string | `CATEGORY` o `PRODUCT`. |
> > | `id` | Sí | string | Identificador externo de categoría o producto. |
> >
> > Cada objeto `availability`:
> >
> > | Campo | Requerido | Tipo | Descripción |
> > | - | - | - | - |
> > | `status` | Sí | string | `ACTIVE`, `INACTIVE` o `HIDDEN`. |
> > | `schedules` | No | array \| null | Entradas de horario semanal. `null` u omitido sin restricción. Si se envía, deben incluir los 7 días. Ver [Reglas de validación](https://firespark.cloud/docs/es/integrations-api/guides/menu-sync.md#reglas-de-validación). |
> > | `inactive_until` | No | string | Fecha y hora ISO 8601 opcional cuando la categoría vuelve a estar activa. |
> >
> > ```json Ejemplo de categoría theme={null}
> > {
> >   "id": "principales",
> >   "name": { "en_us": "Platos fuertes" },
> >   "description": { "en_us": "Hamburguesas y sándwiches" },
> >   "image_url": "https://cdn.example.com/mains.jpg",
> >   "items": [{ "type": "PRODUCT", "id": "hamburguesa-clasica" }],
> >   "availability": {
> >     "status": "ACTIVE",
> >     "schedules": null,
> >     "inactive_until": null
> >   },
> >   "metadata": {}
> > }
> > ```
>
> > [accordion] modifier\_groups
> >
> > Cada grupo de modificadores en el cuerpo de la solicitud:
> >
> > | Campo | Requerido | Tipo | Descripción |
> > | - | - | - | - |
> >
> > \| `id` | Sí | string | Identificador externo del grupo de modificadores. Referenciado desde `pricing.modifier_group_ids`. |
> > \| `title` | Sí | object | Título localizado del grupo por locale. |
> > \| `description` | No | object | null | Descripción localizada. `null` si se omite. |
> > \| `display_type` | No | string | `GRID` o `LIST`. |
> > \| `modifier_ids` | Sí | array | UUIDs de productos modificadores en este grupo. 1–100 entradas. Un grupo de una sola opción es válido. |
> > \| `minimum_quantity` | Sí | number | Selecciones mínimas requeridas. ≥ 0. |
> > \| `maximum_quantity` | Sí | number | Selecciones máximas permitidas. 0–1,000,000. |
> > \| `charge_above_quantity` | No | number | Selecciones gratuitas antes de cobrar adicionales. Opcional. |
> > \| `metadata` | No | object | Metadatos del partner. Por defecto `{}`. |
> >
> > ```json Ejemplo de grupo de modificadores theme={null}
> > {
> >   "id": "elige-bebida",
> >   "title": { "en_us": "Elige una bebida" },
> >   "description": null,
> >   "display_type": "LIST",
> >   "modifier_ids": ["cola", "refresco"],
> >   "minimum_quantity": 0,
> >   "maximum_quantity": 1,
> >   "charge_above_quantity": null,
> >   "metadata": {}
> > }
> > ```
>
> > [accordion] recommendations
> >
> > Cada recomendación en el catálogo de nivel superior:
> >
> > | Campo | Requerido | Tipo | Descripción |
> > | - | - | - | - |
> > | `id` | Sí | string | Identificador externo de la recomendación. Debe ser único en la solicitud. |
> > | `title` | Sí | object | Nombre visible localizado por locale. |
> > | `description` | No | object \| null | Descripción localizada. `null` si se omite. |
> > | `type` | Sí | string | `UPSELL`, `CROSS_SELL` o `DOWNSELL`. |
> > | `target_product_id` | Sí | string | `id` del producto a recomendar. Debe existir en `products`. |
> > | `metadata` | No | object | Metadatos del partner. Por defecto `{}`. |
> >
> > ```json Ejemplo de recomendación 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] variant\_groups
> >
> > Cada grupo de variantes:
> >
> > | Campo | Requerido | Tipo | Descripción |
> > | - | - | - | - |
> > | `id` | Sí | string | Identificador externo del grupo de variantes. Referenciado desde `pricing.variant_group_ids`. |
> > | `title` | Sí | object | Pregunta localizada por locale. |
> > | `description` | No | object \| null | Descripción localizada. |
> > | `display_type` | No | string | `GRID` o `LIST`. |
> > | `variants` | Sí | array | Opciones del grupo. Entre 1 y 100. Mira los campos abajo. |
> > | `metadata` | No | object | Metadatos del partner. Por defecto `{}`. |
> >
> > Cada entrada en `variants`:
> >
> > | Campo | Requerido | Tipo | Descripción |
> > | - | - | - | - |
> > | `id` | Sí | string | Identificador de la opción, único entre todos los grupos de variantes. Referenciado desde `pricing.variants[].variant_group_option_ids`. |
> > | `name` | Sí | object | Etiqueta localizada de la opción. |
> > | `description` | No | object \| null | Descripción localizada. |
> > | `image_url` | No | string \| null | URL de imagen de la opción. Debe ser JPG, PNG o WebP; se copia al almacenamiento de Firespark igual que las imágenes de productos. |
> > | `product_ids` | Sí | array | Productos variante a los que puede llevar esta opción. Cada uno debe existir y listar esta opción en su `pricing.variants`. Entre 1 y 1000. |
> > | `metadata` | No | object | Metadatos del partner. Por defecto `{}`. |
> >
> > ```json Example variant group theme={null}
> > {
> >   "id": "size",
> >   "title": { "en_us": "Size" },
> >   "description": null,
> >   "display_type": "GRID",
> >   "variants": [
> >     { "id": "medium", "name": { "en_us": "Medium" }, "description": null, "image_url": null, "product_ids": ["pep-m-thin", "pep-m-thick"], "metadata": {} },
> >     { "id": "large", "name": { "en_us": "Large" }, "description": null, "image_url": null, "product_ids": ["pep-l-thin", "pep-l-thick"], "metadata": {} }
> >   ],
> >   "metadata": {}
> > }
> > ```
>
> > [accordion] pricing
> >
> > Precio de producto en el cuerpo de la solicitud:
> >
> > | Campo | Requerido | Tipo | Descripción |
> > | - | - | - | - |
> >
> > \| `minimum_quantity` | No | number | Unidades mínimas por pedido. ≥ 0. Por defecto `0`. |
> > \| `maximum_quantity` | No | number | Unidades máximas por pedido. 0–1,000,000. Por defecto `1000000`. |
> > \| `is_tax_inclusive` | Sí | boolean | Si `price` incluye impuestos. |
> > \| `tax_rate` | Sí | number | Tasa de impuesto como decimal (por ejemplo `0.15` para 15%). 0–1. |
> > \| `price` | Sí | number | Precio de lista. ≥ 0. |
> > \| `anchor_price` | No | number | Precio de referencia para tachado o comparación. ≥ 0. |
> > \| `anchor_price_percentage` | No | number | Descuento mostrado respecto a `anchor_price`. 0–1. |
> > \| `starting_price` | No | number | null | Precio "Desde" de referencia. Si lo omites, Fire spark calcula durante el sync el precio más bajo que cumple todos los modificadores obligatorios. Envía `null` para no mostrarlo nunca. ≥ 0. |
> > \| `modifiers` | No | array | Overrides de precio y selección por grupo. Por defecto `[]`. |
> > \| `modifier_group_ids` | No | array | Identificadores externos de grupos de modificadores del producto. Por defecto `[]`. |
> > \| `variant_group_ids` | No | array | Grupos de variantes que se preguntan en este producto base. Cada id debe existir en `variant_groups` y aparecer una vez. Un producto con grupos de variantes no puede tener `modifier_group_ids`. Por defecto `[]`. Mira [Variantes](https://firespark.cloud/docs/es/concepts/products.md#variantes-y-grupos-de-variantes). |
> > \| `variants` | No | array | Cuando este producto es una variante: una entrada por base, cada una `{ "product_id": id de la base, "variant_group_option_ids": [...] }` con exactamente una opción de cada grupo de la base. Por defecto `[]`. |
> > \| `recommendations_ids` | No | array | IDs de recomendaciones a mostrar en este producto. Cada id debe existir en `recommendations` de nivel superior. Por defecto `[]`. |
> > \| `recommendations` | No | array | Configuración de visualización por recomendación en este producto. Por defecto `[]`. |
> >
> > Cada objeto en `recommendations` (dentro de `pricing`) vincula una recomendación del catálogo al producto padre:
> >
> > | Campo | Requerido | Tipo | Descripción |
> > | - | - | - | - |
> > | `recommendation_id` | Sí | string | Debe coincidir con un `id` de `recommendations` de nivel superior. |
> > | `default_quantity` | No | number | Cantidad preseleccionada del producto recomendado en esta línea padre. Por defecto `0`. Independiente de `default_quantity` en `pricing.modifiers`. |
> >
> > Cada objeto en `modifiers` configura un grupo de modificadores referenciado en `modifier_group_ids`. Acepta los mismos campos de precio que el producto padre, más campos específicos del modificador:
> >
> > | Campo | Requerido | Tipo | Descripción |
> > | - | - | - | - |
> >
> > \| `modifier_group_id` | Sí | string | Identificador externo del grupo de modificadores. Debe coincidir con un grupo en `modifier_group_ids`. |
> > \| `minimum_quantity` | No | number | Unidades mínimas por pedido para modificadores de este grupo. ≥ 0. Por defecto `0`. |
> > \| `maximum_quantity` | No | number | Unidades máximas por pedido para modificadores de este grupo. 0–1,000,000. |
> > \| `is_tax_inclusive` | Sí | boolean | Si `price` incluye impuestos para modificadores de este grupo. |
> > \| `tax_rate` | Sí | number | Tasa de impuesto como decimal (por ejemplo `0.15` para 15%). 0–1. |
> > \| `price` | Sí | number | Precio de lista para modificadores de este grupo. ≥ 0. |
> > \| `anchor_price` | No | number | Precio de referencia para tachado o comparación. ≥ 0. |
> > \| `anchor_price_percentage` | No | number | Descuento mostrado respecto a `anchor_price`. 0–1. |
> > \| `default_quantity` | Sí | number | Cantidad preseleccionada para modificadores de este grupo. ≥ 0. |
> > \| `charge_above_quantity` | No | number | Selecciones gratuitas antes de cobrar adicionales. Opcional. |
> > \| `display_type` | No | string | Cómo se muestran los modificadores en la UI: `RADIO`, `CHECKBOX` o `QUANTITY`. Opcional. |
> > \| `image_url` | No | string | Imagen de este producto solo en este grupo de modificadores. JPG, PNG o WebP. Omítela para conservar la imagen del producto. Se copia a almacenamiento público igual que las imágenes de producto. |
> >
> > ```json Ejemplo de entrada en modifiers 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",
> >   "image_url": "https://cdn.example.com/cola-in-combo.jpg"
> > }
> > ```

> [tab] Uber Eats
>
> | Campo | Requerido | Descripción |
> | - | - | - |
> | `style` | Sí | Debe ser `"UBER_EATS"`. |
> | `items` | Sí | Ítems y opciones modificadoras. Productos y modificadores comparten este arreglo. Al menos 1 entrada. Como máximo 1000. |
> | `categories` | Sí | Definiciones de categoría con `entities` que referencian IDs de ítems. Al menos 1 entrada. Como máximo 100. |
> | `modifier_groups` | No | Definiciones de grupos de modificadores. Omite o envía `null` si no hay grupos. Como máximo 1000 si se envía. |
> | `recommendations` | No | Catálogo de recomendaciones. Misma forma que `DEFAULT` tras el mapeo. Como máximo 1000 si se envía. Por defecto `[]`. |
> | `menus` | Sí | Definiciones de menú a crear o actualizar. Al menos 1 entrada. Como máximo 10. |
> | `menu_type` | No | `MENU_TYPE_FULFILLMENT_DELIVERY`, `MENU_TYPE_FULFILLMENT_PICK_UP` o `MENU_TYPE_FULFILLMENT_DINE_IN`. Por defecto delivery. |
> | `display_options` | No | Configuración de visualización de la tienda, como `disable_item_instructions`. |
>
> ### Campos del menú
>
> Cada objeto en `menus` usa la forma de menú de Uber Eats:
>
> | Campo | Requerido al crear | Descripción |
> | - | - | - |
> | `id` | Sí | Identificador externo del menú. Clave del upsert. Inmutable tras la creación. |
> | `title` | Sí | `MultiLanguageText` con mapa `translations` (por ejemplo `en_us`). |
> | `subtitle` | No | Subtítulo opcional como `MultiLanguageText`. |
> | `service_availability` | Sí | Disponibilidad semanal con `day_of_week` y `time_periods` (`HH:MM`). |
> | `category_ids` | Sí | IDs de categorías activas mientras este menú está disponible. |
>
> ### MultiLanguageText
>
> Los textos localizados envuelven los valores en `translations`:
>
> ```json theme={null}
> {
>   "translations": {
>     "en_us": "Coffee"
>   }
> }
> ```
>
> > [accordion] items
> >
> > Cada ítem en el cuerpo de la solicitud. Productos de nivel superior y opciones modificadoras son ítems.
> >
> > | Campo | Requerido | Tipo | Descripción |
> > | - | - | - | - |
> >
> > \| `id` | Sí | string | Identificador único del ítem en tu POS o RMS. |
> > \| `title` | Sí | object | `MultiLanguageText` requerido. |
> > \| `description` | No | object | `MultiLanguageText` opcional. |
> > \| `image_url` | No | string | URL pública de imagen. Opcional. |
> > \| `external_data` | No | string | Metadatos del partner (máx. 1024 caracteres). Opcional. |
> > \| `price_info` | Sí | object | Precio en centavos más overrides por contexto. Ver **price\_info**. |
> > \| `quantity_info` | No | object | Restricciones de cantidad y overrides por modificador. Opcional. |
> > \| `modifier_group_ids` | No | object | `{ "ids": [...] }` con overrides por contexto opcionales. Opcional. |
> > \| `tax_info` | No | object | `tax_rate`, `vat_rate_percentage` o `mx_ieps_rate`. Opcional. |
> > \| `suspension_info` | No | object | Reglas de suspensión por falta de stock. Opcional. |
> > \| `bundled_items` | No | array | Ítems siempre incluidos en un combo. Opcional. |
> > \| `dish_info` | No | object | Etiquetas dietéticas y clasificaciones. Opcional. |
> > \| `visibility_info` | No | object | Ventanas horarias de visibilidad del ítem. Opcional. |
> > \| `tax_label_info` | No | object | Etiquetas de clasificación fiscal. Opcional. |
> > \| `product_info` | No | object | GTIN, PLU o identificadores internos del comercio. Opcional. |
> > \| `selling_info` | No | object | Configuración de venta por peso o por unidad. Opcional. |
> >
> > ```json Ejemplo de ítem 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] categories
> >
> > Cada categoría agrupa ítems para mostrarlos en un menú.
> >
> > | Campo | Requerido | Tipo | Descripción |
> > | - | - | - | - |
> >
> > \| `id` | Sí | string | Identificador único de la categoría. |
> > \| `title` | Sí | object | `MultiLanguageText` requerido. |
> > \| `subtitle` | No | object | `MultiLanguageText` opcional. |
> > \| `image_url` | No | string | URL de imagen de la categoría. Debe ser JPG, PNG o WebP. Opcional. |
> > \| `entities` | Sí | array | Entidades de menú, usualmente `{ "type": "ITEM", "id": "..." }`. |
> >
> > ```json Ejemplo de categoría theme={null}
> > {
> >   "id": "Drinks",
> >   "title": { "translations": { "en_us": "Drinks" } },
> >   "entities": [{ "type": "ITEM", "id": "Coffee" }]
> > }
> > ```
>
> > [accordion] modifier\_groups
> >
> > Los grupos de modificadores adjuntan ítems seleccionables a un ítem padre.
> >
> > | Campo | Requerido | Tipo | Descripción |
> > | - | - | - | - |
> >
> > \| `id` | Sí | string | Identificador único del grupo de modificadores. |
> > \| `title` | Sí | object | `MultiLanguageText` requerido. |
> > \| `external_data` | No | string | Metadatos del partner (máx. 1024 caracteres). Opcional. |
> > \| `modifier_options` | Sí | array | Entradas `{ "type": "ITEM", "id": "..." }`. Todas deben ser ítems. |
> > \| `quantity_info` | No | object | Min/max del grupo, charge-above y reglas de selección única. Opcional. |
> > \| `display_type` | No | string | `expanded` o `collapsed`. Opcional. |
> >
> > ```json Ejemplo de grupo de modificadores 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] price\_info
> >
> > El precio de ítems sigue la Uber Eats Menu API. Los montos son enteros en centavos.
> >
> > | Campo | Requerido | Tipo | Descripción |
> > | - | - | - | - |
> >
> > \| `price` | Sí | integer | Precio de lista en centavos. Requerido cuando `price_info` está presente. |
> > \| `core_price` | No | integer | Valor intrínseco usado en reembolsos de opciones en combos. Opcional. |
> > \| `in_store_price` | No | integer | Precio en tienda antes de descuentos. Opcional. |
> > \| `container_deposit` | No | integer | Metadatos de depósito por envases retornables. Opcional. |
> > \| `overrides` | No | array | Overrides de precio por contexto (`MENU`, `ITEM`, `MODIFIER_GROUP`). Opcional. |
> > \| `priced_by_unit` | No | object | Unidad base del precio (conteo, peso, volumen o longitud). Opcional. |
> >
> > Cada entrada de override:
> >
> > | Campo | Requerido | Tipo | Descripción |
> > | - | - | - | - |
> >
> > \| `context_type` | Sí | string | `MENU`, `ITEM` o `MODIFIER_GROUP`. |
> > \| `context_value` | Sí | string | ID de la entidad de contexto. |
> > \| `price` | Sí | integer | Precio override en centavos. |
> > \| `core_price` | No | integer | Valor intrínseco override en centavos. Opcional. |

## Response

El endpoint responde de forma asíncrona. Fire spark inicia una sincronización de menú en segundo plano y responde de inmediato.

| Estado | Cuándo | Cuerpo |
| - | - | - |
| `201` | Sync aceptado y encolado | `{ "message": "Sync accepted: …" }` — conteos de productos, menús, categorías y grupos de modificadores recibidos |
| `202` | Ya hay un sync en curso para la misma tienda, canal y marca | `{ "message": "Sync already in progress for this store/channel.", "runId": "…" }` — `runId` identifica el workflow activo |
| `409` | Ya hay un sync en curso o completado para el mismo release y scope | `{ "message": "Sync already exists for this release and scope.", "menuSyncId": "…" }` — reintenta con el mismo `x-release-id` solo si el sync previo está en `FAILED` o `CANCELLED` |

Trata `202` como éxito: el catálogo ya se está procesando. Reintenta solo cuando el sync actual termine o falle.

Trata `409` como éxito al reenviar un release que ya completó. Si el sync previo para ese release y scope está en `FAILED` o `CANCELLED`, reenvía el mismo `x-release-id`.

## Límites de tasa

`GET` y `PUT /api/integrations/v1/menus` comparten un límite de **30 solicitudes por 10 segundos por comercio**. La clave es el `merchant_id` de tu access token.

Envía **una solicitud a la vez** por comercio — espera la respuesta antes de iniciar la siguiente. No paralelices upserts ni lecturas de menú para el mismo comercio.

Cada respuesta incluye headers de rate limit para saber cuándo puedes emitir la siguiente solicitud:

| Header | Descripción |
| - | - |
| `X-RateLimit-Limit` | Máximo de solicitudes por ventana (`30`) |
| `X-RateLimit-Reset` | Segundos hasta que se reinicia la ventana actual (`10`) |
| `X-RateLimit-Remaining` | Solicitudes restantes en la ventana — presente en `429` (`0`) |
| `Retry-After` | Segundos de espera antes de reintentar — solo en respuestas `429` |

Si superas el límite, la API responde `429 Too Many Requests`. Espera `Retry-After` segundos (o `X-RateLimit-Reset`) antes de enviar otra solicitud para ese comercio.

## Respuestas de error

\| Estado | Descripción                                                                                                                                                                           |
\| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --- |
\| `400`  | Error de validación — valores inválidos, campos requeridos ausentes o forma semanal inválida. El cuerpo es `{ "error": "Invalid fields", "details": { "<path>": "<message>" } }`. Ver [Reglas de validación](https://firespark.cloud/docs/es/integrations-api/guides/menu-sync.md#reglas-de-validación). |
\| `401`  | Access token ausente o inválido.                                                                                                                                                      |
\| `403`  | El token no incluye el scope `menus:write`.                                                                                                                                           |
\| `409`  | La tienda, canal o fulfillment referenciados no existen para este comercio, o ya existe un sync para el mismo release y scope.                                                        |
\| `429`  | Límite de tasa excedido — espera `Retry-After` / `X-RateLimit-Reset` antes de reintentar. Ver [Límites de tasa](#límites-de-tasa).                                                    |     |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.