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 itemid 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 externalid 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.
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-levelproducts 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 listsitems — 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 aparent_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.statustoOUT_OF_STOCKon that product — the modifier disappears from every group that references it. - Kitchen tickets use real product ids, not free-text notes.
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.
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.
Suggested sync order (when you use a baseline):
- Push a merchant-wide default —
PUT /menuswith no query parameters and set"schedules": null. Send your full product catalog. - Push overrides — repeat the upsert with
?store_id=…,?channel_id=…,?fulfillment_id=…, or aschedulesarray only where the catalog, pricing, or hours diverge from the baseline.
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: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: trueperiod (optionally withstart_date/end_date) for a holiday or maintenance block. - Seasonal categories — Keep the menu always active; schedule the category or product
availabilityinstead. - Temporary 86 — Prefer
availability.status: OUT_OF_STOCKwithout_of_stock_untilfor item-level pauses without rebuilding schedules.
Validation rules
Fire spark validates the full Upsert menus payload before queuing a sync. These rules apply to theDEFAULT style.
Products
Categories
Nesting (see Nested subcategories):
Menu definitions
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 optionallabels and tags. Omit either field on existing syncs — Fire spark defaults to [] and {} and does not reject payloads that leave them out.
Sync styles
Upsert menus accepts a discriminated union onstyle:
- DEFAULT
- UBER_EATS
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
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.
Related endpoints
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