Publish your first menu
Take a saved menu live — list properties and sales channels, run a publish check, publish a revision, and verify it's live, all with curl.
Publish your first menu
This guide picks up where Create your first menu leaves off. You have a saved menu with a menuId and the latest revisionId — now you'll pick the sales channels to expose it on, assign the menu to them, validate the revision, and publish it so it's live for customers. Everything below uses curl.
If you're editing an existing menu rather than publishing a brand-new one, the same steps apply: validate the revision, then publish it. For the wider lifecycle (drafts, exporting, snoozing, rolling back), see Use v3 menus as an integrator.
BetaThe Menu Management API is currently in beta and may change without notice. Pin your integration to specific revisions and be defensive about unknown fields in responses.
Working with an AI assistant?Each step below has a "💬 Ask an AI" block (collapsed by default — click the heading to expand) with a prompt you can paste into Claude, ChatGPT, Cursor, Copilot, or any other AI tool. Hover the code block to reveal the copy button. Every prompt uses placeholders like
<MY_ORG_ID>instead of real values, so it's safe to copy as-is. Substitute your real values after the AI returns the command. Never paste secrets (client secret, access token) into a chat window.
How publishing fits together
| Concept | ID prefix | What it is |
|---|---|---|
| Org | org123 | The top-level tenant. |
| Property | p123 | A physical location. Properties belong to an org. |
| Sales channel | sc123 | A specific way customers order at a property — the Flipdish app, a marketplace like Deliveroo or Uber Eats, a kiosk, etc. Sales channels belong to a property. |
| Menu revision | revisionId | A snapshot of the menu document at a point in time. You publish a specific revision, not "the menu". |
Menus are not attached to properties directly. A menu lives at the org level, and making it "live" at a property takes two distinct actions on that property's sales channels:
- Assign the menu to a channel (step 3) — sets
menuIdon the channel so it knows which menu it serves. - Publish a revision to that channel (step 5) — pushes a specific snapshot of the menu live.
Assign before you publish — publishing a revision to a channel that has no menu assigned fails with a "No storefronts are associated with this menu" error.
Org (org123)
├── Property (p123)
│ ├── Sales Channel (sc123) — Flipdish app ◀─assign menuId─┐
│ └── Sales Channel (sc124) — Deliveroo ◀─assign menuId─┤
└── Menu (menuId) ──────────────────────────────────────────────┘
└── Revision 7 ────publish────▶ [sc123, sc124]
Prerequisites
- A saved menu — see Create your first menu if you don't have one yet. You'll need its
menuIdand the latestrevisionId. - An access token. If you've just come from the create guide you already have one; otherwise re-run step 1 of the create guide.
- An org with at least one property that has at least one sales channel. If a property has no sales channels yet, step 2 shows how to create one.
curlandjq(or any HTTP client and JSON parser).
All examples use:
- Base URL:
https://api.flipdish.co - Auth header:
Authorization: Bearer <access_token> - A
User-Agentheader — it's required on every request.
Set up your session
This guide reuses the same .flipdish.env pattern as the create guide so values like your access token, orgId, menuId, and revisionId survive across shell calls — important inside Claude Code, where each Bash invocation is a fresh shell.
If you came straight from Create your first menu, you already have .flipdish.env populated with FLIPDISH_BASE_URL, FLIPDISH_USER_AGENT, FLIPDISH_CLIENT_ID, FLIPDISH_CLIENT_SECRET, TOKEN, ORG_ID, MENU_ID, and REVISION_ID. Skip ahead to step 1.
If you're landing here directly, set up .flipdish.env first by following Set up your session and step 1 — Get an access token in the create guide. Then come back and add the MENU_ID and REVISION_ID of the menu you want to publish:
echo "export ORG_ID=org123" >> .flipdish.env
echo "export MENU_ID='<your-menu-uuid>'" >> .flipdish.env
echo "export REVISION_ID='<your-revision-id>'" >> .flipdish.env # an integer, e.g. 2 — not a UUID
.flipdish.envholds your client secret and a bearer token — keep it gitignored and never paste its contents into a chat window.
Source it at the top of every shell command in this guide:
source ./.flipdish.envWhen a step captures a new value (PROPERTY_ID, SALES_CHANNEL_IDS), append it back so subsequent steps and Claude Code Bash calls pick it up automatically.
Step 1 — List properties in the org
A property is a physical location in your org. You need at least one so you have sales channels to publish to.
source ./.flipdish.env
curl --silent --request GET \
--url "$FLIPDISH_BASE_URL/orgManagement/orgs/$ORG_ID/properties" \
--header "Authorization: Bearer $TOKEN" \
--header "User-Agent: $FLIPDISH_USER_AGENT" \
| jq -r '.data[] | "\(.propertyId)\t\(.name)"'Each property's identifier is exposed as propertyId, not id — Org Management entities (orgs, properties, brands, sales channels) use fully qualified field names (orgId, propertyId, brandId, salesChannelId). Using .id here would silently return null for every row.
Pick a property and persist its propertyId (shape: p123):
echo "export PROPERTY_ID=p123" >> .flipdish.envReference: get properties by org. If you need to create one first, see create property for org.
💬 Ask an AI to list my properties and persist my pick — click to expand. Safe to copy: no secrets in the prompt. Hover the code block to copy.
I have a `.flipdish.env` file that exports FLIPDISH_BASE_URL,
FLIPDISH_USER_AGENT, TOKEN, and ORG_ID. Don't ask me for any of those.
Show me a bash snippet that:
1. Sources ./.flipdish.env.
2. Lists properties under my org via
GET $FLIPDISH_BASE_URL/orgManagement/orgs/$ORG_ID/properties
with Authorization: Bearer $TOKEN and User-Agent: $FLIPDISH_USER_AGENT.
3. Pipes through jq to print one property per line as
"propertyId<TAB>name". The property identifier in the response is
`propertyId`, not `id` — `.id` would return null for every row.
After I tell you which propertyId I picked, give me a one-liner that
appends `export PROPERTY_ID=<picked>` to .flipdish.env.Step 2 — List the sales channels you can publish to
A sales channel is a specific ordering surface at a property — e.g. the Flipdish web app, a marketplace like Deliveroo, a self-service kiosk. You'll publish your menu revision to one or more of these.
source ./.flipdish.env
curl --silent --request GET \
--url "$FLIPDISH_BASE_URL/orgManagement/orgs/$ORG_ID/properties/$PROPERTY_ID/salesChannels" \
--header "Authorization: Bearer $TOKEN" \
--header "User-Agent: $FLIPDISH_USER_AGENT" \
| jq -r '.data[] | "\(.salesChannelId)\t\(.name)\t\(.venueCode)"'Same Org Management naming convention as properties — the sales channel identifier is exposed as salesChannelId, not id. .id would silently return null for every row.
Pick one or more sales channels and persist their salesChannelIds as a JSON array string. You'll plug it into the publish call in step 4 with no further escaping:
echo 'export SALES_CHANNEL_IDS_JSON='\''["sc123", "sc124"]'\' >> .flipdish.envIf the property has no sales channels yet, create one first:
source ./.flipdish.env
curl --silent --request POST \
--url "$FLIPDISH_BASE_URL/orgManagement/orgs/$ORG_ID/properties/$PROPERTY_ID/salesChannels" \
--header "Authorization: Bearer $TOKEN" \
--header 'Content-Type: application/json' \
--header "User-Agent: $FLIPDISH_USER_AGENT" \
--data '{ "name": "Flipdish web", "venueCode": "Flipdish" }'Reference: list sales channels, create sales channel.
💬 Ask an AI to list my sales channels and persist my picks — click to expand. Safe to copy: no secrets in the prompt. Hover the code block to copy.
I have a `.flipdish.env` file that exports FLIPDISH_BASE_URL,
FLIPDISH_USER_AGENT, TOKEN, ORG_ID, and PROPERTY_ID. Don't ask me for
any of those.
Show me a bash snippet that:
1. Sources ./.flipdish.env.
2. Lists sales channels on my property via
GET $FLIPDISH_BASE_URL/orgManagement/orgs/$ORG_ID/properties/$PROPERTY_ID/salesChannels
with Authorization: Bearer $TOKEN and User-Agent: $FLIPDISH_USER_AGENT.
3. Pipes through jq to print one channel per line as
"salesChannelId<TAB>name<TAB>venueCode". The sales channel
identifier is `salesChannelId`, not `id` — `.id` would return null
for every row.
After I tell you which salesChannelIds I picked (one or more), give me
a one-liner that appends `export SALES_CHANNEL_IDS_JSON='[...]'` to
.flipdish.env, where the value is a JSON array literal of the chosen
salesChannelIds.Step 3 — Assign the menu to your sales channel(s)
There are two linking actions, and they're easy to confuse:
- Assign the menu to a sales channel — sets
menuIdon the channel so the channel knows which menu it serves. This is what this step does. - Publish a revision to that channel (step 5) — pushes a specific revision of that menu live.
You must assign first. If you skip straight to publish, the publish call fails with:
{ "error": { "message": "No storefronts are associated with this menu. You cannot publish a menu without any storefronts", "code": "invalid_request" } }Assign the menu with updateSalesChannelById — a POST to the individual sales-channel path that sets menuId on the channel. This endpoint replaces the channel's fields, so fetch the channel first and carry over its existing name, priceBandId, and phone settings, then add menuId:
source ./.flipdish.env
# One of the salesChannelIds from step 2:
SALES_CHANNEL_ID=sc123
# Fetch the channel, set menuId, drop null fields, and POST it back
curl --silent --request GET \
--url "$FLIPDISH_BASE_URL/orgManagement/orgs/$ORG_ID/properties/$PROPERTY_ID/salesChannels/$SALES_CHANNEL_ID" \
--header "Authorization: Bearer $TOKEN" \
--header "User-Agent: $FLIPDISH_USER_AGENT" \
| jq --arg menuId "$MENU_ID" \
'.data | { name, priceBandId, phoneNumber, usePropertyPhoneNumber, menuId: $menuId } | with_entries(select(.value != null))' \
> channel-update.json
curl --silent --request POST \
--url "$FLIPDISH_BASE_URL/orgManagement/orgs/$ORG_ID/properties/$PROPERTY_ID/salesChannels/$SALES_CHANNEL_ID" \
--header "Authorization: Bearer $TOKEN" \
--header 'Content-Type: application/json' \
--header "User-Agent: $FLIPDISH_USER_AGENT" \
--data @channel-update.jsonRepeat for every sales channel you intend to publish to. The .data envelope and the salesChannelId (not id) naming are the same Org Management conventions as steps 1–2.
This is a full update, not a patch
updateSalesChannelByIdoverwrites the channel with the body you send. If youPOSTonly{ "menuId": "..." }, you risk blanking the channel's name and other settings. Always GET the channel first and mergemenuIdinto the existing values, as above.
Reference: update sales channel.
💬 Ask an AI to assign my menu to a sales channel — click to expand. Safe to copy: no secrets in the prompt. Hover the code block to copy.
I have a `.flipdish.env` file that exports FLIPDISH_BASE_URL,
FLIPDISH_USER_AGENT, TOKEN, ORG_ID, PROPERTY_ID, and MENU_ID. Don't ask
me for any of those.
I need to assign my menu to a sales channel before I can publish it
(otherwise publish fails with "No storefronts are associated with this
menu"). The endpoint is updateSalesChannelById and it REPLACES the
channel, so existing fields must be preserved.
Show me a bash snippet that, for a sales channel id I provide:
1. Sources ./.flipdish.env.
2. GETs
$FLIPDISH_BASE_URL/orgManagement/orgs/$ORG_ID/properties/$PROPERTY_ID/salesChannels/<id>
with Authorization: Bearer $TOKEN and User-Agent: $FLIPDISH_USER_AGENT.
3. Uses jq to build a body from the response's `.data` that carries
over name, priceBandId, phoneNumber, and usePropertyPhoneNumber,
adds menuId = $MENU_ID, and drops any null fields.
4. POSTs that body back to the same salesChannels/<id> path with
Content-Type: application/json.Step 4 — Run a publish check
Before exposing the revision to customers, validate it. The publish-check endpoint returns any structural or business-rule problems that would block a publish — catch them here, not after it's live.
source ./.flipdish.env
curl --silent --request GET \
--url "$FLIPDISH_BASE_URL/menuManagement/orgs/$ORG_ID/menus/$MENU_ID/revisions/$REVISION_ID/publishCheck" \
--header "Authorization: Bearer $TOKEN" \
--header "User-Agent: $FLIPDISH_USER_AGENT" \
| jq .Fix any reported problems with another PUT (see step 5 of the create guide) and re-run the check against the new revisionId. Remember: that step appends a fresh export REVISION_ID= line to .flipdish.env, so the next source picks it up.
Reference: publish check.
💬 Ask an AI to run the publish check — click to expand. Safe to copy: no secrets in the prompt. Hover the code block to copy.
I have a `.flipdish.env` file that exports FLIPDISH_BASE_URL,
FLIPDISH_USER_AGENT, TOKEN, ORG_ID, MENU_ID, and REVISION_ID. Don't ask
me for any of those.
Show me a bash snippet that:
1. Sources ./.flipdish.env.
2. Runs a publish check via
GET $FLIPDISH_BASE_URL/menuManagement/orgs/$ORG_ID/menus/$MENU_ID/revisions/$REVISION_ID/publishCheck
with Authorization: Bearer $TOKEN and User-Agent: $FLIPDISH_USER_AGENT.
3. Pretty-prints the response with jq so I can read any blocking errors.Step 5 — Publish to your sales channels
With the menu assigned to your channels (step 3), publishing pushes a specific revision live to them. The publish is atomic across all the selected channels.
source ./.flipdish.env
curl --silent --request POST \
--url "$FLIPDISH_BASE_URL/menuManagement/orgs/$ORG_ID/menus/$MENU_ID/revisions/$REVISION_ID/publish" \
--header "Authorization: Bearer $TOKEN" \
--header 'Content-Type: application/json' \
--header "User-Agent: $FLIPDISH_USER_AGENT" \
--data "{ \"selectedSalesChannelIds\": $SALES_CHANNEL_IDS_JSON }"selectedSalesChannelIds must contain at least one ID. After a successful response, the menu is live on those sales channels.
Rolling back is the same operation with an earlier revisionId (revision IDs are sequential integers, not UUIDs) — echo "export REVISION_ID=<earlier-revision-id>" >> .flipdish.env and re-run this step.
Reference: publish revision.
💬 Ask an AI to publish the revision to my sales channels — click to expand. Safe to copy: no secrets in the prompt. Hover the code block to copy.
I have a `.flipdish.env` file that exports FLIPDISH_BASE_URL,
FLIPDISH_USER_AGENT, TOKEN, ORG_ID, MENU_ID, REVISION_ID, and
SALES_CHANNEL_IDS_JSON (a JSON array literal like '["sc123","sc124"]').
Don't ask me for any of those.
Show me a bash snippet that:
1. Sources ./.flipdish.env.
2. POSTs to
$FLIPDISH_BASE_URL/menuManagement/orgs/$ORG_ID/menus/$MENU_ID/revisions/$REVISION_ID/publish
with body { "selectedSalesChannelIds": $SALES_CHANNEL_IDS_JSON },
Authorization: Bearer $TOKEN, Content-Type: application/json, and
User-Agent: $FLIPDISH_USER_AGENT.
3. Pretty-prints the response with jq.Step 6 — Verify it's live
The publish in step 5 already succeeded if it returned 2xx. This step is a best-effort confirmation that the menu shows up in the published list for the org:
source ./.flipdish.env
curl --silent --request GET \
--url "$FLIPDISH_BASE_URL/menuManagement/orgs/$ORG_ID/menus/published" \
--header "Authorization: Bearer $TOKEN" \
--header "User-Agent: $FLIPDISH_USER_AGENT" \
| jq --arg menuId "$MENU_ID" '.data[] | select(.id == $menuId)'You should see your menu's entry with the sales channels it's published to.
This verification endpoint is beta and may error
GET .../menus/publishedcan return a server-side error such as{ "error": { "message": "c.replace is not a function", "code": "unknown_error" } }. A failure here does not mean the publish failed — the publish in step 5 is the source of truth. If verification errors, treat it as a known beta issue, don't re-publish on the strength of it, and re-check later.
💬 Ask an AI to verify my menu is live — click to expand. Safe to copy: no secrets in the prompt. Hover the code block to copy.
I have a `.flipdish.env` file that exports FLIPDISH_BASE_URL,
FLIPDISH_USER_AGENT, TOKEN, ORG_ID, and MENU_ID. Don't ask me for any
of those.
Show me a bash snippet that:
1. Sources ./.flipdish.env.
2. GETs $FLIPDISH_BASE_URL/menuManagement/orgs/$ORG_ID/menus/published
with Authorization: Bearer $TOKEN and User-Agent: $FLIPDISH_USER_AGENT.
3. Uses jq with --arg menuId "$MENU_ID" to filter to just my menu's
entry, showing its revisionId and the sales channels it's published to.Where to go next
- Use v3 menus as an integrator — the full editing, revisioning, exporting, and snoozing lifecycle, including the v3-specific revision behaviour (revision bumps on draft creation, not every save).
- Menu Structure v3.0 — the complete menu document shape.
- Error Handling — structured error responses the API returns.
- User Agents — the required
User-Agentheader format.
