Flavoso APIAPI-ReferenzSpeisekarte
Speisekarte
Die ganze Karte lesen, mit PUT /menu in einem Aufruf abgleichen, Ausverkauft melden und einzelne Kategorien, Gerichte, Optionsgruppen und Optionen anlegen, ändern und löschen. Mehr dazu unter Speisekarte abgleichen.
Endpunkte
- get
/menuRetrieve the full menu - put
/menuSync the whole menu from your system - post
/menu/availabilityMark many products and options sold out or available - get
/menu/categoriesList categories - post
/menu/categoriesCreate a category - get
/menu/categories/{id}Retrieve a category - patch
/menu/categories/{id}Update a category - delete
/menu/categories/{id}Delete a category - get
/menu/productsList products - post
/menu/productsCreate a product - get
/menu/products/{id}Retrieve a product - patch
/menu/products/{id}Update a product - delete
/menu/products/{id}Delete a product - get
/menu/option-groupsList option groups - post
/menu/option-groupsCreate an option group - get
/menu/option-groups/{id}Retrieve an option group - patch
/menu/option-groups/{id}Update an option group - delete
/menu/option-groups/{id}Delete an option group - post
/menu/option-groups/{id}/optionsAdd an option to an option group - get
/menu/optionsList options - get
/menu/options/{id}Retrieve an option - patch
/menu/options/{id}Update an option - delete
/menu/options/{id}Delete an option
Retrieve the full menu
get/menu
- Recht:
menu:read- Tarif:
- jeder Tarif
Categories, products and option groups as flat lists with references, including everything that is currently sold out. Each object carries your external_id (or null) and updated_at. For large menus that change often, poll the lists with updated_since instead: GET /menu/products and GET /menu/options (an option's surcharge or sold-out state does not change its product's updated_at).
Antwort200
OK
objectstringPflichtAlways
menu.Werte:
"menu"currencystringPflichtCurrency of all amounts (ISO 4217).
categoriesCategory[]PflichtAll categories by position.
Felder darin (8)
objectstringPflichtAlways
category.Werte:
"category"idstring (uuid)PflichtThe category id.
external_idstringPflichtkann null seinYour app's external_id for this category; null if it has none. Other apps never see yours. Always null in webhook events and in
GET /events: the envelope is built once and sent the same to every receiver, but this value belongs to a single app. Retrieve the object to see yours.1 to 100 chars
namestringPflichtName as the guest sees it.
max 80 chars
positionintegerPflichtSort order on the menu, ascending.
tax_classstringPflichtkann null seinTax group for the products in it that have none of their own. null:
food.Werte:
"food""drink""alcohol"created_atstring (date-time)PflichtWhen the category was created. ISO 8601 in UTC (
Z).updated_atstring (date-time)PflichtLast change of one of its fields. ISO 8601 in UTC (
Z).
productsProduct[]PflichtAll products by position.
Felder darin (17)
objectstringPflichtAlways
product.Werte:
"product"idstring (uuid)PflichtThe product id.
external_idstringPflichtkann null seinYour app's external_id for this product; null if it has none. Always null in webhook events and in
GET /events: the envelope is built once and sent the same to every receiver, but this value belongs to a single app. Retrieve the object to see yours.1 to 100 chars
category_idstring (uuid)Pflichtkann null seinIts category; null: no category.
namestringPflichtName as the guest sees it.
max 200 chars
descriptionstringPflichtkann null seinDescription as the guest sees it.
max 2000 chars
priceintegerPflichtPrice including VAT, without options. Integer amount in the smallest currency unit (cents).
availablebooleanPflichtfalse = sold out: guests cannot order it until it is available again.
positionintegerPflichtSort order within its category, ascending.
allergensstring[]PflichtThe 14 EU allergens as keys:
gluten,crustaceans,eggs,fish,peanuts,soybeans,milk,nuts,celery,mustard,sesame,sulphites,lupin,molluscs. Common names in German, English, French, Spanish and Italian are mapped to the key (Soja→soybeans). Anything else is kept as the restaurant's own label and shown verbatim (at most 20 such labels, 40 characters each).max 34 items
dietsstring[]PflichtDiet marks:
vegetarian,vegan,gluten_free,lactose_free,halal,kosher(Gluten-freeis mapped togluten_free). Anything else is kept as the restaurant's own label, e.g. "gluten-free on request" (at most 14 such labels, 60 characters each). Never mixed with allergens.image_urlstring (uri)Pflichtkann null seinThe product photo as Flavoso serves it; null: none.
tax_classstringPflichtkann null seinThe product's own tax group; null: its category's, else
food.Werte:
"food""drink""alcohol"tax_ratesTaxRatesPflichtThe resolved VAT rates in basis points.
Felder darin (2)
dine_in_bpsintegerPflichtkann null seinRate when eaten in (table orders).
takeaway_bpsintegerPflichtkann null seinRate for pickup and delivery.
option_group_idsstring (uuid)[]PflichtIts option groups in the order the guest sees them: groups that belong to this product only first, then shared groups from the option library.
created_atstring (date-time)PflichtWhen the product was created. ISO 8601 in UTC (
Z).updated_atstring (date-time)PflichtLast change of one of its fields, including its option groups. ISO 8601 in UTC (
Z).
option_groupsOptionGroup[]PflichtAll option groups by position, each with its options.
Felder darin (10)
objectstringPflichtAlways
option_group.Werte:
"option_group"idstring (uuid)PflichtThe option group id.
external_idstringPflichtkann null seinYour app's external_id for this group; null if it has none. Always null in webhook events and in
GET /events: the envelope is built once and sent the same to every receiver, but this value belongs to a single app. Retrieve the object to see yours.1 to 100 chars
namestringPflichtName as the guest sees it, e.g. Size, Extras.
max 120 chars
min_selectintegerPflichtHow many options the guest must pick at least (0 = optional).
0 to 20
max_selectintegerPflichtHow many options the guest may pick at most.
1 to 20
positionintegerPflichtSort order among groups.
product_idstring (uuid)Pflichtkann null seinThe product this group belongs to only; null for a shared group.
optionsOption[]PflichtIts options in order, including sold-out ones.
Felder darin (9)
objectstringPflichtAlways
option.Werte:
"option"idstring (uuid)PflichtThe option id.
external_idstringPflichtkann null seinYour app's external_id for this option; null if it has none. Always null in webhook events and in
GET /events: the envelope is built once and sent the same to every receiver, but this value belongs to a single app. Retrieve the object to see yours.1 to 100 chars
group_idstring (uuid)PflichtThe option group it belongs to.
namestringPflichtName as the guest sees it.
max 120 chars
price_deltaintegerPflichtSurcharge, may be negative. Integer amount in the smallest currency unit (cents).
availablebooleanPflichtfalse = sold out on every product that uses the group.
positionintegerPflichtSort order within the group.
updated_atstring (date-time)PflichtLast change of one of its fields. ISO 8601 in UTC (
Z).
updated_atstring (date-time)PflichtLast change of one of its own fields (not of its options). ISO 8601 in UTC (
Z).
Fehler
400invalid_request: a parameter or the body is wrong, or it contains a query parameter or field the operation does not know.paramnames the field.401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.403insufficient_scope,plan_required(withparamwhen an order type is not in the plan) oraccount_inactive.429rate_limited: more than 120 requests per minute on this connection. WaitRetry-Afterseconds.500server_error. Safe to retry with the same Idempotency-Key.
curl "https://api.flavoso.com/v1/menu" \
-H "Authorization: Bearer $ACCESS_TOKEN"{
"object": "menu",
"currency": "EUR",
"categories": [
{
"object": "category",
"id": "8f6b2c1e-4d3a-4b5f-9c7d-2e1a0b9c8d7e",
"external_id": "CAT-PIZZA",
"name": "Pizza",
"position": 2,
"tax_class": "food",
"created_at": "2026-10-06T09:12:00.000Z",
"updated_at": "2026-10-06T11:40:31.204Z"
}
],
"products": [
{
"object": "product",
"id": "3c9a7e5d-1b2f-4a6c-8d0e-7f5a3b1c9e2d",
"external_id": "PLU-1001",
"category_id": "8f6b2c1e-4d3a-4b5f-9c7d-2e1a0b9c8d7e",
"name": "Pizza Margherita",
"description": "Tomato, mozzarella, basil",
"price": 890,
"available": true,
"position": 0,
"allergens": [
"gluten",
"milk"
],
"diets": [
"vegetarian"
],
"image_url": "https://api.flavoso.com/storage/v1/object/public/tenant-assets/products/6f1e2d3c-4b5a-4c69-8d7e-0f1a2b3c4d5e/api-4f3a9c0d5b1e7a2c8d6f0e1b3a5c7d9e.jpg",
"tax_class": "food",
"tax_rates": {
"dine_in_bps": 700,
"takeaway_bps": 700
},
"option_group_ids": [
"5e4d3c2b-1a09-4f8e-b7d6-c5b4a3928170"
],
"created_at": "2026-10-06T09:12:00.000Z",
"updated_at": "2026-10-06T11:40:31.204Z"
}
],
"option_groups": [
{
"object": "option_group",
"id": "5e4d3c2b-1a09-4f8e-b7d6-c5b4a3928170",
"external_id": "GRP-EXTRAS",
"name": "Extras",
"min_select": 0,
"max_select": 3,
"position": 0,
"product_id": null,
"options": [
{
"object": "option",
"id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
"external_id": "MOD-CHEESE",
"group_id": "5e4d3c2b-1a09-4f8e-b7d6-c5b4a3928170",
"name": "Extra cheese",
"price_delta": 150,
"available": true,
"position": 0,
"updated_at": "2026-10-06T11:40:31.204Z"
},
{
"object": "option",
"id": "2b3c4d5e-6f70-4182-93a4-b5c6d7e8f901",
"external_id": "MOD-SALAMI",
"group_id": "5e4d3c2b-1a09-4f8e-b7d6-c5b4a3928170",
"name": "Salami",
"price_delta": 200,
"available": false,
"position": 1,
"updated_at": "2026-10-06T11:40:31.204Z"
}
],
"updated_at": "2026-10-06T11:40:31.204Z"
}
]
}Sync the whole menu from your system
put/menu
- Recht:
menu:write- Tarif:
- jeder Tarif
Creates, updates and (with mode: "replace") deletes categories, products, option groups and options so that Flavoso matches your system. The key is your external_id; objects without your id are not deleted or updated, except as a consequence of deleting yours in replace mode: the restaurant's products in one of your deleted categories move to no category, and the restaurant's options in one of your deleted option groups are deleted with it (warning unmanaged_options_deleted). Everything happens in one transaction: either the whole sync applies or nothing does.
Fields you leave out stay as they are, so a system that does not track allergens or sold-out states does not overwrite what the restaurant set. Use dry_run: true to see the outcome first.
Images: new image URLs are downloaded during the sync, at most 25 per request; the rest follow with the next sync (warning images_deferred). A URL that was used before is not downloaded again. A test restaurant keeps at most 50 downloaded images; beyond that, images are skipped (warning images_capped). Resetting the test restaurant or its menu deletes them.
A sync that changes something sends ONE webhook event menu.synced instead of one event per object; it names your connection in connection_id.
At most 10 syncs per minute per connection. Wrong references (an unknown category_external_id or option group) answer 400 with param, e.g. products[3].category_external_id; too many objects answer 422 limit_exceeded; a body larger than 2 MB (counted in bytes) answers 413 payload_too_large.
Rumpfapplication/json
modestringPflichtmergecreates and updates;replacealso deletes your objects that are missing.Werte:
"merge""replace"dry_runbooleantrue: validate and report what would happen, change nothing. Images are not downloaded.
default false
categoriesMenuSyncCategory[]Your categories.
max 200 items
Felder darin (4)
external_idstringPflichtYour id; the key of the sync.
1 to 100 chars
namestringPflichtName as the guest sees it.
1 to 80 chars
positionintegerSort order. Missing: unchanged (new categories go last).
0 to 100000
tax_classstringkann null seinTax group:
food,drinkoralcohol. Flavoso stores the group, not a percentage: the restaurant sets the rate per group for its country, andtax_ratesshows the result. null means: use the category's group, elsefood. Missing: unchanged.Werte:
"food""drink""alcohol"
productsMenuSyncProduct[]Your products.
max 2000 items
Felder darin (12)
external_idstringPflichtYour id (article number, PLU); the key of the sync.
1 to 100 chars
category_external_idstringkann null seinYour id of its category, from
categoriesor synced before; null: no category.1 to 100 chars
namestringPflichtName as the guest sees it.
1 to 200 chars
descriptionstringkann null seinDescription; null or empty removes it.
max 2000 chars
priceintegerPflichtPrice including VAT in cents.
0 to 1000000
availablebooleanfalse = sold out. Missing: unchanged (new: true).
positionintegerSort order. Missing: unchanged (new products go last).
0 to 100000
allergensstring[]The 14 EU allergens as keys:
gluten,crustaceans,eggs,fish,peanuts,soybeans,milk,nuts,celery,mustard,sesame,sulphites,lupin,molluscs. Common names in German, English, French, Spanish and Italian are mapped to the key (Soja→soybeans). Anything else is kept as the restaurant's own label and shown verbatim (at most 20 such labels, 40 characters each).max 34 items
dietsstring[]Diet marks:
vegetarian,vegan,gluten_free,lactose_free,halal,kosher(Gluten-freeis mapped togluten_free). Anything else is kept as the restaurant's own label, e.g. "gluten-free on request" (at most 14 such labels, 60 characters each). Never mixed with allergens.tax_classstringkann null seinTax group:
food,drinkoralcohol. Flavoso stores the group, not a percentage: the restaurant sets the rate per group for its country, andtax_ratesshows the result. null means: use the category's group, elsefood.Werte:
"food""drink""alcohol"image_urlstring (uri)kann null seinAn https URL of a PNG, JPEG or WebP image of at most 1 MB. Flavoso downloads it (public hosts only), checks the bytes and stores its own copy; the product then shows Flavoso's URL. The same URL again does not download again; to replace an image under the same URL, add a version parameter (
?v=2). null removes the image.max 1000 chars
option_group_external_idsstring[]Your ids of its option groups, in order. Replaces the product's attachments to groups carrying your ids; attachments to groups the restaurant created itself stay, after yours. Missing: unchanged.
max 50 items
option_groupsMenuSyncOptionGroup[]Your option groups with their options.
max 500 items
Felder darin (6)
external_idstringPflichtYour id; the key of the sync.
1 to 100 chars
namestringPflichtName as the guest sees it.
1 to 120 chars
min_selectintegerMissing: unchanged (new: 0).
0 to 20
max_selectintegerMissing: unchanged (new: 1).
1 to 20
positionintegerMissing: unchanged (new groups go last).
0 to 100000
optionsMenuSyncOption[]PflichtThe group's options carrying your ids. Options the restaurant added without your id stay.
Felder darin (5)
external_idstringPflichtYour id; unique across all options of the sync. An existing option listed under another group moves there.
1 to 100 chars
namestringPflichtName as the guest sees it.
1 to 120 chars
price_deltaintegerSurcharge in cents. Missing: unchanged (new: 0).
-100000 to 100000
availablebooleanMissing: unchanged (new: true), so a sync never undoes a sold-out mark you do not track.
positionintegerSort order. Missing: unchanged (new options go last).
0 to 100000
Antwort200
OK
objectstringPflichtAlways
menu_sync.Werte:
"menu_sync"modestringPflichtThe mode you sent.
Werte:
"merge""replace"dry_runbooleanPflichttrue: nothing was changed.
summaryMenuSyncSummaryPflichtWhat was created, updated, left unchanged and deleted.
Felder darin (4)
categoriesMenuSyncCountsPflichtCategories.
Felder darin (4)
createdintegerPflichtCreated by this sync.
updatedintegerPflichtChanged by this sync.
unchangedintegerPflichtListed, but already as in the body.
deletedintegerPflichtDeleted by this sync (
replaceonly).
productsMenuSyncCountsPflichtProducts.
Felder darin (4)
createdintegerPflichtCreated by this sync.
updatedintegerPflichtChanged by this sync.
unchangedintegerPflichtListed, but already as in the body.
deletedintegerPflichtDeleted by this sync (
replaceonly).
option_groupsMenuSyncCountsPflichtOption groups.
Felder darin (4)
createdintegerPflichtCreated by this sync.
updatedintegerPflichtChanged by this sync.
unchangedintegerPflichtListed, but already as in the body.
deletedintegerPflichtDeleted by this sync (
replaceonly).
optionsMenuSyncCountsPflichtOptions.
Felder darin (4)
createdintegerPflichtCreated by this sync.
updatedintegerPflichtChanged by this sync.
unchangedintegerPflichtListed, but already as in the body.
deletedintegerPflichtDeleted by this sync (
replaceonly).
warningsMenuSyncWarning[]PflichtEmpty when all went as asked. Comes before
ids, so it survives a truncated log.Felder darin (3)
codestringPflichtimage_failed: an image could not be used (the product keeps its image).images_deferred: more new images than one sync fetches; sync again.images_not_checked: dry run.images_capped: only in a test restaurant, which keeps at most 50 images downloaded fromimage_url; the rest was not downloaded. Resetting the test restaurant or its menu in the developer console deletes them.unmanaged_kept: objects without your id that areplacekept.unmanaged_options_deleted: options without your id that went with a deleted group (inreplacemode also the restaurant's own options in one of your groups).own_option_group_kept: a group that belongs to a product only stays attached.option_group_released: a group of a deleted product became a shared group.Werte:
"image_failed""images_deferred""images_not_checked""images_capped""unmanaged_kept""unmanaged_options_deleted""own_option_group_kept""option_group_released"messagestringPflichtExplanation for developers; may change.
paramstringThe field it concerns, e.g.
products[3].image_url, when there is one.
idsobjectPflichtFlavoso's id for each of your ids, to store on your side (webhooks and orders carry Flavoso's ids).
Felder darin (4)
categoriesMenuSyncIdPair[]PflichtIn the order of the body.
Felder darin (2)
external_idstringPflichtYour id.
idstring (uuid)Pflichtkann null seinFlavoso's id; null in a dry run for objects that would be created.
productsMenuSyncIdPair[]PflichtIn the order of the body.
Felder darin (2)
external_idstringPflichtYour id.
idstring (uuid)Pflichtkann null seinFlavoso's id; null in a dry run for objects that would be created.
option_groupsMenuSyncIdPair[]PflichtIn the order of the body.
Felder darin (2)
external_idstringPflichtYour id.
idstring (uuid)Pflichtkann null seinFlavoso's id; null in a dry run for objects that would be created.
optionsMenuSyncIdPair[]PflichtIn the order of the body.
Felder darin (2)
external_idstringPflichtYour id.
idstring (uuid)Pflichtkann null seinFlavoso's id; null in a dry run for objects that would be created.
Fehler
400invalid_request: a parameter or the body is wrong, or it contains a query parameter or field the operation does not know.paramnames the field.401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.403insufficient_scope,plan_required(withparamwhen an order type is not in the plan) oraccount_inactive.409invalid_transition(the status of the object does not allow this),stage_not_used(the restaurant does not use this status),external_id_taken(your app already uses thisexternal_idfor another object of the same kind in this restaurant),slot_unavailable(the time or the table is taken),idempotency_in_progress(a request with this Idempotency-Key is still running) orconflict(the same data was changed elsewhere at the same moment, three times in a row; retry unchanged afterRetry-Afterseconds).413payload_too_large: the body is larger than the operation takes, counted in bytes: 64 KB, 2 MB forPUT /menu. Split the request.422idempotency_key_reused(the key was used with a different request) orlimit_exceeded(too many entries in one request, such as products inPUT /menu, or a limit of the restaurant is reached, such as 60 special days).429rate_limited: more than 120 requests per minute on this connection. WaitRetry-Afterseconds.500server_error. Safe to retry with the same Idempotency-Key.
curl -X PUT "https://api.flavoso.com/v1/menu" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"mode": "replace",
"categories": [
{
"external_id": "CAT-PIZZA",
"name": "Pizza",
"position": 0,
"tax_class": "food"
}
],
"option_groups": [
{
"external_id": "GRP-EXTRAS",
"name": "Extras",
"min_select": 0,
"max_select": 3,
"options": [
{
"external_id": "MOD-CHEESE",
"name": "Extra cheese",
"price_delta": 150
},
{
"external_id": "MOD-SALAMI",
"name": "Salami",
"price_delta": 200
}
]
}
],
"products": [
{
"external_id": "PLU-1001",
"category_external_id": "CAT-PIZZA",
"name": "Pizza Margherita",
"description": "Tomato, mozzarella, basil",
"price": 890,
"allergens": [
"gluten",
"milk"
],
"diets": [
"vegetarian"
],
"image_url": "https://example.com/pos/images/plu-1001.jpg?v=3",
"option_group_external_ids": [
"GRP-EXTRAS"
]
}
]
}'{
"mode": "replace",
"categories": [
{
"external_id": "CAT-PIZZA",
"name": "Pizza",
"position": 0,
"tax_class": "food"
}
],
"option_groups": [
{
"external_id": "GRP-EXTRAS",
"name": "Extras",
"min_select": 0,
"max_select": 3,
"options": [
{
"external_id": "MOD-CHEESE",
"name": "Extra cheese",
"price_delta": 150
},
{
"external_id": "MOD-SALAMI",
"name": "Salami",
"price_delta": 200
}
]
}
],
"products": [
{
"external_id": "PLU-1001",
"category_external_id": "CAT-PIZZA",
"name": "Pizza Margherita",
"description": "Tomato, mozzarella, basil",
"price": 890,
"allergens": [
"gluten",
"milk"
],
"diets": [
"vegetarian"
],
"image_url": "https://example.com/pos/images/plu-1001.jpg?v=3",
"option_group_external_ids": [
"GRP-EXTRAS"
]
}
]
}{
"object": "menu_sync",
"mode": "replace",
"dry_run": false,
"summary": {
"categories": {
"created": 0,
"updated": 1,
"unchanged": 7,
"deleted": 0
},
"products": {
"created": 2,
"updated": 5,
"unchanged": 113,
"deleted": 1
},
"option_groups": {
"created": 0,
"updated": 0,
"unchanged": 12,
"deleted": 0
},
"options": {
"created": 1,
"updated": 0,
"unchanged": 64,
"deleted": 0
}
},
"warnings": [
{
"code": "unmanaged_kept",
"message": "Kept 3 products without your external_id (created in Flavoso or by another app). A sync never deletes those."
}
],
"ids": {
"categories": [
{
"external_id": "CAT-PIZZA",
"id": "8f6b2c1e-4d3a-4b5f-9c7d-2e1a0b9c8d7e"
}
],
"products": [
{
"external_id": "PLU-1001",
"id": "3c9a7e5d-1b2f-4a6c-8d0e-7f5a3b1c9e2d"
}
],
"option_groups": [
{
"external_id": "GRP-EXTRAS",
"id": "5e4d3c2b-1a09-4f8e-b7d6-c5b4a3928170"
}
],
"options": [
{
"external_id": "MOD-CHEESE",
"id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d"
},
{
"external_id": "MOD-SALAMI",
"id": "2b3c4d5e-6f70-4182-93a4-b5c6d7e8f901"
}
]
}
}Mark many products and options sold out or available
post/menu/availability
- Recht:
menu:write- Tarif:
- jeder Tarif
Up to 500 target states in one call, by id or your external_id. All or nothing. Each object that changes sends its own menu.product.updated or menu.option.updated.
Parameter
| Name | Ort | Beschreibung |
|---|---|---|
Idempotency-Keystring | header | Any unique string up to 255 printable characters. Retrying a POST with the same key returns the first response (header Idempotent-Replayed: true) instead of acting twice; the same key with a different body is 422 idempotency_key_reused. Kept for 24 hours. Answers that ask for a retry (5xx, 409 conflict) are not kept. While the first request runs, a retry gets 409 idempotency_in_progress; a first request that never finished frees the key after 2 minutes.max 255 chars |
Rumpfapplication/json
itemsMenuAvailabilityItem[]PflichtEach object at most once.
max 500 items · min 1 items
Felder darin (4)
typestringPflichtWhat it is.
Werte:
"product""option"idstring (uuid)Flavoso's id.
external_idstringYour id.
1 to 100 chars
availablebooleanPflichtfalse = sold out, true = available again.
Antwort200
OK
objectstringPflichtAlways
menu_availability.Werte:
"menu_availability"updatedintegerPflichtItems whose state changed.
unchangedintegerPflichtItems that already had this state.
itemsobject[]PflichtIn the order of the request.
Felder darin (5)
typestringPflichtWhat it is.
Werte:
"product""option"idstring (uuid)PflichtFlavoso's id.
external_idstringPflichtkann null seinYour id, if the object has one.
availablebooleanPflichtThe state now.
changedbooleanPflichtWhether this request changed it.
Fehler
400invalid_request: a parameter or the body is wrong, or it contains a query parameter or field the operation does not know.paramnames the field.401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.403insufficient_scope,plan_required(withparamwhen an order type is not in the plan) oraccount_inactive.404not_found: no such object in this restaurant (objects of other restaurants are never found).409invalid_transition(the status of the object does not allow this),stage_not_used(the restaurant does not use this status),external_id_taken(your app already uses thisexternal_idfor another object of the same kind in this restaurant),slot_unavailable(the time or the table is taken),idempotency_in_progress(a request with this Idempotency-Key is still running) orconflict(the same data was changed elsewhere at the same moment, three times in a row; retry unchanged afterRetry-Afterseconds).413payload_too_large: the body is larger than the operation takes, counted in bytes: 64 KB, 2 MB forPUT /menu. Split the request.422idempotency_key_reused(the key was used with a different request) orlimit_exceeded(too many entries in one request, such as products inPUT /menu, or a limit of the restaurant is reached, such as 60 special days).429rate_limited: more than 120 requests per minute on this connection. WaitRetry-Afterseconds.500server_error. Safe to retry with the same Idempotency-Key.
curl -X POST "https://api.flavoso.com/v1/menu/availability" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"type": "product",
"external_id": "PLU-1001",
"available": false
},
{
"type": "option",
"id": "2b3c4d5e-6f70-4182-93a4-b5c6d7e8f901",
"available": true
}
]
}'{
"items": [
{
"type": "product",
"external_id": "PLU-1001",
"available": false
},
{
"type": "option",
"id": "2b3c4d5e-6f70-4182-93a4-b5c6d7e8f901",
"available": true
}
]
}{
"object": "menu_availability",
"updated": 1,
"unchanged": 1,
"items": [
{
"type": "product",
"id": "3c9a7e5d-1b2f-4a6c-8d0e-7f5a3b1c9e2d",
"external_id": "PLU-1001",
"available": false,
"changed": true
},
{
"type": "option",
"id": "2b3c4d5e-6f70-4182-93a4-b5c6d7e8f901",
"external_id": "MOD-SALAMI",
"available": true,
"changed": false
}
]
}List categories
get/menu/categories
- Recht:
menu:read- Tarif:
- jeder Tarif
Sorted by updated_at ascending. GET /menu returns them by position.
Parameter
| Name | Ort | Beschreibung |
|---|---|---|
limitinteger | query | Page size, 1 to 100. Default 25.1 to 100 · default 25 |
cursorstring | query | next_cursor of the previous page. Opaque. Send the same filters again with it; the cursor only remembers where the last page ended. |
updated_sincestring (date-time) | query | Only objects changed after this instant (exclusive; ISO 8601 with time zone, write + as %2B in a URL). Lists are always sorted by updated_at ascending, and an object changed while you page through moves to the end and shows up again. Poll with an overlap: pass the updated_at of the last object you processed minus a minute and deduplicate by id + updated_at, because a change committed at the same moment as your previous poll can otherwise be missed. |
external_idstring | query | Only the object that carries this external_id of your app (zero or one result).max 100 chars |
Antwort200
OK
objectstringPflichtAlways
list.Werte:
"list"dataCategory[]PflichtThis page, oldest change first.
Felder darin (8)
objectstringPflichtAlways
category.Werte:
"category"idstring (uuid)PflichtThe category id.
external_idstringPflichtkann null seinYour app's external_id for this category; null if it has none. Other apps never see yours. Always null in webhook events and in
GET /events: the envelope is built once and sent the same to every receiver, but this value belongs to a single app. Retrieve the object to see yours.1 to 100 chars
namestringPflichtName as the guest sees it.
max 80 chars
positionintegerPflichtSort order on the menu, ascending.
tax_classstringPflichtkann null seinTax group for the products in it that have none of their own. null:
food.Werte:
"food""drink""alcohol"created_atstring (date-time)PflichtWhen the category was created. ISO 8601 in UTC (
Z).updated_atstring (date-time)PflichtLast change of one of its fields. ISO 8601 in UTC (
Z).
has_morebooleanPflichtWhether another page follows.
next_cursorstringPflichtkann null seinPass as
cursorfor the next page; null on the last page.
Fehler
400invalid_request: a parameter or the body is wrong, or it contains a query parameter or field the operation does not know.paramnames the field.401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.403insufficient_scope,plan_required(withparamwhen an order type is not in the plan) oraccount_inactive.429rate_limited: more than 120 requests per minute on this connection. WaitRetry-Afterseconds.500server_error. Safe to retry with the same Idempotency-Key.
curl "https://api.flavoso.com/v1/menu/categories" \
-H "Authorization: Bearer $ACCESS_TOKEN"{
"object": "list",
"data": [
{
"object": "category",
"id": "8f6b2c1e-4d3a-4b5f-9c7d-2e1a0b9c8d7e",
"external_id": "CAT-PIZZA",
"name": "Pizza",
"position": 2,
"tax_class": "food",
"created_at": "2026-10-06T09:12:00.000Z",
"updated_at": "2026-10-06T11:40:31.204Z"
}
],
"has_more": false,
"next_cursor": null
}Create a category
post/menu/categories
- Recht:
menu:write- Tarif:
- jeder Tarif
Without position, the category goes last, as in the dashboard. With external_id, 409 external_id_taken if another object of the same type in this restaurant already carries it for your app.
Parameter
| Name | Ort | Beschreibung |
|---|---|---|
Idempotency-Keystring | header | Any unique string up to 255 printable characters. Retrying a POST with the same key returns the first response (header Idempotent-Replayed: true) instead of acting twice; the same key with a different body is 422 idempotency_key_reused. Kept for 24 hours. Answers that ask for a retry (5xx, 409 conflict) are not kept. While the first request runs, a retry gets 409 idempotency_in_progress; a first request that never finished frees the key after 2 minutes.max 255 chars |
Rumpfapplication/json
namestringPflichtName as the guest sees it.
1 to 80 chars
positionintegerSort order. Default: after the last category.
0 to 100000
tax_classstringkann null seinTax group:
food,drinkoralcohol. Flavoso stores the group, not a percentage: the restaurant sets the rate per group for its country, andtax_ratesshows the result. null means: use the category's group, elsefood.Werte:
"food""drink""alcohol"external_idstringYour own id for this category; 409
external_id_takenif another category of this restaurant already has it.1 to 100 chars
Antwort201
OK
objectstringPflichtAlways
category.Werte:
"category"idstring (uuid)PflichtThe category id.
external_idstringPflichtkann null seinYour app's external_id for this category; null if it has none. Other apps never see yours. Always null in webhook events and in
GET /events: the envelope is built once and sent the same to every receiver, but this value belongs to a single app. Retrieve the object to see yours.1 to 100 chars
namestringPflichtName as the guest sees it.
max 80 chars
positionintegerPflichtSort order on the menu, ascending.
tax_classstringPflichtkann null seinTax group for the products in it that have none of their own. null:
food.Werte:
"food""drink""alcohol"created_atstring (date-time)PflichtWhen the category was created. ISO 8601 in UTC (
Z).updated_atstring (date-time)PflichtLast change of one of its fields. ISO 8601 in UTC (
Z).
Fehler
400invalid_request: a parameter or the body is wrong, or it contains a query parameter or field the operation does not know.paramnames the field.401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.403insufficient_scope,plan_required(withparamwhen an order type is not in the plan) oraccount_inactive.404not_found: no such object in this restaurant (objects of other restaurants are never found).409invalid_transition(the status of the object does not allow this),stage_not_used(the restaurant does not use this status),external_id_taken(your app already uses thisexternal_idfor another object of the same kind in this restaurant),slot_unavailable(the time or the table is taken),idempotency_in_progress(a request with this Idempotency-Key is still running) orconflict(the same data was changed elsewhere at the same moment, three times in a row; retry unchanged afterRetry-Afterseconds).413payload_too_large: the body is larger than the operation takes, counted in bytes: 64 KB, 2 MB forPUT /menu. Split the request.422idempotency_key_reused(the key was used with a different request) orlimit_exceeded(too many entries in one request, such as products inPUT /menu, or a limit of the restaurant is reached, such as 60 special days).429rate_limited: more than 120 requests per minute on this connection. WaitRetry-Afterseconds.500server_error. Safe to retry with the same Idempotency-Key.
curl -X POST "https://api.flavoso.com/v1/menu/categories" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"name": "Pizza",
"external_id": "CAT-PIZZA",
"tax_class": "food"
}'{
"name": "Pizza",
"external_id": "CAT-PIZZA",
"tax_class": "food"
}{
"object": "category",
"id": "8f6b2c1e-4d3a-4b5f-9c7d-2e1a0b9c8d7e",
"external_id": "CAT-PIZZA",
"name": "Pizza",
"position": 2,
"tax_class": "food",
"created_at": "2026-10-06T09:12:00.000Z",
"updated_at": "2026-10-06T11:40:31.204Z"
}Retrieve a category
get/menu/categories/{id}
- Recht:
menu:read- Tarif:
- jeder Tarif
Parameter
| Name | Ort | Beschreibung |
|---|---|---|
idstring (uuid)Pflicht | path | The category id (UUID). |
Antwort200
OK
objectstringPflichtAlways
category.Werte:
"category"idstring (uuid)PflichtThe category id.
external_idstringPflichtkann null seinYour app's external_id for this category; null if it has none. Other apps never see yours. Always null in webhook events and in
GET /events: the envelope is built once and sent the same to every receiver, but this value belongs to a single app. Retrieve the object to see yours.1 to 100 chars
namestringPflichtName as the guest sees it.
max 80 chars
positionintegerPflichtSort order on the menu, ascending.
tax_classstringPflichtkann null seinTax group for the products in it that have none of their own. null:
food.Werte:
"food""drink""alcohol"created_atstring (date-time)PflichtWhen the category was created. ISO 8601 in UTC (
Z).updated_atstring (date-time)PflichtLast change of one of its fields. ISO 8601 in UTC (
Z).
Fehler
400invalid_request: a parameter or the body is wrong, or it contains a query parameter or field the operation does not know.paramnames the field.401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.403insufficient_scope,plan_required(withparamwhen an order type is not in the plan) oraccount_inactive.404not_found: no such object in this restaurant (objects of other restaurants are never found).429rate_limited: more than 120 requests per minute on this connection. WaitRetry-Afterseconds.500server_error. Safe to retry with the same Idempotency-Key.
curl "https://api.flavoso.com/v1/menu/categories/CATEGORY_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN"{
"object": "category",
"id": "8f6b2c1e-4d3a-4b5f-9c7d-2e1a0b9c8d7e",
"external_id": "CAT-PIZZA",
"name": "Pizza",
"position": 2,
"tax_class": "food",
"created_at": "2026-10-06T09:12:00.000Z",
"updated_at": "2026-10-06T11:40:31.204Z"
}Update a category
patch/menu/categories/{id}
- Recht:
menu:write- Tarif:
- jeder Tarif
Only the fields you send change. With external_id, 409 external_id_taken if another object of the same type in this restaurant already carries it for your app.
Parameter
| Name | Ort | Beschreibung |
|---|---|---|
idstring (uuid)Pflicht | path | The category id (UUID). |
Rumpfapplication/json
namestringNew name.
1 to 80 chars
positionintegerNew sort order.
0 to 100000
tax_classstringkann null seinTax group:
food,drinkoralcohol. Flavoso stores the group, not a percentage: the restaurant sets the rate per group for its country, andtax_ratesshows the result. null means: use the category's group, elsefood.Werte:
"food""drink""alcohol"external_idstringkann null seinSet or change your id; null removes it.
1 to 100 chars
Antwort200
OK
objectstringPflichtAlways
category.Werte:
"category"idstring (uuid)PflichtThe category id.
external_idstringPflichtkann null seinYour app's external_id for this category; null if it has none. Other apps never see yours. Always null in webhook events and in
GET /events: the envelope is built once and sent the same to every receiver, but this value belongs to a single app. Retrieve the object to see yours.1 to 100 chars
namestringPflichtName as the guest sees it.
max 80 chars
positionintegerPflichtSort order on the menu, ascending.
tax_classstringPflichtkann null seinTax group for the products in it that have none of their own. null:
food.Werte:
"food""drink""alcohol"created_atstring (date-time)PflichtWhen the category was created. ISO 8601 in UTC (
Z).updated_atstring (date-time)PflichtLast change of one of its fields. ISO 8601 in UTC (
Z).
Fehler
400invalid_request: a parameter or the body is wrong, or it contains a query parameter or field the operation does not know.paramnames the field.401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.403insufficient_scope,plan_required(withparamwhen an order type is not in the plan) oraccount_inactive.404not_found: no such object in this restaurant (objects of other restaurants are never found).409invalid_transition(the status of the object does not allow this),stage_not_used(the restaurant does not use this status),external_id_taken(your app already uses thisexternal_idfor another object of the same kind in this restaurant),slot_unavailable(the time or the table is taken),idempotency_in_progress(a request with this Idempotency-Key is still running) orconflict(the same data was changed elsewhere at the same moment, three times in a row; retry unchanged afterRetry-Afterseconds).413payload_too_large: the body is larger than the operation takes, counted in bytes: 64 KB, 2 MB forPUT /menu. Split the request.422idempotency_key_reused(the key was used with a different request) orlimit_exceeded(too many entries in one request, such as products inPUT /menu, or a limit of the restaurant is reached, such as 60 special days).429rate_limited: more than 120 requests per minute on this connection. WaitRetry-Afterseconds.500server_error. Safe to retry with the same Idempotency-Key.
curl -X PATCH "https://api.flavoso.com/v1/menu/categories/CATEGORY_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Pizza & Pinsa"
}'{
"name": "Pizza & Pinsa"
}{
"object": "category",
"id": "8f6b2c1e-4d3a-4b5f-9c7d-2e1a0b9c8d7e",
"external_id": "CAT-PIZZA",
"name": "Pizza & Pinsa",
"position": 2,
"tax_class": "food",
"created_at": "2026-10-06T09:12:00.000Z",
"updated_at": "2026-10-06T11:40:31.204Z"
}Delete a category
delete/menu/categories/{id}
- Recht:
menu:write- Tarif:
- jeder Tarif
As in the dashboard: its products move to no category, or with with_products=true they are deleted with it (all of them, also those without your external_id). Orders keep their items.
Parameter
| Name | Ort | Beschreibung |
|---|---|---|
idstring (uuid)Pflicht | path | The category id (UUID). |
with_productsboolean | query | true: delete the category's products too. Default false.default false |
Antwort200
OK
objectstringPflichtAlways
category.Werte:
"category"idstring (uuid)PflichtThe deleted category.
deletedbooleanPflichtAlways true.
Werte:
trueproducts_deletedintegerPflichtProducts deleted with it (
with_products=true).products_uncategorizedintegerPflichtProducts that are now without a category.
Fehler
400invalid_request: a parameter or the body is wrong, or it contains a query parameter or field the operation does not know.paramnames the field.401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.403insufficient_scope,plan_required(withparamwhen an order type is not in the plan) oraccount_inactive.404not_found: no such object in this restaurant (objects of other restaurants are never found).409invalid_transition(the status of the object does not allow this),stage_not_used(the restaurant does not use this status),external_id_taken(your app already uses thisexternal_idfor another object of the same kind in this restaurant),slot_unavailable(the time or the table is taken),idempotency_in_progress(a request with this Idempotency-Key is still running) orconflict(the same data was changed elsewhere at the same moment, three times in a row; retry unchanged afterRetry-Afterseconds).429rate_limited: more than 120 requests per minute on this connection. WaitRetry-Afterseconds.500server_error. Safe to retry with the same Idempotency-Key.
curl -X DELETE "https://api.flavoso.com/v1/menu/categories/CATEGORY_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN"{
"object": "category",
"id": "8f6b2c1e-4d3a-4b5f-9c7d-2e1a0b9c8d7e",
"deleted": true,
"products_deleted": 0,
"products_uncategorized": 4
}List products
get/menu/products
- Recht:
menu:read- Tarif:
- jeder Tarif
Sorted by updated_at ascending, which is the way to poll: remember the updated_at of the last product you processed and pass it, minus a minute of overlap, as the next updated_since, deduplicating by id + updated_at. A product counts as changed when one of its fields or the list of its option groups change; a changed option does not change the product, poll GET /menu/options for those.
Parameter
| Name | Ort | Beschreibung |
|---|---|---|
limitinteger | query | Page size, 1 to 100. Default 25.1 to 100 · default 25 |
cursorstring | query | next_cursor of the previous page. Opaque. Send the same filters again with it; the cursor only remembers where the last page ended. |
updated_sincestring (date-time) | query | Only objects changed after this instant (exclusive; ISO 8601 with time zone, write + as %2B in a URL). Lists are always sorted by updated_at ascending, and an object changed while you page through moves to the end and shows up again. Poll with an overlap: pass the updated_at of the last object you processed minus a minute and deduplicate by id + updated_at, because a change committed at the same moment as your previous poll can otherwise be missed. |
external_idstring | query | Only the object that carries this external_id of your app (zero or one result).max 100 chars |
category_idstring | query | Only products in this category; none for products without a category. |
availableboolean | query | false: only sold-out products; true: only available ones. |
Antwort200
OK
objectstringPflichtAlways
list.Werte:
"list"dataProduct[]PflichtThis page, oldest change first.
Felder darin (17)
objectstringPflichtAlways
product.Werte:
"product"idstring (uuid)PflichtThe product id.
external_idstringPflichtkann null seinYour app's external_id for this product; null if it has none. Always null in webhook events and in
GET /events: the envelope is built once and sent the same to every receiver, but this value belongs to a single app. Retrieve the object to see yours.1 to 100 chars
category_idstring (uuid)Pflichtkann null seinIts category; null: no category.
namestringPflichtName as the guest sees it.
max 200 chars
descriptionstringPflichtkann null seinDescription as the guest sees it.
max 2000 chars
priceintegerPflichtPrice including VAT, without options. Integer amount in the smallest currency unit (cents).
availablebooleanPflichtfalse = sold out: guests cannot order it until it is available again.
positionintegerPflichtSort order within its category, ascending.
allergensstring[]PflichtThe 14 EU allergens as keys:
gluten,crustaceans,eggs,fish,peanuts,soybeans,milk,nuts,celery,mustard,sesame,sulphites,lupin,molluscs. Common names in German, English, French, Spanish and Italian are mapped to the key (Soja→soybeans). Anything else is kept as the restaurant's own label and shown verbatim (at most 20 such labels, 40 characters each).max 34 items
dietsstring[]PflichtDiet marks:
vegetarian,vegan,gluten_free,lactose_free,halal,kosher(Gluten-freeis mapped togluten_free). Anything else is kept as the restaurant's own label, e.g. "gluten-free on request" (at most 14 such labels, 60 characters each). Never mixed with allergens.image_urlstring (uri)Pflichtkann null seinThe product photo as Flavoso serves it; null: none.
tax_classstringPflichtkann null seinThe product's own tax group; null: its category's, else
food.Werte:
"food""drink""alcohol"tax_ratesTaxRatesPflichtThe resolved VAT rates in basis points.
Felder darin (2)
dine_in_bpsintegerPflichtkann null seinRate when eaten in (table orders).
takeaway_bpsintegerPflichtkann null seinRate for pickup and delivery.
option_group_idsstring (uuid)[]PflichtIts option groups in the order the guest sees them: groups that belong to this product only first, then shared groups from the option library.
created_atstring (date-time)PflichtWhen the product was created. ISO 8601 in UTC (
Z).updated_atstring (date-time)PflichtLast change of one of its fields, including its option groups. ISO 8601 in UTC (
Z).
has_morebooleanPflichtWhether another page follows.
next_cursorstringPflichtkann null seinPass as
cursorfor the next page; null on the last page.
Fehler
400invalid_request: a parameter or the body is wrong, or it contains a query parameter or field the operation does not know.paramnames the field.401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.403insufficient_scope,plan_required(withparamwhen an order type is not in the plan) oraccount_inactive.429rate_limited: more than 120 requests per minute on this connection. WaitRetry-Afterseconds.500server_error. Safe to retry with the same Idempotency-Key.
curl "https://api.flavoso.com/v1/menu/products" \
-H "Authorization: Bearer $ACCESS_TOKEN"{
"object": "list",
"data": [
{
"object": "product",
"id": "3c9a7e5d-1b2f-4a6c-8d0e-7f5a3b1c9e2d",
"external_id": "PLU-1001",
"category_id": "8f6b2c1e-4d3a-4b5f-9c7d-2e1a0b9c8d7e",
"name": "Pizza Margherita",
"description": "Tomato, mozzarella, basil",
"price": 890,
"available": true,
"position": 0,
"allergens": [
"gluten",
"milk"
],
"diets": [
"vegetarian"
],
"image_url": "https://api.flavoso.com/storage/v1/object/public/tenant-assets/products/6f1e2d3c-4b5a-4c69-8d7e-0f1a2b3c4d5e/api-4f3a9c0d5b1e7a2c8d6f0e1b3a5c7d9e.jpg",
"tax_class": "food",
"tax_rates": {
"dine_in_bps": 700,
"takeaway_bps": 700
},
"option_group_ids": [
"5e4d3c2b-1a09-4f8e-b7d6-c5b4a3928170"
],
"created_at": "2026-10-06T09:12:00.000Z",
"updated_at": "2026-10-06T11:40:31.204Z"
}
],
"has_more": false,
"next_cursor": null
}Create a product
post/menu/products
- Recht:
menu:write- Tarif:
- jeder Tarif
Without position, the product goes last. image_url is downloaded before the product is created; if that fails, nothing is created (400 with param: "image_url"). With external_id, 409 external_id_taken if another object of the same type in this restaurant already carries it for your app.
Parameter
| Name | Ort | Beschreibung |
|---|---|---|
Idempotency-Keystring | header | Any unique string up to 255 printable characters. Retrying a POST with the same key returns the first response (header Idempotent-Replayed: true) instead of acting twice; the same key with a different body is 422 idempotency_key_reused. Kept for 24 hours. Answers that ask for a retry (5xx, 409 conflict) are not kept. While the first request runs, a retry gets 409 idempotency_in_progress; a first request that never finished frees the key after 2 minutes.max 255 chars |
Rumpfapplication/json
namestringPflichtName as the guest sees it.
1 to 200 chars
priceintegerPflichtPrice including VAT in the smallest currency unit (cents).
0 to 1000000
category_idstring (uuid)kann null seinIts category; null or missing: no category.
descriptionstringkann null seinDescription; empty means none.
max 2000 chars
availablebooleanfalse = sold out. Default true.
positionintegerSort order. Default: after the last product.
0 to 100000
allergensstring[]The 14 EU allergens as keys:
gluten,crustaceans,eggs,fish,peanuts,soybeans,milk,nuts,celery,mustard,sesame,sulphites,lupin,molluscs. Common names in German, English, French, Spanish and Italian are mapped to the key (Soja→soybeans). Anything else is kept as the restaurant's own label and shown verbatim (at most 20 such labels, 40 characters each).max 34 items
dietsstring[]Diet marks:
vegetarian,vegan,gluten_free,lactose_free,halal,kosher(Gluten-freeis mapped togluten_free). Anything else is kept as the restaurant's own label, e.g. "gluten-free on request" (at most 14 such labels, 60 characters each). Never mixed with allergens.tax_classstringkann null seinTax group:
food,drinkoralcohol. Flavoso stores the group, not a percentage: the restaurant sets the rate per group for its country, andtax_ratesshows the result. null means: use the category's group, elsefood.Werte:
"food""drink""alcohol"image_urlstring (uri)kann null seinAn https URL of a PNG, JPEG or WebP image of at most 1 MB. Flavoso downloads it (public hosts only), checks the bytes and stores its own copy; the product then shows Flavoso's URL. The same URL again does not download again; to replace an image under the same URL, add a version parameter (
?v=2). null removes the image.max 1000 chars
option_group_idsstring (uuid)[]Shared option groups to attach, in order.
max 50 items
external_idstringYour own id for this product; 409
external_id_takenif another product of this restaurant already has it.1 to 100 chars
Antwort201
OK
objectstringPflichtAlways
product.Werte:
"product"idstring (uuid)PflichtThe product id.
external_idstringPflichtkann null seinYour app's external_id for this product; null if it has none. Always null in webhook events and in
GET /events: the envelope is built once and sent the same to every receiver, but this value belongs to a single app. Retrieve the object to see yours.1 to 100 chars
category_idstring (uuid)Pflichtkann null seinIts category; null: no category.
namestringPflichtName as the guest sees it.
max 200 chars
descriptionstringPflichtkann null seinDescription as the guest sees it.
max 2000 chars
priceintegerPflichtPrice including VAT, without options. Integer amount in the smallest currency unit (cents).
availablebooleanPflichtfalse = sold out: guests cannot order it until it is available again.
positionintegerPflichtSort order within its category, ascending.
allergensstring[]PflichtThe 14 EU allergens as keys:
gluten,crustaceans,eggs,fish,peanuts,soybeans,milk,nuts,celery,mustard,sesame,sulphites,lupin,molluscs. Common names in German, English, French, Spanish and Italian are mapped to the key (Soja→soybeans). Anything else is kept as the restaurant's own label and shown verbatim (at most 20 such labels, 40 characters each).max 34 items
dietsstring[]PflichtDiet marks:
vegetarian,vegan,gluten_free,lactose_free,halal,kosher(Gluten-freeis mapped togluten_free). Anything else is kept as the restaurant's own label, e.g. "gluten-free on request" (at most 14 such labels, 60 characters each). Never mixed with allergens.image_urlstring (uri)Pflichtkann null seinThe product photo as Flavoso serves it; null: none.
tax_classstringPflichtkann null seinThe product's own tax group; null: its category's, else
food.Werte:
"food""drink""alcohol"tax_ratesTaxRatesPflichtThe resolved VAT rates in basis points.
Felder darin (2)
dine_in_bpsintegerPflichtkann null seinRate when eaten in (table orders).
takeaway_bpsintegerPflichtkann null seinRate for pickup and delivery.
option_group_idsstring (uuid)[]PflichtIts option groups in the order the guest sees them: groups that belong to this product only first, then shared groups from the option library.
created_atstring (date-time)PflichtWhen the product was created. ISO 8601 in UTC (
Z).updated_atstring (date-time)PflichtLast change of one of its fields, including its option groups. ISO 8601 in UTC (
Z).
Fehler
400invalid_request: a parameter or the body is wrong, or it contains a query parameter or field the operation does not know.paramnames the field.401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.403insufficient_scope,plan_required(withparamwhen an order type is not in the plan) oraccount_inactive.404not_found: no such object in this restaurant (objects of other restaurants are never found).409invalid_transition(the status of the object does not allow this),stage_not_used(the restaurant does not use this status),external_id_taken(your app already uses thisexternal_idfor another object of the same kind in this restaurant),slot_unavailable(the time or the table is taken),idempotency_in_progress(a request with this Idempotency-Key is still running) orconflict(the same data was changed elsewhere at the same moment, three times in a row; retry unchanged afterRetry-Afterseconds).413payload_too_large: the body is larger than the operation takes, counted in bytes: 64 KB, 2 MB forPUT /menu. Split the request.422idempotency_key_reused(the key was used with a different request) orlimit_exceeded(too many entries in one request, such as products inPUT /menu, or a limit of the restaurant is reached, such as 60 special days).429rate_limited: more than 120 requests per minute on this connection. WaitRetry-Afterseconds.500server_error. Safe to retry with the same Idempotency-Key.
curl -X POST "https://api.flavoso.com/v1/menu/products" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"name": "Pizza Margherita",
"price": 890,
"category_id": "8f6b2c1e-4d3a-4b5f-9c7d-2e1a0b9c8d7e",
"description": "Tomato, mozzarella, basil",
"allergens": [
"gluten",
"milk"
],
"diets": [
"vegetarian"
],
"tax_class": "food",
"option_group_ids": [
"5e4d3c2b-1a09-4f8e-b7d6-c5b4a3928170"
],
"external_id": "PLU-1001"
}'{
"name": "Pizza Margherita",
"price": 890,
"category_id": "8f6b2c1e-4d3a-4b5f-9c7d-2e1a0b9c8d7e",
"description": "Tomato, mozzarella, basil",
"allergens": [
"gluten",
"milk"
],
"diets": [
"vegetarian"
],
"tax_class": "food",
"option_group_ids": [
"5e4d3c2b-1a09-4f8e-b7d6-c5b4a3928170"
],
"external_id": "PLU-1001"
}{
"object": "product",
"id": "3c9a7e5d-1b2f-4a6c-8d0e-7f5a3b1c9e2d",
"external_id": "PLU-1001",
"category_id": "8f6b2c1e-4d3a-4b5f-9c7d-2e1a0b9c8d7e",
"name": "Pizza Margherita",
"description": "Tomato, mozzarella, basil",
"price": 890,
"available": true,
"position": 0,
"allergens": [
"gluten",
"milk"
],
"diets": [
"vegetarian"
],
"image_url": "https://api.flavoso.com/storage/v1/object/public/tenant-assets/products/6f1e2d3c-4b5a-4c69-8d7e-0f1a2b3c4d5e/api-4f3a9c0d5b1e7a2c8d6f0e1b3a5c7d9e.jpg",
"tax_class": "food",
"tax_rates": {
"dine_in_bps": 700,
"takeaway_bps": 700
},
"option_group_ids": [
"5e4d3c2b-1a09-4f8e-b7d6-c5b4a3928170"
],
"created_at": "2026-10-06T09:12:00.000Z",
"updated_at": "2026-10-06T11:40:31.204Z"
}Retrieve a product
get/menu/products/{id}
- Recht:
menu:read- Tarif:
- jeder Tarif
Parameter
| Name | Ort | Beschreibung |
|---|---|---|
idstring (uuid)Pflicht | path | The product id (UUID). |
Antwort200
OK
objectstringPflichtAlways
product.Werte:
"product"idstring (uuid)PflichtThe product id.
external_idstringPflichtkann null seinYour app's external_id for this product; null if it has none. Always null in webhook events and in
GET /events: the envelope is built once and sent the same to every receiver, but this value belongs to a single app. Retrieve the object to see yours.1 to 100 chars
category_idstring (uuid)Pflichtkann null seinIts category; null: no category.
namestringPflichtName as the guest sees it.
max 200 chars
descriptionstringPflichtkann null seinDescription as the guest sees it.
max 2000 chars
priceintegerPflichtPrice including VAT, without options. Integer amount in the smallest currency unit (cents).
availablebooleanPflichtfalse = sold out: guests cannot order it until it is available again.
positionintegerPflichtSort order within its category, ascending.
allergensstring[]PflichtThe 14 EU allergens as keys:
gluten,crustaceans,eggs,fish,peanuts,soybeans,milk,nuts,celery,mustard,sesame,sulphites,lupin,molluscs. Common names in German, English, French, Spanish and Italian are mapped to the key (Soja→soybeans). Anything else is kept as the restaurant's own label and shown verbatim (at most 20 such labels, 40 characters each).max 34 items
dietsstring[]PflichtDiet marks:
vegetarian,vegan,gluten_free,lactose_free,halal,kosher(Gluten-freeis mapped togluten_free). Anything else is kept as the restaurant's own label, e.g. "gluten-free on request" (at most 14 such labels, 60 characters each). Never mixed with allergens.image_urlstring (uri)Pflichtkann null seinThe product photo as Flavoso serves it; null: none.
tax_classstringPflichtkann null seinThe product's own tax group; null: its category's, else
food.Werte:
"food""drink""alcohol"tax_ratesTaxRatesPflichtThe resolved VAT rates in basis points.
Felder darin (2)
dine_in_bpsintegerPflichtkann null seinRate when eaten in (table orders).
takeaway_bpsintegerPflichtkann null seinRate for pickup and delivery.
option_group_idsstring (uuid)[]PflichtIts option groups in the order the guest sees them: groups that belong to this product only first, then shared groups from the option library.
created_atstring (date-time)PflichtWhen the product was created. ISO 8601 in UTC (
Z).updated_atstring (date-time)PflichtLast change of one of its fields, including its option groups. ISO 8601 in UTC (
Z).
Fehler
400invalid_request: a parameter or the body is wrong, or it contains a query parameter or field the operation does not know.paramnames the field.401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.403insufficient_scope,plan_required(withparamwhen an order type is not in the plan) oraccount_inactive.404not_found: no such object in this restaurant (objects of other restaurants are never found).429rate_limited: more than 120 requests per minute on this connection. WaitRetry-Afterseconds.500server_error. Safe to retry with the same Idempotency-Key.
curl "https://api.flavoso.com/v1/menu/products/PRODUCT_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN"{
"object": "product",
"id": "3c9a7e5d-1b2f-4a6c-8d0e-7f5a3b1c9e2d",
"external_id": "PLU-1001",
"category_id": "8f6b2c1e-4d3a-4b5f-9c7d-2e1a0b9c8d7e",
"name": "Pizza Margherita",
"description": "Tomato, mozzarella, basil",
"price": 890,
"available": true,
"position": 0,
"allergens": [
"gluten",
"milk"
],
"diets": [
"vegetarian"
],
"image_url": "https://api.flavoso.com/storage/v1/object/public/tenant-assets/products/6f1e2d3c-4b5a-4c69-8d7e-0f1a2b3c4d5e/api-4f3a9c0d5b1e7a2c8d6f0e1b3a5c7d9e.jpg",
"tax_class": "food",
"tax_rates": {
"dine_in_bps": 700,
"takeaway_bps": 700
},
"option_group_ids": [
"5e4d3c2b-1a09-4f8e-b7d6-c5b4a3928170"
],
"created_at": "2026-10-06T09:12:00.000Z",
"updated_at": "2026-10-06T11:40:31.204Z"
}Update a product
patch/menu/products/{id}
- Recht:
menu:write- Tarif:
- jeder Tarif
Any field: sold out or back (available is a target state, no toggling), price, name, description, category, position, allergens, diets, tax group, image, option groups (the complete list, in order) and your external_id. With external_id, 409 external_id_taken if another object of the same type in this restaurant already carries it for your app.
Parameter
| Name | Ort | Beschreibung |
|---|---|---|
idstring (uuid)Pflicht | path | The product id (UUID). |
Rumpfapplication/json
namestringNew name.
1 to 200 chars
priceintegerNew price in cents.
0 to 1000000
category_idstring (uuid)kann null seinMove to this category; null: no category.
descriptionstringkann null seinNew description; null or empty removes it.
max 2000 chars
availablebooleanfalse = sold out, true = available again.
positionintegerNew sort order.
0 to 100000
allergensstring[]Replaces the list. The 14 EU allergens as keys:
gluten,crustaceans,eggs,fish,peanuts,soybeans,milk,nuts,celery,mustard,sesame,sulphites,lupin,molluscs. Common names in German, English, French, Spanish and Italian are mapped to the key (Soja→soybeans). Anything else is kept as the restaurant's own label and shown verbatim (at most 20 such labels, 40 characters each).max 34 items
dietsstring[]Replaces the list. Diet marks:
vegetarian,vegan,gluten_free,lactose_free,halal,kosher(Gluten-freeis mapped togluten_free). Anything else is kept as the restaurant's own label, e.g. "gluten-free on request" (at most 14 such labels, 60 characters each). Never mixed with allergens.tax_classstringkann null seinTax group:
food,drinkoralcohol. Flavoso stores the group, not a percentage: the restaurant sets the rate per group for its country, andtax_ratesshows the result. null means: use the category's group, elsefood.Werte:
"food""drink""alcohol"image_urlstring (uri)kann null seinAn https URL of a PNG, JPEG or WebP image of at most 1 MB. Flavoso downloads it (public hosts only), checks the bytes and stores its own copy; the product then shows Flavoso's URL. The same URL again does not download again; to replace an image under the same URL, add a version parameter (
?v=2). null removes the image.max 1000 chars
option_group_idsstring (uuid)[]The complete list of option groups, in order; replaces the current one. A group that belongs to another product only cannot be attached. A group that belongs to this product only (
product_idset, e.g. from the menu import) cannot be detached, only deleted, and such groups always come first.max 50 items
external_idstringkann null seinSet or change your id; null removes it. Setting it on a product created in Flavoso lets your syncs take it over.
1 to 100 chars
Antwort200
OK
objectstringPflichtAlways
product.Werte:
"product"idstring (uuid)PflichtThe product id.
external_idstringPflichtkann null seinYour app's external_id for this product; null if it has none. Always null in webhook events and in
GET /events: the envelope is built once and sent the same to every receiver, but this value belongs to a single app. Retrieve the object to see yours.1 to 100 chars
category_idstring (uuid)Pflichtkann null seinIts category; null: no category.
namestringPflichtName as the guest sees it.
max 200 chars
descriptionstringPflichtkann null seinDescription as the guest sees it.
max 2000 chars
priceintegerPflichtPrice including VAT, without options. Integer amount in the smallest currency unit (cents).
availablebooleanPflichtfalse = sold out: guests cannot order it until it is available again.
positionintegerPflichtSort order within its category, ascending.
allergensstring[]PflichtThe 14 EU allergens as keys:
gluten,crustaceans,eggs,fish,peanuts,soybeans,milk,nuts,celery,mustard,sesame,sulphites,lupin,molluscs. Common names in German, English, French, Spanish and Italian are mapped to the key (Soja→soybeans). Anything else is kept as the restaurant's own label and shown verbatim (at most 20 such labels, 40 characters each).max 34 items
dietsstring[]PflichtDiet marks:
vegetarian,vegan,gluten_free,lactose_free,halal,kosher(Gluten-freeis mapped togluten_free). Anything else is kept as the restaurant's own label, e.g. "gluten-free on request" (at most 14 such labels, 60 characters each). Never mixed with allergens.image_urlstring (uri)Pflichtkann null seinThe product photo as Flavoso serves it; null: none.
tax_classstringPflichtkann null seinThe product's own tax group; null: its category's, else
food.Werte:
"food""drink""alcohol"tax_ratesTaxRatesPflichtThe resolved VAT rates in basis points.
Felder darin (2)
dine_in_bpsintegerPflichtkann null seinRate when eaten in (table orders).
takeaway_bpsintegerPflichtkann null seinRate for pickup and delivery.
option_group_idsstring (uuid)[]PflichtIts option groups in the order the guest sees them: groups that belong to this product only first, then shared groups from the option library.
created_atstring (date-time)PflichtWhen the product was created. ISO 8601 in UTC (
Z).updated_atstring (date-time)PflichtLast change of one of its fields, including its option groups. ISO 8601 in UTC (
Z).
Fehler
400invalid_request: a parameter or the body is wrong, or it contains a query parameter or field the operation does not know.paramnames the field.401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.403insufficient_scope,plan_required(withparamwhen an order type is not in the plan) oraccount_inactive.404not_found: no such object in this restaurant (objects of other restaurants are never found).409invalid_transition(the status of the object does not allow this),stage_not_used(the restaurant does not use this status),external_id_taken(your app already uses thisexternal_idfor another object of the same kind in this restaurant),slot_unavailable(the time or the table is taken),idempotency_in_progress(a request with this Idempotency-Key is still running) orconflict(the same data was changed elsewhere at the same moment, three times in a row; retry unchanged afterRetry-Afterseconds).413payload_too_large: the body is larger than the operation takes, counted in bytes: 64 KB, 2 MB forPUT /menu. Split the request.422idempotency_key_reused(the key was used with a different request) orlimit_exceeded(too many entries in one request, such as products inPUT /menu, or a limit of the restaurant is reached, such as 60 special days).429rate_limited: more than 120 requests per minute on this connection. WaitRetry-Afterseconds.500server_error. Safe to retry with the same Idempotency-Key.
curl -X PATCH "https://api.flavoso.com/v1/menu/products/PRODUCT_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"available": false
}'{
"available": false
}{
"object": "product",
"id": "3c9a7e5d-1b2f-4a6c-8d0e-7f5a3b1c9e2d",
"external_id": "PLU-1001",
"category_id": "8f6b2c1e-4d3a-4b5f-9c7d-2e1a0b9c8d7e",
"name": "Pizza Margherita",
"description": "Tomato, mozzarella, basil",
"price": 890,
"available": false,
"position": 0,
"allergens": [
"gluten",
"milk"
],
"diets": [
"vegetarian"
],
"image_url": "https://api.flavoso.com/storage/v1/object/public/tenant-assets/products/6f1e2d3c-4b5a-4c69-8d7e-0f1a2b3c4d5e/api-4f3a9c0d5b1e7a2c8d6f0e1b3a5c7d9e.jpg",
"tax_class": "food",
"tax_rates": {
"dine_in_bps": 700,
"takeaway_bps": 700
},
"option_group_ids": [
"5e4d3c2b-1a09-4f8e-b7d6-c5b4a3928170"
],
"created_at": "2026-10-06T09:12:00.000Z",
"updated_at": "2026-10-06T11:40:31.204Z"
}Delete a product
delete/menu/products/{id}
- Recht:
menu:write- Tarif:
- jeder Tarif
Deleted for good, as in the dashboard. Orders keep their items with the name at the time of ordering; groups that belonged to this product only are deleted with it. A stamp card that gives this product away is switched off.
Parameter
| Name | Ort | Beschreibung |
|---|---|---|
idstring (uuid)Pflicht | path | The product id (UUID). |
Antwort200
OK
objectstringPflichtThe type of the deleted object.
Werte:
"product""option_group""option"idstring (uuid)PflichtThe deleted object.
deletedbooleanPflichtAlways true.
Werte:
true
Fehler
400invalid_request: a parameter or the body is wrong, or it contains a query parameter or field the operation does not know.paramnames the field.401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.403insufficient_scope,plan_required(withparamwhen an order type is not in the plan) oraccount_inactive.404not_found: no such object in this restaurant (objects of other restaurants are never found).409invalid_transition(the status of the object does not allow this),stage_not_used(the restaurant does not use this status),external_id_taken(your app already uses thisexternal_idfor another object of the same kind in this restaurant),slot_unavailable(the time or the table is taken),idempotency_in_progress(a request with this Idempotency-Key is still running) orconflict(the same data was changed elsewhere at the same moment, three times in a row; retry unchanged afterRetry-Afterseconds).429rate_limited: more than 120 requests per minute on this connection. WaitRetry-Afterseconds.500server_error. Safe to retry with the same Idempotency-Key.
curl -X DELETE "https://api.flavoso.com/v1/menu/products/PRODUCT_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN"{
"object": "product",
"id": "3c9a7e5d-1b2f-4a6c-8d0e-7f5a3b1c9e2d",
"deleted": true
}List option groups
get/menu/option-groups
- Recht:
menu:read- Tarif:
- jeder Tarif
Each with its options. Sorted by updated_at ascending.
Parameter
| Name | Ort | Beschreibung |
|---|---|---|
limitinteger | query | Page size, 1 to 100. Default 25.1 to 100 · default 25 |
cursorstring | query | next_cursor of the previous page. Opaque. Send the same filters again with it; the cursor only remembers where the last page ended. |
updated_sincestring (date-time) | query | Only objects changed after this instant (exclusive; ISO 8601 with time zone, write + as %2B in a URL). Lists are always sorted by updated_at ascending, and an object changed while you page through moves to the end and shows up again. Poll with an overlap: pass the updated_at of the last object you processed minus a minute and deduplicate by id + updated_at, because a change committed at the same moment as your previous poll can otherwise be missed. |
external_idstring | query | Only the object that carries this external_id of your app (zero or one result).max 100 chars |
Antwort200
OK
objectstringPflichtAlways
list.Werte:
"list"dataOptionGroup[]PflichtThis page, oldest change first.
Felder darin (10)
objectstringPflichtAlways
option_group.Werte:
"option_group"idstring (uuid)PflichtThe option group id.
external_idstringPflichtkann null seinYour app's external_id for this group; null if it has none. Always null in webhook events and in
GET /events: the envelope is built once and sent the same to every receiver, but this value belongs to a single app. Retrieve the object to see yours.1 to 100 chars
namestringPflichtName as the guest sees it, e.g. Size, Extras.
max 120 chars
min_selectintegerPflichtHow many options the guest must pick at least (0 = optional).
0 to 20
max_selectintegerPflichtHow many options the guest may pick at most.
1 to 20
positionintegerPflichtSort order among groups.
product_idstring (uuid)Pflichtkann null seinThe product this group belongs to only; null for a shared group.
optionsOption[]PflichtIts options in order, including sold-out ones.
Felder darin (9)
objectstringPflichtAlways
option.Werte:
"option"idstring (uuid)PflichtThe option id.
external_idstringPflichtkann null seinYour app's external_id for this option; null if it has none. Always null in webhook events and in
GET /events: the envelope is built once and sent the same to every receiver, but this value belongs to a single app. Retrieve the object to see yours.1 to 100 chars
group_idstring (uuid)PflichtThe option group it belongs to.
namestringPflichtName as the guest sees it.
max 120 chars
price_deltaintegerPflichtSurcharge, may be negative. Integer amount in the smallest currency unit (cents).
availablebooleanPflichtfalse = sold out on every product that uses the group.
positionintegerPflichtSort order within the group.
updated_atstring (date-time)PflichtLast change of one of its fields. ISO 8601 in UTC (
Z).
updated_atstring (date-time)PflichtLast change of one of its own fields (not of its options). ISO 8601 in UTC (
Z).
has_morebooleanPflichtWhether another page follows.
next_cursorstringPflichtkann null seinPass as
cursorfor the next page; null on the last page.
Fehler
400invalid_request: a parameter or the body is wrong, or it contains a query parameter or field the operation does not know.paramnames the field.401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.403insufficient_scope,plan_required(withparamwhen an order type is not in the plan) oraccount_inactive.429rate_limited: more than 120 requests per minute on this connection. WaitRetry-Afterseconds.500server_error. Safe to retry with the same Idempotency-Key.
curl "https://api.flavoso.com/v1/menu/option-groups" \
-H "Authorization: Bearer $ACCESS_TOKEN"{
"object": "list",
"data": [
{
"object": "option_group",
"id": "5e4d3c2b-1a09-4f8e-b7d6-c5b4a3928170",
"external_id": "GRP-EXTRAS",
"name": "Extras",
"min_select": 0,
"max_select": 3,
"position": 0,
"product_id": null,
"options": [
{
"object": "option",
"id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
"external_id": "MOD-CHEESE",
"group_id": "5e4d3c2b-1a09-4f8e-b7d6-c5b4a3928170",
"name": "Extra cheese",
"price_delta": 150,
"available": true,
"position": 0,
"updated_at": "2026-10-06T11:40:31.204Z"
},
{
"object": "option",
"id": "2b3c4d5e-6f70-4182-93a4-b5c6d7e8f901",
"external_id": "MOD-SALAMI",
"group_id": "5e4d3c2b-1a09-4f8e-b7d6-c5b4a3928170",
"name": "Salami",
"price_delta": 200,
"available": false,
"position": 1,
"updated_at": "2026-10-06T11:40:31.204Z"
}
],
"updated_at": "2026-10-06T11:40:31.204Z"
}
],
"has_more": false,
"next_cursor": null
}Create an option group
post/menu/option-groups
- Recht:
menu:write- Tarif:
- jeder Tarif
Creates a shared group in the option library, optionally with its options. Attach it to products with option_group_ids. With external_id, 409 external_id_taken if another object of the same type in this restaurant already carries it for your app.
Parameter
| Name | Ort | Beschreibung |
|---|---|---|
Idempotency-Keystring | header | Any unique string up to 255 printable characters. Retrying a POST with the same key returns the first response (header Idempotent-Replayed: true) instead of acting twice; the same key with a different body is 422 idempotency_key_reused. Kept for 24 hours. Answers that ask for a retry (5xx, 409 conflict) are not kept. While the first request runs, a retry gets 409 idempotency_in_progress; a first request that never finished frees the key after 2 minutes.max 255 chars |
Rumpfapplication/json
namestringPflichtName as the guest sees it.
1 to 120 chars
min_selectintegerDefault 0; must not exceed max_select.
0 to 20
max_selectintegerDefault 1 (or min_select if higher).
1 to 20
positionintegerSort order. Default: after the last group.
0 to 100000
external_idstringYour own id for this group.
1 to 100 chars
optionsOptionInput[]Options to create with the group, in order (at most 100).
max 100 items
Felder darin (5)
namestringPflichtName as the guest sees it.
1 to 120 chars
price_deltaintegerSurcharge in cents, may be negative. Default 0.
-100000 to 100000
availablebooleanfalse = sold out. Default true.
positionintegerSort order. Default: after the last option of the group.
0 to 100000
external_idstringYour own id for this option.
1 to 100 chars
Antwort201
OK
objectstringPflichtAlways
option_group.Werte:
"option_group"idstring (uuid)PflichtThe option group id.
external_idstringPflichtkann null seinYour app's external_id for this group; null if it has none. Always null in webhook events and in
GET /events: the envelope is built once and sent the same to every receiver, but this value belongs to a single app. Retrieve the object to see yours.1 to 100 chars
namestringPflichtName as the guest sees it, e.g. Size, Extras.
max 120 chars
min_selectintegerPflichtHow many options the guest must pick at least (0 = optional).
0 to 20
max_selectintegerPflichtHow many options the guest may pick at most.
1 to 20
positionintegerPflichtSort order among groups.
product_idstring (uuid)Pflichtkann null seinThe product this group belongs to only; null for a shared group.
optionsOption[]PflichtIts options in order, including sold-out ones.
Felder darin (9)
objectstringPflichtAlways
option.Werte:
"option"idstring (uuid)PflichtThe option id.
external_idstringPflichtkann null seinYour app's external_id for this option; null if it has none. Always null in webhook events and in
GET /events: the envelope is built once and sent the same to every receiver, but this value belongs to a single app. Retrieve the object to see yours.1 to 100 chars
group_idstring (uuid)PflichtThe option group it belongs to.
namestringPflichtName as the guest sees it.
max 120 chars
price_deltaintegerPflichtSurcharge, may be negative. Integer amount in the smallest currency unit (cents).
availablebooleanPflichtfalse = sold out on every product that uses the group.
positionintegerPflichtSort order within the group.
updated_atstring (date-time)PflichtLast change of one of its fields. ISO 8601 in UTC (
Z).
updated_atstring (date-time)PflichtLast change of one of its own fields (not of its options). ISO 8601 in UTC (
Z).
Fehler
400invalid_request: a parameter or the body is wrong, or it contains a query parameter or field the operation does not know.paramnames the field.401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.403insufficient_scope,plan_required(withparamwhen an order type is not in the plan) oraccount_inactive.404not_found: no such object in this restaurant (objects of other restaurants are never found).409invalid_transition(the status of the object does not allow this),stage_not_used(the restaurant does not use this status),external_id_taken(your app already uses thisexternal_idfor another object of the same kind in this restaurant),slot_unavailable(the time or the table is taken),idempotency_in_progress(a request with this Idempotency-Key is still running) orconflict(the same data was changed elsewhere at the same moment, three times in a row; retry unchanged afterRetry-Afterseconds).413payload_too_large: the body is larger than the operation takes, counted in bytes: 64 KB, 2 MB forPUT /menu. Split the request.422idempotency_key_reused(the key was used with a different request) orlimit_exceeded(too many entries in one request, such as products inPUT /menu, or a limit of the restaurant is reached, such as 60 special days).429rate_limited: more than 120 requests per minute on this connection. WaitRetry-Afterseconds.500server_error. Safe to retry with the same Idempotency-Key.
curl -X POST "https://api.flavoso.com/v1/menu/option-groups" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"name": "Extras",
"min_select": 0,
"max_select": 3,
"external_id": "GRP-EXTRAS",
"options": [
{
"name": "Extra cheese",
"price_delta": 150,
"external_id": "MOD-CHEESE"
},
{
"name": "Salami",
"price_delta": 200,
"external_id": "MOD-SALAMI"
}
]
}'{
"name": "Extras",
"min_select": 0,
"max_select": 3,
"external_id": "GRP-EXTRAS",
"options": [
{
"name": "Extra cheese",
"price_delta": 150,
"external_id": "MOD-CHEESE"
},
{
"name": "Salami",
"price_delta": 200,
"external_id": "MOD-SALAMI"
}
]
}{
"object": "option_group",
"id": "5e4d3c2b-1a09-4f8e-b7d6-c5b4a3928170",
"external_id": "GRP-EXTRAS",
"name": "Extras",
"min_select": 0,
"max_select": 3,
"position": 0,
"product_id": null,
"options": [
{
"object": "option",
"id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
"external_id": "MOD-CHEESE",
"group_id": "5e4d3c2b-1a09-4f8e-b7d6-c5b4a3928170",
"name": "Extra cheese",
"price_delta": 150,
"available": true,
"position": 0,
"updated_at": "2026-10-06T11:40:31.204Z"
},
{
"object": "option",
"id": "2b3c4d5e-6f70-4182-93a4-b5c6d7e8f901",
"external_id": "MOD-SALAMI",
"group_id": "5e4d3c2b-1a09-4f8e-b7d6-c5b4a3928170",
"name": "Salami",
"price_delta": 200,
"available": false,
"position": 1,
"updated_at": "2026-10-06T11:40:31.204Z"
}
],
"updated_at": "2026-10-06T11:40:31.204Z"
}Retrieve an option group
get/menu/option-groups/{id}
- Recht:
menu:read- Tarif:
- jeder Tarif
Parameter
| Name | Ort | Beschreibung |
|---|---|---|
idstring (uuid)Pflicht | path | The option group id (UUID). |
Antwort200
OK
objectstringPflichtAlways
option_group.Werte:
"option_group"idstring (uuid)PflichtThe option group id.
external_idstringPflichtkann null seinYour app's external_id for this group; null if it has none. Always null in webhook events and in
GET /events: the envelope is built once and sent the same to every receiver, but this value belongs to a single app. Retrieve the object to see yours.1 to 100 chars
namestringPflichtName as the guest sees it, e.g. Size, Extras.
max 120 chars
min_selectintegerPflichtHow many options the guest must pick at least (0 = optional).
0 to 20
max_selectintegerPflichtHow many options the guest may pick at most.
1 to 20
positionintegerPflichtSort order among groups.
product_idstring (uuid)Pflichtkann null seinThe product this group belongs to only; null for a shared group.
optionsOption[]PflichtIts options in order, including sold-out ones.
Felder darin (9)
objectstringPflichtAlways
option.Werte:
"option"idstring (uuid)PflichtThe option id.
external_idstringPflichtkann null seinYour app's external_id for this option; null if it has none. Always null in webhook events and in
GET /events: the envelope is built once and sent the same to every receiver, but this value belongs to a single app. Retrieve the object to see yours.1 to 100 chars
group_idstring (uuid)PflichtThe option group it belongs to.
namestringPflichtName as the guest sees it.
max 120 chars
price_deltaintegerPflichtSurcharge, may be negative. Integer amount in the smallest currency unit (cents).
availablebooleanPflichtfalse = sold out on every product that uses the group.
positionintegerPflichtSort order within the group.
updated_atstring (date-time)PflichtLast change of one of its fields. ISO 8601 in UTC (
Z).
updated_atstring (date-time)PflichtLast change of one of its own fields (not of its options). ISO 8601 in UTC (
Z).
Fehler
400invalid_request: a parameter or the body is wrong, or it contains a query parameter or field the operation does not know.paramnames the field.401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.403insufficient_scope,plan_required(withparamwhen an order type is not in the plan) oraccount_inactive.404not_found: no such object in this restaurant (objects of other restaurants are never found).429rate_limited: more than 120 requests per minute on this connection. WaitRetry-Afterseconds.500server_error. Safe to retry with the same Idempotency-Key.
curl "https://api.flavoso.com/v1/menu/option-groups/OPTION_GROUP_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN"{
"object": "option_group",
"id": "5e4d3c2b-1a09-4f8e-b7d6-c5b4a3928170",
"external_id": "GRP-EXTRAS",
"name": "Extras",
"min_select": 0,
"max_select": 3,
"position": 0,
"product_id": null,
"options": [
{
"object": "option",
"id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
"external_id": "MOD-CHEESE",
"group_id": "5e4d3c2b-1a09-4f8e-b7d6-c5b4a3928170",
"name": "Extra cheese",
"price_delta": 150,
"available": true,
"position": 0,
"updated_at": "2026-10-06T11:40:31.204Z"
},
{
"object": "option",
"id": "2b3c4d5e-6f70-4182-93a4-b5c6d7e8f901",
"external_id": "MOD-SALAMI",
"group_id": "5e4d3c2b-1a09-4f8e-b7d6-c5b4a3928170",
"name": "Salami",
"price_delta": 200,
"available": false,
"position": 1,
"updated_at": "2026-10-06T11:40:31.204Z"
}
],
"updated_at": "2026-10-06T11:40:31.204Z"
}Update an option group
patch/menu/option-groups/{id}
- Recht:
menu:write- Tarif:
- jeder Tarif
Name, selection limits, position and your external_id. With external_id, 409 external_id_taken if another object of the same type in this restaurant already carries it for your app.
Parameter
| Name | Ort | Beschreibung |
|---|---|---|
idstring (uuid)Pflicht | path | The option group id (UUID). |
Rumpfapplication/json
namestringNew name.
1 to 120 chars
min_selectintegerNew minimum; must not exceed the (new) maximum.
0 to 20
max_selectintegerNew maximum.
1 to 20
positionintegerNew sort order.
0 to 100000
external_idstringkann null seinSet or change your id; null removes it.
1 to 100 chars
Antwort200
OK
objectstringPflichtAlways
option_group.Werte:
"option_group"idstring (uuid)PflichtThe option group id.
external_idstringPflichtkann null seinYour app's external_id for this group; null if it has none. Always null in webhook events and in
GET /events: the envelope is built once and sent the same to every receiver, but this value belongs to a single app. Retrieve the object to see yours.1 to 100 chars
namestringPflichtName as the guest sees it, e.g. Size, Extras.
max 120 chars
min_selectintegerPflichtHow many options the guest must pick at least (0 = optional).
0 to 20
max_selectintegerPflichtHow many options the guest may pick at most.
1 to 20
positionintegerPflichtSort order among groups.
product_idstring (uuid)Pflichtkann null seinThe product this group belongs to only; null for a shared group.
optionsOption[]PflichtIts options in order, including sold-out ones.
Felder darin (9)
objectstringPflichtAlways
option.Werte:
"option"idstring (uuid)PflichtThe option id.
external_idstringPflichtkann null seinYour app's external_id for this option; null if it has none. Always null in webhook events and in
GET /events: the envelope is built once and sent the same to every receiver, but this value belongs to a single app. Retrieve the object to see yours.1 to 100 chars
group_idstring (uuid)PflichtThe option group it belongs to.
namestringPflichtName as the guest sees it.
max 120 chars
price_deltaintegerPflichtSurcharge, may be negative. Integer amount in the smallest currency unit (cents).
availablebooleanPflichtfalse = sold out on every product that uses the group.
positionintegerPflichtSort order within the group.
updated_atstring (date-time)PflichtLast change of one of its fields. ISO 8601 in UTC (
Z).
updated_atstring (date-time)PflichtLast change of one of its own fields (not of its options). ISO 8601 in UTC (
Z).
Fehler
400invalid_request: a parameter or the body is wrong, or it contains a query parameter or field the operation does not know.paramnames the field.401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.403insufficient_scope,plan_required(withparamwhen an order type is not in the plan) oraccount_inactive.404not_found: no such object in this restaurant (objects of other restaurants are never found).409invalid_transition(the status of the object does not allow this),stage_not_used(the restaurant does not use this status),external_id_taken(your app already uses thisexternal_idfor another object of the same kind in this restaurant),slot_unavailable(the time or the table is taken),idempotency_in_progress(a request with this Idempotency-Key is still running) orconflict(the same data was changed elsewhere at the same moment, three times in a row; retry unchanged afterRetry-Afterseconds).413payload_too_large: the body is larger than the operation takes, counted in bytes: 64 KB, 2 MB forPUT /menu. Split the request.422idempotency_key_reused(the key was used with a different request) orlimit_exceeded(too many entries in one request, such as products inPUT /menu, or a limit of the restaurant is reached, such as 60 special days).429rate_limited: more than 120 requests per minute on this connection. WaitRetry-Afterseconds.500server_error. Safe to retry with the same Idempotency-Key.
curl -X PATCH "https://api.flavoso.com/v1/menu/option-groups/OPTION_GROUP_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"max_select": 2
}'{
"max_select": 2
}{
"object": "option_group",
"id": "5e4d3c2b-1a09-4f8e-b7d6-c5b4a3928170",
"external_id": "GRP-EXTRAS",
"name": "Extras",
"min_select": 0,
"max_select": 2,
"position": 0,
"product_id": null,
"options": [
{
"object": "option",
"id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
"external_id": "MOD-CHEESE",
"group_id": "5e4d3c2b-1a09-4f8e-b7d6-c5b4a3928170",
"name": "Extra cheese",
"price_delta": 150,
"available": true,
"position": 0,
"updated_at": "2026-10-06T11:40:31.204Z"
},
{
"object": "option",
"id": "2b3c4d5e-6f70-4182-93a4-b5c6d7e8f901",
"external_id": "MOD-SALAMI",
"group_id": "5e4d3c2b-1a09-4f8e-b7d6-c5b4a3928170",
"name": "Salami",
"price_delta": 200,
"available": false,
"position": 1,
"updated_at": "2026-10-06T11:40:31.204Z"
}
],
"updated_at": "2026-10-06T11:40:31.204Z"
}Delete an option group
delete/menu/option-groups/{id}
- Recht:
menu:write- Tarif:
- jeder Tarif
As in the dashboard: its options and its attachments to products go with it.
Parameter
| Name | Ort | Beschreibung |
|---|---|---|
idstring (uuid)Pflicht | path | The option group id (UUID). |
Antwort200
OK
objectstringPflichtThe type of the deleted object.
Werte:
"product""option_group""option"idstring (uuid)PflichtThe deleted object.
deletedbooleanPflichtAlways true.
Werte:
true
Fehler
400invalid_request: a parameter or the body is wrong, or it contains a query parameter or field the operation does not know.paramnames the field.401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.403insufficient_scope,plan_required(withparamwhen an order type is not in the plan) oraccount_inactive.404not_found: no such object in this restaurant (objects of other restaurants are never found).409invalid_transition(the status of the object does not allow this),stage_not_used(the restaurant does not use this status),external_id_taken(your app already uses thisexternal_idfor another object of the same kind in this restaurant),slot_unavailable(the time or the table is taken),idempotency_in_progress(a request with this Idempotency-Key is still running) orconflict(the same data was changed elsewhere at the same moment, three times in a row; retry unchanged afterRetry-Afterseconds).429rate_limited: more than 120 requests per minute on this connection. WaitRetry-Afterseconds.500server_error. Safe to retry with the same Idempotency-Key.
curl -X DELETE "https://api.flavoso.com/v1/menu/option-groups/OPTION_GROUP_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN"{
"object": "option_group",
"id": "5e4d3c2b-1a09-4f8e-b7d6-c5b4a3928170",
"deleted": true
}Add an option to an option group
post/menu/option-groups/{id}/options
- Recht:
menu:write- Tarif:
- jeder Tarif
Without position, the option goes last in the group. With external_id, 409 external_id_taken if another object of the same type in this restaurant already carries it for your app.
Parameter
| Name | Ort | Beschreibung |
|---|---|---|
idstring (uuid)Pflicht | path | The option group id (UUID). |
Idempotency-Keystring | header | Any unique string up to 255 printable characters. Retrying a POST with the same key returns the first response (header Idempotent-Replayed: true) instead of acting twice; the same key with a different body is 422 idempotency_key_reused. Kept for 24 hours. Answers that ask for a retry (5xx, 409 conflict) are not kept. While the first request runs, a retry gets 409 idempotency_in_progress; a first request that never finished frees the key after 2 minutes.max 255 chars |
Rumpfapplication/json
namestringPflichtName as the guest sees it.
1 to 120 chars
price_deltaintegerSurcharge in cents, may be negative. Default 0.
-100000 to 100000
availablebooleanfalse = sold out. Default true.
positionintegerSort order. Default: after the last option of the group.
0 to 100000
external_idstringYour own id for this option.
1 to 100 chars
Antwort201
OK
objectstringPflichtAlways
option.Werte:
"option"idstring (uuid)PflichtThe option id.
external_idstringPflichtkann null seinYour app's external_id for this option; null if it has none. Always null in webhook events and in
GET /events: the envelope is built once and sent the same to every receiver, but this value belongs to a single app. Retrieve the object to see yours.1 to 100 chars
group_idstring (uuid)PflichtThe option group it belongs to.
namestringPflichtName as the guest sees it.
max 120 chars
price_deltaintegerPflichtSurcharge, may be negative. Integer amount in the smallest currency unit (cents).
availablebooleanPflichtfalse = sold out on every product that uses the group.
positionintegerPflichtSort order within the group.
updated_atstring (date-time)PflichtLast change of one of its fields. ISO 8601 in UTC (
Z).
Fehler
400invalid_request: a parameter or the body is wrong, or it contains a query parameter or field the operation does not know.paramnames the field.401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.403insufficient_scope,plan_required(withparamwhen an order type is not in the plan) oraccount_inactive.404not_found: no such object in this restaurant (objects of other restaurants are never found).409invalid_transition(the status of the object does not allow this),stage_not_used(the restaurant does not use this status),external_id_taken(your app already uses thisexternal_idfor another object of the same kind in this restaurant),slot_unavailable(the time or the table is taken),idempotency_in_progress(a request with this Idempotency-Key is still running) orconflict(the same data was changed elsewhere at the same moment, three times in a row; retry unchanged afterRetry-Afterseconds).413payload_too_large: the body is larger than the operation takes, counted in bytes: 64 KB, 2 MB forPUT /menu. Split the request.422idempotency_key_reused(the key was used with a different request) orlimit_exceeded(too many entries in one request, such as products inPUT /menu, or a limit of the restaurant is reached, such as 60 special days).429rate_limited: more than 120 requests per minute on this connection. WaitRetry-Afterseconds.500server_error. Safe to retry with the same Idempotency-Key.
curl -X POST "https://api.flavoso.com/v1/menu/option-groups/OPTION_GROUP_ID/options" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"name": "Extra cheese",
"price_delta": 150,
"external_id": "MOD-CHEESE"
}'{
"name": "Extra cheese",
"price_delta": 150,
"external_id": "MOD-CHEESE"
}{
"object": "option",
"id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
"external_id": "MOD-CHEESE",
"group_id": "5e4d3c2b-1a09-4f8e-b7d6-c5b4a3928170",
"name": "Extra cheese",
"price_delta": 150,
"available": true,
"position": 0,
"updated_at": "2026-10-06T11:40:31.204Z"
}List options
get/menu/options
- Recht:
menu:read- Tarif:
- jeder Tarif
Every option of every group, sorted by updated_at ascending, with the same paging and polling rules as the other menu lists. The way to follow surcharges and sold-out states of options: a changed option changes neither its product nor its group. Options are created in their group (POST /menu/option-groups/{id}/options).
Parameter
| Name | Ort | Beschreibung |
|---|---|---|
limitinteger | query | Page size, 1 to 100. Default 25.1 to 100 · default 25 |
cursorstring | query | next_cursor of the previous page. Opaque. Send the same filters again with it; the cursor only remembers where the last page ended. |
updated_sincestring (date-time) | query | Only objects changed after this instant (exclusive; ISO 8601 with time zone, write + as %2B in a URL). Lists are always sorted by updated_at ascending, and an object changed while you page through moves to the end and shows up again. Poll with an overlap: pass the updated_at of the last object you processed minus a minute and deduplicate by id + updated_at, because a change committed at the same moment as your previous poll can otherwise be missed. |
external_idstring | query | Only the object that carries this external_id of your app (zero or one result).max 100 chars |
Antwort200
OK
objectstringPflichtAlways
list.Werte:
"list"dataOption[]PflichtThis page, oldest change first.
Felder darin (9)
objectstringPflichtAlways
option.Werte:
"option"idstring (uuid)PflichtThe option id.
external_idstringPflichtkann null seinYour app's external_id for this option; null if it has none. Always null in webhook events and in
GET /events: the envelope is built once and sent the same to every receiver, but this value belongs to a single app. Retrieve the object to see yours.1 to 100 chars
group_idstring (uuid)PflichtThe option group it belongs to.
namestringPflichtName as the guest sees it.
max 120 chars
price_deltaintegerPflichtSurcharge, may be negative. Integer amount in the smallest currency unit (cents).
availablebooleanPflichtfalse = sold out on every product that uses the group.
positionintegerPflichtSort order within the group.
updated_atstring (date-time)PflichtLast change of one of its fields. ISO 8601 in UTC (
Z).
has_morebooleanPflichtWhether another page follows.
next_cursorstringPflichtkann null seinPass as
cursorfor the next page; null on the last page.
Fehler
400invalid_request: a parameter or the body is wrong, or it contains a query parameter or field the operation does not know.paramnames the field.401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.403insufficient_scope,plan_required(withparamwhen an order type is not in the plan) oraccount_inactive.429rate_limited: more than 120 requests per minute on this connection. WaitRetry-Afterseconds.500server_error. Safe to retry with the same Idempotency-Key.
curl "https://api.flavoso.com/v1/menu/options" \
-H "Authorization: Bearer $ACCESS_TOKEN"{
"object": "list",
"data": [
{
"object": "option",
"id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
"external_id": "MOD-CHEESE",
"group_id": "5e4d3c2b-1a09-4f8e-b7d6-c5b4a3928170",
"name": "Extra cheese",
"price_delta": 150,
"available": true,
"position": 0,
"updated_at": "2026-10-06T11:40:31.204Z"
}
],
"has_more": false,
"next_cursor": null
}Retrieve an option
get/menu/options/{id}
- Recht:
menu:read- Tarif:
- jeder Tarif
Parameter
| Name | Ort | Beschreibung |
|---|---|---|
idstring (uuid)Pflicht | path | The option id (UUID). |
Antwort200
OK
objectstringPflichtAlways
option.Werte:
"option"idstring (uuid)PflichtThe option id.
external_idstringPflichtkann null seinYour app's external_id for this option; null if it has none. Always null in webhook events and in
GET /events: the envelope is built once and sent the same to every receiver, but this value belongs to a single app. Retrieve the object to see yours.1 to 100 chars
group_idstring (uuid)PflichtThe option group it belongs to.
namestringPflichtName as the guest sees it.
max 120 chars
price_deltaintegerPflichtSurcharge, may be negative. Integer amount in the smallest currency unit (cents).
availablebooleanPflichtfalse = sold out on every product that uses the group.
positionintegerPflichtSort order within the group.
updated_atstring (date-time)PflichtLast change of one of its fields. ISO 8601 in UTC (
Z).
Fehler
400invalid_request: a parameter or the body is wrong, or it contains a query parameter or field the operation does not know.paramnames the field.401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.403insufficient_scope,plan_required(withparamwhen an order type is not in the plan) oraccount_inactive.404not_found: no such object in this restaurant (objects of other restaurants are never found).429rate_limited: more than 120 requests per minute on this connection. WaitRetry-Afterseconds.500server_error. Safe to retry with the same Idempotency-Key.
curl "https://api.flavoso.com/v1/menu/options/OPTION_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN"{
"object": "option",
"id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
"external_id": "MOD-CHEESE",
"group_id": "5e4d3c2b-1a09-4f8e-b7d6-c5b4a3928170",
"name": "Extra cheese",
"price_delta": 150,
"available": true,
"position": 0,
"updated_at": "2026-10-06T11:40:31.204Z"
}Update an option
patch/menu/options/{id}
- Recht:
menu:write- Tarif:
- jeder Tarif
Sold out or back (a target state, no toggling), surcharge, name, position, group (group_id moves it) and your external_id. A sold-out option disappears from every product that uses its group. With external_id, 409 external_id_taken if another object of the same type in this restaurant already carries it for your app.
Parameter
| Name | Ort | Beschreibung |
|---|---|---|
idstring (uuid)Pflicht | path | The option id (UUID). |
Rumpfapplication/json
namestringNew name.
1 to 120 chars
price_deltaintegerNew surcharge in cents, may be negative.
-100000 to 100000
availablebooleanfalse = sold out, true = available again. A target state, no toggling.
positionintegerNew sort order.
0 to 100000
group_idstring (uuid)Move the option into this option group.
external_idstringkann null seinSet or change your id; null removes it.
1 to 100 chars
Antwort200
OK
objectstringPflichtAlways
option.Werte:
"option"idstring (uuid)PflichtThe option id.
external_idstringPflichtkann null seinYour app's external_id for this option; null if it has none. Always null in webhook events and in
GET /events: the envelope is built once and sent the same to every receiver, but this value belongs to a single app. Retrieve the object to see yours.1 to 100 chars
group_idstring (uuid)PflichtThe option group it belongs to.
namestringPflichtName as the guest sees it.
max 120 chars
price_deltaintegerPflichtSurcharge, may be negative. Integer amount in the smallest currency unit (cents).
availablebooleanPflichtfalse = sold out on every product that uses the group.
positionintegerPflichtSort order within the group.
updated_atstring (date-time)PflichtLast change of one of its fields. ISO 8601 in UTC (
Z).
Fehler
400invalid_request: a parameter or the body is wrong, or it contains a query parameter or field the operation does not know.paramnames the field.401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.403insufficient_scope,plan_required(withparamwhen an order type is not in the plan) oraccount_inactive.404not_found: no such object in this restaurant (objects of other restaurants are never found).409invalid_transition(the status of the object does not allow this),stage_not_used(the restaurant does not use this status),external_id_taken(your app already uses thisexternal_idfor another object of the same kind in this restaurant),slot_unavailable(the time or the table is taken),idempotency_in_progress(a request with this Idempotency-Key is still running) orconflict(the same data was changed elsewhere at the same moment, three times in a row; retry unchanged afterRetry-Afterseconds).413payload_too_large: the body is larger than the operation takes, counted in bytes: 64 KB, 2 MB forPUT /menu. Split the request.422idempotency_key_reused(the key was used with a different request) orlimit_exceeded(too many entries in one request, such as products inPUT /menu, or a limit of the restaurant is reached, such as 60 special days).429rate_limited: more than 120 requests per minute on this connection. WaitRetry-Afterseconds.500server_error. Safe to retry with the same Idempotency-Key.
curl -X PATCH "https://api.flavoso.com/v1/menu/options/OPTION_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"available": false
}'{
"available": false
}{
"object": "option",
"id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
"external_id": "MOD-CHEESE",
"group_id": "5e4d3c2b-1a09-4f8e-b7d6-c5b4a3928170",
"name": "Extra cheese",
"price_delta": 150,
"available": false,
"position": 0,
"updated_at": "2026-10-06T11:40:31.204Z"
}Delete an option
delete/menu/options/{id}
- Recht:
menu:write- Tarif:
- jeder Tarif
Parameter
| Name | Ort | Beschreibung |
|---|---|---|
idstring (uuid)Pflicht | path | The option id (UUID). |
Antwort200
OK
objectstringPflichtThe type of the deleted object.
Werte:
"product""option_group""option"idstring (uuid)PflichtThe deleted object.
deletedbooleanPflichtAlways true.
Werte:
true
Fehler
400invalid_request: a parameter or the body is wrong, or it contains a query parameter or field the operation does not know.paramnames the field.401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.403insufficient_scope,plan_required(withparamwhen an order type is not in the plan) oraccount_inactive.404not_found: no such object in this restaurant (objects of other restaurants are never found).409invalid_transition(the status of the object does not allow this),stage_not_used(the restaurant does not use this status),external_id_taken(your app already uses thisexternal_idfor another object of the same kind in this restaurant),slot_unavailable(the time or the table is taken),idempotency_in_progress(a request with this Idempotency-Key is still running) orconflict(the same data was changed elsewhere at the same moment, three times in a row; retry unchanged afterRetry-Afterseconds).429rate_limited: more than 120 requests per minute on this connection. WaitRetry-Afterseconds.500server_error. Safe to retry with the same Idempotency-Key.
curl -X DELETE "https://api.flavoso.com/v1/menu/options/OPTION_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN"{
"object": "option",
"id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
"deleted": true
}Etwas unklar geblieben? Schreib an support@flavoso.com und nenne die Kennung aus X-Request-Id. Damit finden wir deine Anfrage im Protokoll.