Skip to main content
Menu sync is how your POS or RMS becomes the source of truth for what customers can order on every connected channel. You push catalog entities once, then Fire spark composes what each surface shows — different assortments by channel, store, fulfillment type, or schedule without maintaining a separate integration per partner.

New to Fire spark?

Read the Integrations API introduction for authentication, webhooks, and the full integration model.

Where menu sync sits in the stack

Your operational system owns items, prices, and availability. Fire spark sits in the middle and publishes composed menus to owned channels and aggregators. When a customer orders, line item id values from the composed menu appear on the order payload and on the POS ticket after order.injected. Keep external product ids stable across menu sync and order injection.

Prerequisites

Create stores, channels, and fulfillment types in the Fire spark dashboard before your first menu upsert. Each resource uses the same external id you use in your POS or RMS.
1

Obtain an access token

Authenticate with OAuth 2.0 client credentials. Menu endpoints require menus:read and menus:write. See Token.
2

Confirm fulfillment options

Verify delivery, pickup, dine-in, and any custom fulfillment types exist in the dashboard. See List fulfillment.
3

Confirm stores

Verify each merchant location and its external ids. Stores carry channel availability and local rules. See List stores.
4

Confirm channels

Verify the channels configured for the merchant — app, web, kiosk, aggregators, and others. See List channels.
Menu upserts return 409 when referenced fulfillment_id, store_id, channel_id, or brand_id values do not exist for the merchant. Create stores, channels, fulfillment, and brands in the dashboard before the first sync.

How menu composition works

A composed menu is the catalog a customer sees for one selling context. Fire spark scopes each menu to a store, channel, and fulfillment triple. You can add schedules so the same triple shows different categories at breakfast vs lunch. You update the POS once. Your integration decides how many composed menus to push and which categories and products each menu includes.
For background on why menus are composed per context, see Menus. For category-level availability by channel and store, see Categories.

Catalog building blocks

Every composed menu is built from four entity types you send in the same request.

Products

A product is anything that can appear on a ticket: a standalone burger, a combo, or a modifier option such as “Cola.” Send products in the top-level products array on Upsert menus. Each product needs a stable external id — the same value your kitchen expects on injected orders.

Categories

Categories organize products for browsing. Each category lists items — products and nested categories visible in that section on this menu. You can hide a category on one channel while keeping it on another by composing different menus — not by duplicating product masters in your POS.

Nested subcategories

Menu sync models subcategories as a reference tree, not with a parent_id field. Declare every category in the flat categories[] array and build the tree with type: "CATEGORY" entries in the parent’s items. Display order. Array order is display order: You may mix CATEGORY and PRODUCT entries in the same items array; array order defines how they appear together in the UI. Recommended convention: put products on leaf categories and use intermediate categories only for navigation. Validation does not require this, but it avoids screens where customers see subcategories and products at the same level unintentionally. Depth limit: up to 3 levels counting the root linked from the menu — root (1) → subcategory (2) → sub-subcategory (3). Deeper nesting fails validation. See the full payload in Nested subcategories.
When reading a composed menu with List menus, categories[] includes only the root categories from category_ids. Child category ids appear in the parent’s items with type: "CATEGORY" — to resolve each child’s name and products, keep your sync payload or walk the tree from the root in your integration.

Modifier groups and modifiers

Customization is modeled in two layers: Modifiers are products. Each entry in a group’s modifier_ids array is the external id of another product in the same payload. That means:
  • A drink sold standalone and as a combo modifier shares one product record.
  • When you 86 still water in the POS, set availability.status to OUT_OF_STOCK on that product — the modifier disappears from every group that references it.
  • Kitchen tickets use real product ids, not free-text notes.
Attach groups to a parent product through pricing.modifier_group_ids. Set selection rules on the group: Per-modifier price, default quantity, display type (RADIO, CHECKBOX, QUANTITY), and charge rules live on the modifier product under pricing.modifiers — one entry per modifier_group_id where that product appears as an option. See Modifier display types for the quantity rules that determine each display_type, validation errors, and examples.
Use the same external ids in menu sync, order line items, and POS mapping. When order.injected fires, modifier lines reference these ids in the order payload.

Defaults first, override where needed

Omitted scope means any (__ANY__). You can publish one baseline menu and add narrower upserts only where your operation differs — instead of one API call per store, channel, and schedule combination. Add fulfillment_id, store_id, channel_id, brand_id, or schedule entries only when a specific context needs something different from that baseline. More specific upserts apply to that context alone; they do not remove the default for contexts you left untouched.
A merchant-wide default is optional but recommended when the customer has not chosen a store or fulfillment type yet — for example pickup vs delivery while browsing before checkout. Fire spark does not require a baseline upsert before scoped overrides.
Suggested sync order (when you use a baseline):
  1. Push a merchant-wide defaultPUT /menus with no query parameters and set "schedules": null. Send your full product catalog.
  2. Push overrides — repeat the upsert with ?store_id=…, ?channel_id=…, ?fulfillment_id=…, or a schedules array only where the catalog, pricing, or hours diverge from the baseline.
You can also sync only scoped menus if every selling context you support is already known.

Sync workflow

1

