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.

🚧

Beta

The 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

ConceptID prefixWhat it is
Orgorg123The top-level tenant.
Propertyp123A physical location. Properties belong to an org.
Sales channelsc123A 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 revisionrevisionIdA 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:

  1. Assign the menu to a channel (step 3) — sets menuId on the channel so it knows which menu it serves.
  2. 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 menuId and the latest revisionId.
  • 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.
  • curl and jq (or any HTTP client and JSON parser).

All examples use:

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.env holds 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.env

When 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.env

Reference: 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.env

If 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:

  1. Assign the menu to a sales channel — sets menuId on the channel so the channel knows which menu it serves. This is what this step does.
  2. 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.json

Repeat 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

updateSalesChannelById overwrites the channel with the body you send. If you POST only { "menuId": "..." }, you risk blanking the channel's name and other settings. Always GET the channel first and merge menuId into 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/published can 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-Agent header format.