Choose the selling context

Decide whether this upsert is the merchant-wide default (omit scope query parameters) or a targeted override for one store, channel, brand, and/or fulfillment type. See Upsert menus for query parameters.
2

Build the payload

Send shared catalog entities (products, categories, modifier_groups) plus up to 10 menus entries in one request. Each menu object references category_ids and a fulfillment_id.
3

Upsert the menu

Call PUT /menus for the baseline, or add ?store_id=… and/or ?channel_id=… for overrides. Use "style": "DEFAULT" for owned channels, or "style": "UBER_EATS" for Uber Eats Menu API v2 shape. See Upsert menus.
4

Verify the result

Read back composed menus with List menus. Pass the same store_id and channel_id you used on the upsert. Confirm ids match your POS mapping.
5

Push incremental changes

For single-item updates — price changes, 86s, channel-specific overrides — call Update product instead of resending the full menu.

Multiple channels, stores, and fulfillment modes

Each composed menu is keyed by context:
When fulfillment_id, store_id, channel_id, or brand_id is omitted, that dimension is all. Use that to your advantage: one baseline upsert with the full catalog, then overrides only where needed.

Item-level overrides across contexts

When one product needs different pricing or availability per channel, store, or fulfillment type without replacing whole menus, call Update product with ?store_id=…, ?channel_id=…, ?fulfillment_id=…, and/or ?menu_id=… when several menus share the same scope. The body uses the same menu product shape as Upsert menus — top-level pricing and availability, not a nested channels object. This matches how commercial teams think: push a baseline product once, then send a scoped update so Cola is $0 on the combo in the app at downtown, while the default catalog keeps a different price elsewhere. Edits made from the dashboard or using our sync API persist across normal upserts. To override these changes, send x-force: true on Upsert menus. To stage a sync without publishing yet, send x-publish: false and publish from the dashboard when ready.

Schedules

Schedules restrict when a menu or individual entity is orderable. Use null or omit schedules when the menu is available around the clock — Fire spark treats that as 24 hours, every day. Set a schedules array only when orderability is limited to specific days or time windows. When you send a schedules array, Fire spark validates the full weekly shape: Times in start_time and end_time use ISO time strings (for example 11:00:00). Set closed: true when the period is a closed window (start_time / end_time may be omitted). Use null dates for the always-on fallback; set start_date / end_date for a more specific window. An empty periods array means closed all day. Multiple menus in the same sync may share overlapping schedule windows. Get menu returns every composed menu whose schedule matches the current time in data. When none are in schedule, it returns the menu or menus whose next window starts soonest. Menus with schedules: null are always considered active. Common patterns:
  • Always-on default — Omit schedules (null) on the baseline menu; add scheduled overrides only for dayparts that differ.
  • Daypart menus — Separate menu ids for breakfast and lunch on the same store + channel + fulfillment, each with its own schedule and category_ids.
  • Closed windows — Keep open hours on a day and add a closed: true period (optionally with start_date / end_date) for a holiday or maintenance block.
  • Seasonal categories — Keep the menu always active; schedule the category or product availability instead.
  • Temporary 86 — Prefer availability.status: OUT_OF_STOCK with out_of_stock_until for item-level pauses without rebuilding schedules.

Validation rules

Fire spark validates the full Upsert menus payload before queuing a sync. These rules apply to the DEFAULT style.

Products

Categories

Nesting (see Nested subcategories):

Modifier groups

Recommendations

When a sync defines multiple menus, Fire spark composes each menu from the shared catalog and keeps only recommendations whose target_product_id is reachable in that menu — the same composition rules as products and modifier_groups.

Labels and tags

Products and categories accept optional labels and tags. Omit either field on existing syncs — Fire spark defaults to [] and {} and does not reject payloads that leave them out.
Category availability uses ACTIVE, INACTIVE, and HIDDEN — not the product values AVAILABLE, UNAVAILABLE, and OUT_OF_STOCK. Categories also use inactive_until, not out_of_stock_until.

Sync styles

Upsert menus accepts a discriminated union on style:
Fire spark native format for owned channels (app, web, kiosk, call center). Use field names products, categories, modifier_groups, and localized name objects keyed by locale (for example en_us).Best when you control the customer UI through the Storefront API or a custom front end.

Full sync vs incremental updates

Menu id values are immutable after creation. Send the same external menu id on every sync for a given selling context.

Map ids back to your POS

Use external ids for cross-system mapping. Use *_uid fields only when referencing Fire spark resources in other API calls.

Upsert menus

Create or update composed menus

List menus

Read menus for reconciliation

Update product

Push item-level changes

Order webhooks

Receive orders with the same product ids

List stores

Read store external ids before menus

List fulfillment

Read fulfillment external ids before menus

Example payloads

Copy-paste payloads for common menu sync scenarios:

Simple menu

3 products, 1 category, 1 modifier group

Nested categories

Root category with two subcategories

Multi-schedule

2 daypart menus, no modifier groups

Multi-level modifiers

3 nested modifier group levels

Modifier overrides

Price, min, and max overrides

Product schedule

Happy-hour availability on one product

Product recommendations

Combo upsell, side cross-sell, and combo downsell