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

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

  • objectstringPflicht

    Always menu.

    Werte:"menu"

  • currencystringPflicht

    Currency of all amounts (ISO 4217).

  • categoriesCategory[]Pflicht

    All categories by position.

    Felder darin (8)
    • objectstringPflicht

      Always category.

      Werte:"category"

    • idstring (uuid)Pflicht

      The category id.

    • external_idstringPflichtkann null sein

      Your 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

    • namestringPflicht

      Name as the guest sees it.

      max 80 chars

    • positionintegerPflicht

      Sort order on the menu, ascending.

    • tax_classstringPflichtkann null sein

      Tax group for the products in it that have none of their own. null: food.

      Werte:"food""drink""alcohol"

    • created_atstring (date-time)Pflicht

      When the category was created. ISO 8601 in UTC (Z).

    • updated_atstring (date-time)Pflicht

      Last change of one of its fields. ISO 8601 in UTC (Z).

  • productsProduct[]Pflicht

    All products by position.

    Felder darin (17)
    • objectstringPflicht

      Always product.

      Werte:"product"

    • idstring (uuid)Pflicht

      The product id.

    • external_idstringPflichtkann null sein

      Your 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 sein

      Its category; null: no category.

    • namestringPflicht

      Name as the guest sees it.

      max 200 chars

    • descriptionstringPflichtkann null sein

      Description as the guest sees it.

      max 2000 chars

    • priceintegerPflicht

      Price including VAT, without options. Integer amount in the smallest currency unit (cents).

    • availablebooleanPflicht

      false = sold out: guests cannot order it until it is available again.

    • positionintegerPflicht

      Sort order within its category, ascending.

    • allergensstring[]Pflicht

      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[]Pflicht

      Diet marks: vegetarian, vegan, gluten_free, lactose_free, halal, kosher (Gluten-free is mapped to gluten_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 sein

      The product photo as Flavoso serves it; null: none.

    • tax_classstringPflichtkann null sein

      The product's own tax group; null: its category's, else food.

      Werte:"food""drink""alcohol"

    • tax_ratesTaxRatesPflicht

      The resolved VAT rates in basis points.

      Felder darin (2)
      • dine_in_bpsintegerPflichtkann null sein

        Rate when eaten in (table orders).

      • takeaway_bpsintegerPflichtkann null sein

        Rate for pickup and delivery.

    • option_group_idsstring (uuid)[]Pflicht

      Its 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)Pflicht

      When the product was created. ISO 8601 in UTC (Z).

    • updated_atstring (date-time)Pflicht

      Last change of one of its fields, including its option groups. ISO 8601 in UTC (Z).

  • option_groupsOptionGroup[]Pflicht

    All option groups by position, each with its options.

    Felder darin (10)
    • objectstringPflicht

      Always option_group.

      Werte:"option_group"

    • idstring (uuid)Pflicht

      The option group id.

    • external_idstringPflichtkann null sein

      Your 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

    • namestringPflicht

      Name as the guest sees it, e.g. Size, Extras.

      max 120 chars

    • min_selectintegerPflicht

      How many options the guest must pick at least (0 = optional).

      0 to 20

    • max_selectintegerPflicht

      How many options the guest may pick at most.

      1 to 20

    • positionintegerPflicht

      Sort order among groups.

    • product_idstring (uuid)Pflichtkann null sein

      The product this group belongs to only; null for a shared group.

    • optionsOption[]Pflicht

      Its options in order, including sold-out ones.

      Felder darin (9)
      • objectstringPflicht

        Always option.

        Werte:"option"

      • idstring (uuid)Pflicht

        The option id.

      • external_idstringPflichtkann null sein

        Your 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)Pflicht

        The option group it belongs to.

      • namestringPflicht

        Name as the guest sees it.

        max 120 chars

      • price_deltaintegerPflicht

        Surcharge, may be negative. Integer amount in the smallest currency unit (cents).

      • availablebooleanPflicht

        false = sold out on every product that uses the group.

      • positionintegerPflicht

        Sort order within the group.

      • updated_atstring (date-time)Pflicht

        Last change of one of its fields. ISO 8601 in UTC (Z).

    • updated_atstring (date-time)Pflicht

      Last 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. param names the field.
  • 401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.
  • 403insufficient_scope, plan_required (with param when an order type is not in the plan) or account_inactive.
  • 429rate_limited: more than 120 requests per minute on this connection. Wait Retry-After seconds.
  • 500server_error. Safe to retry with the same Idempotency-Key.

Alle Fehlercodes

Anfrage
curl "https://api.flavoso.com/v1/menu" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
Antwort
{
  "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

  • modestringPflicht

    merge creates and updates; replace also deletes your objects that are missing.

    Werte:"merge""replace"

  • dry_runboolean

    true: validate and report what would happen, change nothing. Images are not downloaded.

    default false

  • categoriesMenuSyncCategory[]

    Your categories.

    max 200 items

    Felder darin (4)
    • external_idstringPflicht

      Your id; the key of the sync.

      1 to 100 chars

    • namestringPflicht

      Name as the guest sees it.

      1 to 80 chars

    • positioninteger

      Sort order. Missing: unchanged (new categories go last).

      0 to 100000

    • tax_classstringkann null sein

      Tax group: food, drink or alcohol. Flavoso stores the group, not a percentage: the restaurant sets the rate per group for its country, and tax_rates shows the result. null means: use the category's group, else food. Missing: unchanged.

      Werte:"food""drink""alcohol"

  • productsMenuSyncProduct[]

    Your products.

    max 2000 items

    Felder darin (12)
    • external_idstringPflicht

      Your id (article number, PLU); the key of the sync.

      1 to 100 chars

    • category_external_idstringkann null sein

      Your id of its category, from categories or synced before; null: no category.

      1 to 100 chars

    • namestringPflicht

      Name as the guest sees it.

      1 to 200 chars

    • descriptionstringkann null sein

      Description; null or empty removes it.

      max 2000 chars

    • priceintegerPflicht

      Price including VAT in cents.

      0 to 1000000

    • availableboolean

      false = sold out. Missing: unchanged (new: true).

    • positioninteger

      Sort 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-free is mapped to gluten_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 sein

      Tax group: food, drink or alcohol. Flavoso stores the group, not a percentage: the restaurant sets the rate per group for its country, and tax_rates shows the result. null means: use the category's group, else food.

      Werte:"food""drink""alcohol"

    • image_urlstring (uri)kann null sein

      An 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_idstringPflicht

      Your id; the key of the sync.

      1 to 100 chars

    • namestringPflicht

      Name as the guest sees it.

      1 to 120 chars

    • min_selectinteger

      Missing: unchanged (new: 0).

      0 to 20

    • max_selectinteger

      Missing: unchanged (new: 1).

      1 to 20

    • positioninteger

      Missing: unchanged (new groups go last).

      0 to 100000

    • optionsMenuSyncOption[]Pflicht

      The group's options carrying your ids. Options the restaurant added without your id stay.

      Felder darin (5)
      • external_idstringPflicht

        Your id; unique across all options of the sync. An existing option listed under another group moves there.

        1 to 100 chars

      • namestringPflicht

        Name as the guest sees it.

        1 to 120 chars

      • price_deltainteger

        Surcharge in cents. Missing: unchanged (new: 0).

        -100000 to 100000

      • availableboolean

        Missing: unchanged (new: true), so a sync never undoes a sold-out mark you do not track.

      • positioninteger

        Sort order. Missing: unchanged (new options go last).

        0 to 100000

Antwort200

OK

  • objectstringPflicht

    Always menu_sync.

    Werte:"menu_sync"

  • modestringPflicht

    The mode you sent.

    Werte:"merge""replace"

  • dry_runbooleanPflicht

    true: nothing was changed.

  • summaryMenuSyncSummaryPflicht

    What was created, updated, left unchanged and deleted.

    Felder darin (4)
    • categoriesMenuSyncCountsPflicht

      Categories.

      Felder darin (4)
      • createdintegerPflicht

        Created by this sync.

      • updatedintegerPflicht

        Changed by this sync.

      • unchangedintegerPflicht

        Listed, but already as in the body.

      • deletedintegerPflicht

        Deleted by this sync (replace only).

    • productsMenuSyncCountsPflicht

      Products.

      Felder darin (4)
      • createdintegerPflicht

        Created by this sync.

      • updatedintegerPflicht

        Changed by this sync.

      • unchangedintegerPflicht

        Listed, but already as in the body.

      • deletedintegerPflicht

        Deleted by this sync (replace only).

    • option_groupsMenuSyncCountsPflicht

      Option groups.

      Felder darin (4)
      • createdintegerPflicht

        Created by this sync.

      • updatedintegerPflicht

        Changed by this sync.

      • unchangedintegerPflicht

        Listed, but already as in the body.

      • deletedintegerPflicht

        Deleted by this sync (replace only).

    • optionsMenuSyncCountsPflicht

      Options.

      Felder darin (4)
      • createdintegerPflicht

        Created by this sync.

      • updatedintegerPflicht

        Changed by this sync.

      • unchangedintegerPflicht

        Listed, but already as in the body.

      • deletedintegerPflicht

        Deleted by this sync (replace only).

  • warningsMenuSyncWarning[]Pflicht

    Empty when all went as asked. Comes before ids, so it survives a truncated log.

    Felder darin (3)
    • codestringPflicht

      image_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 from image_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 a replace kept. unmanaged_options_deleted: options without your id that went with a deleted group (in replace mode 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"

    • messagestringPflicht

      Explanation for developers; may change.

    • paramstring

      The field it concerns, e.g. products[3].image_url, when there is one.

  • idsobjectPflicht

    Flavoso's id for each of your ids, to store on your side (webhooks and orders carry Flavoso's ids).

    Felder darin (4)
    • categoriesMenuSyncIdPair[]Pflicht

      In the order of the body.

      Felder darin (2)
      • external_idstringPflicht

        Your id.

      • idstring (uuid)Pflichtkann null sein

        Flavoso's id; null in a dry run for objects that would be created.

    • productsMenuSyncIdPair[]Pflicht

      In the order of the body.

      Felder darin (2)
      • external_idstringPflicht

        Your id.

      • idstring (uuid)Pflichtkann null sein

        Flavoso's id; null in a dry run for objects that would be created.

    • option_groupsMenuSyncIdPair[]Pflicht

      In the order of the body.

      Felder darin (2)
      • external_idstringPflicht

        Your id.

      • idstring (uuid)Pflichtkann null sein

        Flavoso's id; null in a dry run for objects that would be created.

    • optionsMenuSyncIdPair[]Pflicht

      In the order of the body.

      Felder darin (2)
      • external_idstringPflicht

        Your id.

      • idstring (uuid)Pflichtkann null sein

        Flavoso'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. param names the field.
  • 401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.
  • 403insufficient_scope, plan_required (with param when an order type is not in the plan) or account_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 this external_id for 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) or conflict (the same data was changed elsewhere at the same moment, three times in a row; retry unchanged after Retry-After seconds).
  • 413payload_too_large: the body is larger than the operation takes, counted in bytes: 64 KB, 2 MB for PUT /menu. Split the request.
  • 422idempotency_key_reused (the key was used with a different request) or limit_exceeded (too many entries in one request, such as products in PUT /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. Wait Retry-After seconds.
  • 500server_error. Safe to retry with the same Idempotency-Key.

Alle Fehlercodes

Anfrage
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"
      ]
    }
  ]
}'
Beispiel: Rumpf
{
  "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"
      ]
    }
  ]
}
Antwort
{
  "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

NameOrtBeschreibung
Idempotency-KeystringheaderAny 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[]Pflicht

    Each object at most once.

    max 500 items · min 1 items

    Felder darin (4)
    • typestringPflicht

      What it is.

      Werte:"product""option"

    • idstring (uuid)

      Flavoso's id.

    • external_idstring

      Your id.

      1 to 100 chars

    • availablebooleanPflicht

      false = sold out, true = available again.

Antwort200

OK

  • objectstringPflicht

    Always menu_availability.

    Werte:"menu_availability"

  • updatedintegerPflicht

    Items whose state changed.

  • unchangedintegerPflicht

    Items that already had this state.

  • itemsobject[]Pflicht

    In the order of the request.

    Felder darin (5)
    • typestringPflicht

      What it is.

      Werte:"product""option"

    • idstring (uuid)Pflicht

      Flavoso's id.

    • external_idstringPflichtkann null sein

      Your id, if the object has one.

    • availablebooleanPflicht

      The state now.

    • changedbooleanPflicht

      Whether 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. param names the field.
  • 401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.
  • 403insufficient_scope, plan_required (with param when an order type is not in the plan) or account_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 this external_id for 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) or conflict (the same data was changed elsewhere at the same moment, three times in a row; retry unchanged after Retry-After seconds).
  • 413payload_too_large: the body is larger than the operation takes, counted in bytes: 64 KB, 2 MB for PUT /menu. Split the request.
  • 422idempotency_key_reused (the key was used with a different request) or limit_exceeded (too many entries in one request, such as products in PUT /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. Wait Retry-After seconds.
  • 500server_error. Safe to retry with the same Idempotency-Key.

Alle Fehlercodes

Anfrage
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
    }
  ]
}'
Beispiel: Rumpf
{
  "items": [
    {
      "type": "product",
      "external_id": "PLU-1001",
      "available": false
    },
    {
      "type": "option",
      "id": "2b3c4d5e-6f70-4182-93a4-b5c6d7e8f901",
      "available": true
    }
  ]
}
Antwort
{
  "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

NameOrtBeschreibung
limitintegerqueryPage size, 1 to 100. Default 25.1 to 100 · default 25
cursorstringquerynext_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)queryOnly 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_idstringqueryOnly the object that carries this external_id of your app (zero or one result).max 100 chars

Antwort200

OK

  • objectstringPflicht

    Always list.

    Werte:"list"

  • dataCategory[]Pflicht

    This page, oldest change first.

    Felder darin (8)
    • objectstringPflicht

      Always category.

      Werte:"category"

    • idstring (uuid)Pflicht

      The category id.

    • external_idstringPflichtkann null sein

      Your 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

    • namestringPflicht

      Name as the guest sees it.

      max 80 chars

    • positionintegerPflicht

      Sort order on the menu, ascending.

    • tax_classstringPflichtkann null sein

      Tax group for the products in it that have none of their own. null: food.

      Werte:"food""drink""alcohol"

    • created_atstring (date-time)Pflicht

      When the category was created. ISO 8601 in UTC (Z).

    • updated_atstring (date-time)Pflicht

      Last change of one of its fields. ISO 8601 in UTC (Z).

  • has_morebooleanPflicht

    Whether another page follows.

  • next_cursorstringPflichtkann null sein

    Pass as cursor for 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. param names the field.
  • 401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.
  • 403insufficient_scope, plan_required (with param when an order type is not in the plan) or account_inactive.
  • 429rate_limited: more than 120 requests per minute on this connection. Wait Retry-After seconds.
  • 500server_error. Safe to retry with the same Idempotency-Key.

Alle Fehlercodes

Anfrage
curl "https://api.flavoso.com/v1/menu/categories" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
Antwort
{
  "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

NameOrtBeschreibung
Idempotency-KeystringheaderAny 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

  • namestringPflicht

    Name as the guest sees it.

    1 to 80 chars

  • positioninteger

    Sort order. Default: after the last category.

    0 to 100000

  • tax_classstringkann null sein

    Tax group: food, drink or alcohol. Flavoso stores the group, not a percentage: the restaurant sets the rate per group for its country, and tax_rates shows the result. null means: use the category's group, else food.

    Werte:"food""drink""alcohol"

  • external_idstring

    Your own id for this category; 409 external_id_taken if another category of this restaurant already has it.

    1 to 100 chars

Antwort201

OK

  • objectstringPflicht

    Always category.

    Werte:"category"

  • idstring (uuid)Pflicht

    The category id.

  • external_idstringPflichtkann null sein

    Your 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

  • namestringPflicht

    Name as the guest sees it.

    max 80 chars

  • positionintegerPflicht

    Sort order on the menu, ascending.

  • tax_classstringPflichtkann null sein

    Tax group for the products in it that have none of their own. null: food.

    Werte:"food""drink""alcohol"

  • created_atstring (date-time)Pflicht

    When the category was created. ISO 8601 in UTC (Z).

  • updated_atstring (date-time)Pflicht

    Last 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. param names the field.
  • 401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.
  • 403insufficient_scope, plan_required (with param when an order type is not in the plan) or account_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 this external_id for 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) or conflict (the same data was changed elsewhere at the same moment, three times in a row; retry unchanged after Retry-After seconds).
  • 413payload_too_large: the body is larger than the operation takes, counted in bytes: 64 KB, 2 MB for PUT /menu. Split the request.
  • 422idempotency_key_reused (the key was used with a different request) or limit_exceeded (too many entries in one request, such as products in PUT /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. Wait Retry-After seconds.
  • 500server_error. Safe to retry with the same Idempotency-Key.

Alle Fehlercodes

Anfrage
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"
}'
Beispiel: Rumpf
{
  "name": "Pizza",
  "external_id": "CAT-PIZZA",
  "tax_class": "food"
}
Antwort
{
  "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

NameOrtBeschreibung
idstring (uuid)PflichtpathThe category id (UUID).

Antwort200

OK

  • objectstringPflicht

    Always category.

    Werte:"category"

  • idstring (uuid)Pflicht

    The category id.

  • external_idstringPflichtkann null sein

    Your 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

  • namestringPflicht

    Name as the guest sees it.

    max 80 chars

  • positionintegerPflicht

    Sort order on the menu, ascending.

  • tax_classstringPflichtkann null sein

    Tax group for the products in it that have none of their own. null: food.

    Werte:"food""drink""alcohol"

  • created_atstring (date-time)Pflicht

    When the category was created. ISO 8601 in UTC (Z).

  • updated_atstring (date-time)Pflicht

    Last 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. param names the field.
  • 401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.
  • 403insufficient_scope, plan_required (with param when an order type is not in the plan) or account_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. Wait Retry-After seconds.
  • 500server_error. Safe to retry with the same Idempotency-Key.

Alle Fehlercodes

Anfrage
curl "https://api.flavoso.com/v1/menu/categories/CATEGORY_ID" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
Antwort
{
  "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

NameOrtBeschreibung
idstring (uuid)PflichtpathThe category id (UUID).

Rumpfapplication/json

  • namestring

    New name.

    1 to 80 chars

  • positioninteger

    New sort order.

    0 to 100000

  • tax_classstringkann null sein

    Tax group: food, drink or alcohol. Flavoso stores the group, not a percentage: the restaurant sets the rate per group for its country, and tax_rates shows the result. null means: use the category's group, else food.

    Werte:"food""drink""alcohol"

  • external_idstringkann null sein

    Set or change your id; null removes it.

    1 to 100 chars

Antwort200

OK

  • objectstringPflicht

    Always category.

    Werte:"category"

  • idstring (uuid)Pflicht

    The category id.

  • external_idstringPflichtkann null sein

    Your 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

  • namestringPflicht

    Name as the guest sees it.

    max 80 chars

  • positionintegerPflicht

    Sort order on the menu, ascending.

  • tax_classstringPflichtkann null sein

    Tax group for the products in it that have none of their own. null: food.

    Werte:"food""drink""alcohol"

  • created_atstring (date-time)Pflicht

    When the category was created. ISO 8601 in UTC (Z).

  • updated_atstring (date-time)Pflicht

    Last 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. param names the field.
  • 401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.
  • 403insufficient_scope, plan_required (with param when an order type is not in the plan) or account_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 this external_id for 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) or conflict (the same data was changed elsewhere at the same moment, three times in a row; retry unchanged after Retry-After seconds).
  • 413payload_too_large: the body is larger than the operation takes, counted in bytes: 64 KB, 2 MB for PUT /menu. Split the request.
  • 422idempotency_key_reused (the key was used with a different request) or limit_exceeded (too many entries in one request, such as products in PUT /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. Wait Retry-After seconds.
  • 500server_error. Safe to retry with the same Idempotency-Key.

Alle Fehlercodes

Anfrage
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"
}'
Beispiel: Rumpf
{
  "name": "Pizza & Pinsa"
}
Antwort
{
  "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

NameOrtBeschreibung
idstring (uuid)PflichtpathThe category id (UUID).
with_productsbooleanquerytrue: delete the category's products too. Default false.default false

Antwort200

OK

  • objectstringPflicht

    Always category.

    Werte:"category"

  • idstring (uuid)Pflicht

    The deleted category.

  • deletedbooleanPflicht

    Always true.

    Werte:true

  • products_deletedintegerPflicht

    Products deleted with it (with_products=true).

  • products_uncategorizedintegerPflicht

    Products 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. param names the field.
  • 401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.
  • 403insufficient_scope, plan_required (with param when an order type is not in the plan) or account_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 this external_id for 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) or conflict (the same data was changed elsewhere at the same moment, three times in a row; retry unchanged after Retry-After seconds).
  • 429rate_limited: more than 120 requests per minute on this connection. Wait Retry-After seconds.
  • 500server_error. Safe to retry with the same Idempotency-Key.

Alle Fehlercodes

Anfrage
curl -X DELETE "https://api.flavoso.com/v1/menu/categories/CATEGORY_ID" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
Antwort
{
  "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

NameOrtBeschreibung
limitintegerqueryPage size, 1 to 100. Default 25.1 to 100 · default 25
cursorstringquerynext_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)queryOnly 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_idstringqueryOnly the object that carries this external_id of your app (zero or one result).max 100 chars
category_idstringqueryOnly products in this category; none for products without a category.
availablebooleanqueryfalse: only sold-out products; true: only available ones.

Antwort200

OK

  • objectstringPflicht

    Always list.

    Werte:"list"

  • dataProduct[]Pflicht

    This page, oldest change first.

    Felder darin (17)
    • objectstringPflicht

      Always product.

      Werte:"product"

    • idstring (uuid)Pflicht

      The product id.

    • external_idstringPflichtkann null sein

      Your 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 sein

      Its category; null: no category.

    • namestringPflicht

      Name as the guest sees it.

      max 200 chars

    • descriptionstringPflichtkann null sein

      Description as the guest sees it.

      max 2000 chars

    • priceintegerPflicht

      Price including VAT, without options. Integer amount in the smallest currency unit (cents).

    • availablebooleanPflicht

      false = sold out: guests cannot order it until it is available again.

    • positionintegerPflicht

      Sort order within its category, ascending.

    • allergensstring[]Pflicht

      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[]Pflicht

      Diet marks: vegetarian, vegan, gluten_free, lactose_free, halal, kosher (Gluten-free is mapped to gluten_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 sein

      The product photo as Flavoso serves it; null: none.

    • tax_classstringPflichtkann null sein

      The product's own tax group; null: its category's, else food.

      Werte:"food""drink""alcohol"

    • tax_ratesTaxRatesPflicht

      The resolved VAT rates in basis points.

      Felder darin (2)
      • dine_in_bpsintegerPflichtkann null sein

        Rate when eaten in (table orders).

      • takeaway_bpsintegerPflichtkann null sein

        Rate for pickup and delivery.

    • option_group_idsstring (uuid)[]Pflicht

      Its 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)Pflicht

      When the product was created. ISO 8601 in UTC (Z).

    • updated_atstring (date-time)Pflicht

      Last change of one of its fields, including its option groups. ISO 8601 in UTC (Z).

  • has_morebooleanPflicht

    Whether another page follows.

  • next_cursorstringPflichtkann null sein

    Pass as cursor for 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. param names the field.
  • 401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.
  • 403insufficient_scope, plan_required (with param when an order type is not in the plan) or account_inactive.
  • 429rate_limited: more than 120 requests per minute on this connection. Wait Retry-After seconds.
  • 500server_error. Safe to retry with the same Idempotency-Key.

Alle Fehlercodes

Anfrage
curl "https://api.flavoso.com/v1/menu/products" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
Antwort
{
  "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

NameOrtBeschreibung
Idempotency-KeystringheaderAny 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

  • namestringPflicht

    Name as the guest sees it.

    1 to 200 chars

  • priceintegerPflicht

    Price including VAT in the smallest currency unit (cents).

    0 to 1000000

  • category_idstring (uuid)kann null sein

    Its category; null or missing: no category.

  • descriptionstringkann null sein

    Description; empty means none.

    max 2000 chars

  • availableboolean

    false = sold out. Default true.

  • positioninteger

    Sort 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-free is mapped to gluten_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 sein

    Tax group: food, drink or alcohol. Flavoso stores the group, not a percentage: the restaurant sets the rate per group for its country, and tax_rates shows the result. null means: use the category's group, else food.

    Werte:"food""drink""alcohol"

  • image_urlstring (uri)kann null sein

    An 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_idstring

    Your own id for this product; 409 external_id_taken if another product of this restaurant already has it.

    1 to 100 chars

Antwort201

OK

  • objectstringPflicht

    Always product.

    Werte:"product"

  • idstring (uuid)Pflicht

    The product id.

  • external_idstringPflichtkann null sein

    Your 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 sein

    Its category; null: no category.

  • namestringPflicht

    Name as the guest sees it.

    max 200 chars

  • descriptionstringPflichtkann null sein

    Description as the guest sees it.

    max 2000 chars

  • priceintegerPflicht

    Price including VAT, without options. Integer amount in the smallest currency unit (cents).

  • availablebooleanPflicht

    false = sold out: guests cannot order it until it is available again.

  • positionintegerPflicht

    Sort order within its category, ascending.

  • allergensstring[]Pflicht

    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[]Pflicht

    Diet marks: vegetarian, vegan, gluten_free, lactose_free, halal, kosher (Gluten-free is mapped to gluten_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 sein

    The product photo as Flavoso serves it; null: none.

  • tax_classstringPflichtkann null sein

    The product's own tax group; null: its category's, else food.

    Werte:"food""drink""alcohol"

  • tax_ratesTaxRatesPflicht

    The resolved VAT rates in basis points.

    Felder darin (2)
    • dine_in_bpsintegerPflichtkann null sein

      Rate when eaten in (table orders).

    • takeaway_bpsintegerPflichtkann null sein

      Rate for pickup and delivery.

  • option_group_idsstring (uuid)[]Pflicht

    Its 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)Pflicht

    When the product was created. ISO 8601 in UTC (Z).

  • updated_atstring (date-time)Pflicht

    Last 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. param names the field.
  • 401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.
  • 403insufficient_scope, plan_required (with param when an order type is not in the plan) or account_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 this external_id for 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) or conflict (the same data was changed elsewhere at the same moment, three times in a row; retry unchanged after Retry-After seconds).
  • 413payload_too_large: the body is larger than the operation takes, counted in bytes: 64 KB, 2 MB for PUT /menu. Split the request.
  • 422idempotency_key_reused (the key was used with a different request) or limit_exceeded (too many entries in one request, such as products in PUT /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. Wait Retry-After seconds.
  • 500server_error. Safe to retry with the same Idempotency-Key.

Alle Fehlercodes

Anfrage
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"
}'
Beispiel: Rumpf
{
  "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"
}
Antwort
{
  "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

NameOrtBeschreibung
idstring (uuid)PflichtpathThe product id (UUID).

Antwort200

OK

  • objectstringPflicht

    Always product.

    Werte:"product"

  • idstring (uuid)Pflicht

    The product id.

  • external_idstringPflichtkann null sein

    Your 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 sein

    Its category; null: no category.

  • namestringPflicht

    Name as the guest sees it.

    max 200 chars

  • descriptionstringPflichtkann null sein

    Description as the guest sees it.

    max 2000 chars

  • priceintegerPflicht

    Price including VAT, without options. Integer amount in the smallest currency unit (cents).

  • availablebooleanPflicht

    false = sold out: guests cannot order it until it is available again.

  • positionintegerPflicht

    Sort order within its category, ascending.

  • allergensstring[]Pflicht

    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[]Pflicht

    Diet marks: vegetarian, vegan, gluten_free, lactose_free, halal, kosher (Gluten-free is mapped to gluten_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 sein

    The product photo as Flavoso serves it; null: none.

  • tax_classstringPflichtkann null sein

    The product's own tax group; null: its category's, else food.

    Werte:"food""drink""alcohol"

  • tax_ratesTaxRatesPflicht

    The resolved VAT rates in basis points.

    Felder darin (2)
    • dine_in_bpsintegerPflichtkann null sein

      Rate when eaten in (table orders).

    • takeaway_bpsintegerPflichtkann null sein

      Rate for pickup and delivery.

  • option_group_idsstring (uuid)[]Pflicht

    Its 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)Pflicht

    When the product was created. ISO 8601 in UTC (Z).

  • updated_atstring (date-time)Pflicht

    Last 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. param names the field.
  • 401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.
  • 403insufficient_scope, plan_required (with param when an order type is not in the plan) or account_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. Wait Retry-After seconds.
  • 500server_error. Safe to retry with the same Idempotency-Key.

Alle Fehlercodes

Anfrage
curl "https://api.flavoso.com/v1/menu/products/PRODUCT_ID" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
Antwort
{
  "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

NameOrtBeschreibung
idstring (uuid)PflichtpathThe product id (UUID).

Rumpfapplication/json

  • namestring

    New name.

    1 to 200 chars

  • priceinteger

    New price in cents.

    0 to 1000000

  • category_idstring (uuid)kann null sein

    Move to this category; null: no category.

  • descriptionstringkann null sein

    New description; null or empty removes it.

    max 2000 chars

  • availableboolean

    false = sold out, true = available again.

  • positioninteger

    New 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-free is mapped to gluten_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 sein

    Tax group: food, drink or alcohol. Flavoso stores the group, not a percentage: the restaurant sets the rate per group for its country, and tax_rates shows the result. null means: use the category's group, else food.

    Werte:"food""drink""alcohol"

  • image_urlstring (uri)kann null sein

    An 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_id set, e.g. from the menu import) cannot be detached, only deleted, and such groups always come first.

    max 50 items

  • external_idstringkann null sein

    Set 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

  • objectstringPflicht

    Always product.

    Werte:"product"

  • idstring (uuid)Pflicht

    The product id.

  • external_idstringPflichtkann null sein

    Your 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 sein

    Its category; null: no category.

  • namestringPflicht

    Name as the guest sees it.

    max 200 chars

  • descriptionstringPflichtkann null sein

    Description as the guest sees it.

    max 2000 chars

  • priceintegerPflicht

    Price including VAT, without options. Integer amount in the smallest currency unit (cents).

  • availablebooleanPflicht

    false = sold out: guests cannot order it until it is available again.

  • positionintegerPflicht

    Sort order within its category, ascending.

  • allergensstring[]Pflicht

    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[]Pflicht

    Diet marks: vegetarian, vegan, gluten_free, lactose_free, halal, kosher (Gluten-free is mapped to gluten_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 sein

    The product photo as Flavoso serves it; null: none.

  • tax_classstringPflichtkann null sein

    The product's own tax group; null: its category's, else food.

    Werte:"food""drink""alcohol"

  • tax_ratesTaxRatesPflicht

    The resolved VAT rates in basis points.

    Felder darin (2)
    • dine_in_bpsintegerPflichtkann null sein

      Rate when eaten in (table orders).

    • takeaway_bpsintegerPflichtkann null sein

      Rate for pickup and delivery.

  • option_group_idsstring (uuid)[]Pflicht

    Its 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)Pflicht

    When the product was created. ISO 8601 in UTC (Z).

  • updated_atstring (date-time)Pflicht

    Last 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. param names the field.
  • 401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.
  • 403insufficient_scope, plan_required (with param when an order type is not in the plan) or account_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 this external_id for 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) or conflict (the same data was changed elsewhere at the same moment, three times in a row; retry unchanged after Retry-After seconds).
  • 413payload_too_large: the body is larger than the operation takes, counted in bytes: 64 KB, 2 MB for PUT /menu. Split the request.
  • 422idempotency_key_reused (the key was used with a different request) or limit_exceeded (too many entries in one request, such as products in PUT /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. Wait Retry-After seconds.
  • 500server_error. Safe to retry with the same Idempotency-Key.

Alle Fehlercodes

Anfrage
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
}'
Beispiel: Rumpf
{
  "available": false
}
Antwort
{
  "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

NameOrtBeschreibung
idstring (uuid)PflichtpathThe product id (UUID).

Antwort200

OK

  • objectstringPflicht

    The type of the deleted object.

    Werte:"product""option_group""option"

  • idstring (uuid)Pflicht

    The deleted object.

  • deletedbooleanPflicht

    Always 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. param names the field.
  • 401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.
  • 403insufficient_scope, plan_required (with param when an order type is not in the plan) or account_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 this external_id for 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) or conflict (the same data was changed elsewhere at the same moment, three times in a row; retry unchanged after Retry-After seconds).
  • 429rate_limited: more than 120 requests per minute on this connection. Wait Retry-After seconds.
  • 500server_error. Safe to retry with the same Idempotency-Key.

Alle Fehlercodes

Anfrage
curl -X DELETE "https://api.flavoso.com/v1/menu/products/PRODUCT_ID" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
Antwort
{
  "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

NameOrtBeschreibung
limitintegerqueryPage size, 1 to 100. Default 25.1 to 100 · default 25
cursorstringquerynext_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)queryOnly 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_idstringqueryOnly the object that carries this external_id of your app (zero or one result).max 100 chars

Antwort200

OK

  • objectstringPflicht

    Always list.

    Werte:"list"

  • dataOptionGroup[]Pflicht

    This page, oldest change first.

    Felder darin (10)
    • objectstringPflicht

      Always option_group.

      Werte:"option_group"

    • idstring (uuid)Pflicht

      The option group id.

    • external_idstringPflichtkann null sein

      Your 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

    • namestringPflicht

      Name as the guest sees it, e.g. Size, Extras.

      max 120 chars

    • min_selectintegerPflicht

      How many options the guest must pick at least (0 = optional).

      0 to 20

    • max_selectintegerPflicht

      How many options the guest may pick at most.

      1 to 20

    • positionintegerPflicht

      Sort order among groups.

    • product_idstring (uuid)Pflichtkann null sein

      The product this group belongs to only; null for a shared group.

    • optionsOption[]Pflicht

      Its options in order, including sold-out ones.

      Felder darin (9)
      • objectstringPflicht

        Always option.

        Werte:"option"

      • idstring (uuid)Pflicht

        The option id.

      • external_idstringPflichtkann null sein

        Your 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)Pflicht

        The option group it belongs to.

      • namestringPflicht

        Name as the guest sees it.

        max 120 chars

      • price_deltaintegerPflicht

        Surcharge, may be negative. Integer amount in the smallest currency unit (cents).

      • availablebooleanPflicht

        false = sold out on every product that uses the group.

      • positionintegerPflicht

        Sort order within the group.

      • updated_atstring (date-time)Pflicht

        Last change of one of its fields. ISO 8601 in UTC (Z).

    • updated_atstring (date-time)Pflicht

      Last change of one of its own fields (not of its options). ISO 8601 in UTC (Z).

  • has_morebooleanPflicht

    Whether another page follows.

  • next_cursorstringPflichtkann null sein

    Pass as cursor for 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. param names the field.
  • 401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.
  • 403insufficient_scope, plan_required (with param when an order type is not in the plan) or account_inactive.
  • 429rate_limited: more than 120 requests per minute on this connection. Wait Retry-After seconds.
  • 500server_error. Safe to retry with the same Idempotency-Key.

Alle Fehlercodes

Anfrage
curl "https://api.flavoso.com/v1/menu/option-groups" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
Antwort
{
  "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

NameOrtBeschreibung
Idempotency-KeystringheaderAny 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

  • namestringPflicht

    Name as the guest sees it.

    1 to 120 chars

  • min_selectinteger

    Default 0; must not exceed max_select.

    0 to 20

  • max_selectinteger

    Default 1 (or min_select if higher).

    1 to 20

  • positioninteger

    Sort order. Default: after the last group.

    0 to 100000

  • external_idstring

    Your 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)
    • namestringPflicht

      Name as the guest sees it.

      1 to 120 chars

    • price_deltainteger

      Surcharge in cents, may be negative. Default 0.

      -100000 to 100000

    • availableboolean

      false = sold out. Default true.

    • positioninteger

      Sort order. Default: after the last option of the group.

      0 to 100000

    • external_idstring

      Your own id for this option.

      1 to 100 chars

Antwort201

OK

  • objectstringPflicht

    Always option_group.

    Werte:"option_group"

  • idstring (uuid)Pflicht

    The option group id.

  • external_idstringPflichtkann null sein

    Your 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

  • namestringPflicht

    Name as the guest sees it, e.g. Size, Extras.

    max 120 chars

  • min_selectintegerPflicht

    How many options the guest must pick at least (0 = optional).

    0 to 20

  • max_selectintegerPflicht

    How many options the guest may pick at most.

    1 to 20

  • positionintegerPflicht

    Sort order among groups.

  • product_idstring (uuid)Pflichtkann null sein

    The product this group belongs to only; null for a shared group.

  • optionsOption[]Pflicht

    Its options in order, including sold-out ones.

    Felder darin (9)
    • objectstringPflicht

      Always option.

      Werte:"option"

    • idstring (uuid)Pflicht

      The option id.

    • external_idstringPflichtkann null sein

      Your 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)Pflicht

      The option group it belongs to.

    • namestringPflicht

      Name as the guest sees it.

      max 120 chars

    • price_deltaintegerPflicht

      Surcharge, may be negative. Integer amount in the smallest currency unit (cents).

    • availablebooleanPflicht

      false = sold out on every product that uses the group.

    • positionintegerPflicht

      Sort order within the group.

    • updated_atstring (date-time)Pflicht

      Last change of one of its fields. ISO 8601 in UTC (Z).

  • updated_atstring (date-time)Pflicht

    Last 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. param names the field.
  • 401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.
  • 403insufficient_scope, plan_required (with param when an order type is not in the plan) or account_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 this external_id for 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) or conflict (the same data was changed elsewhere at the same moment, three times in a row; retry unchanged after Retry-After seconds).
  • 413payload_too_large: the body is larger than the operation takes, counted in bytes: 64 KB, 2 MB for PUT /menu. Split the request.
  • 422idempotency_key_reused (the key was used with a different request) or limit_exceeded (too many entries in one request, such as products in PUT /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. Wait Retry-After seconds.
  • 500server_error. Safe to retry with the same Idempotency-Key.

Alle Fehlercodes

Anfrage
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"
    }
  ]
}'
Beispiel: Rumpf
{
  "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"
    }
  ]
}
Antwort
{
  "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

NameOrtBeschreibung
idstring (uuid)PflichtpathThe option group id (UUID).

Antwort200

OK

  • objectstringPflicht

    Always option_group.

    Werte:"option_group"

  • idstring (uuid)Pflicht

    The option group id.

  • external_idstringPflichtkann null sein

    Your 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

  • namestringPflicht

    Name as the guest sees it, e.g. Size, Extras.

    max 120 chars

  • min_selectintegerPflicht

    How many options the guest must pick at least (0 = optional).

    0 to 20

  • max_selectintegerPflicht

    How many options the guest may pick at most.

    1 to 20

  • positionintegerPflicht

    Sort order among groups.

  • product_idstring (uuid)Pflichtkann null sein

    The product this group belongs to only; null for a shared group.

  • optionsOption[]Pflicht

    Its options in order, including sold-out ones.

    Felder darin (9)
    • objectstringPflicht

      Always option.

      Werte:"option"

    • idstring (uuid)Pflicht

      The option id.

    • external_idstringPflichtkann null sein

      Your 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)Pflicht

      The option group it belongs to.

    • namestringPflicht

      Name as the guest sees it.

      max 120 chars

    • price_deltaintegerPflicht

      Surcharge, may be negative. Integer amount in the smallest currency unit (cents).

    • availablebooleanPflicht

      false = sold out on every product that uses the group.

    • positionintegerPflicht

      Sort order within the group.

    • updated_atstring (date-time)Pflicht

      Last change of one of its fields. ISO 8601 in UTC (Z).

  • updated_atstring (date-time)Pflicht

    Last 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. param names the field.
  • 401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.
  • 403insufficient_scope, plan_required (with param when an order type is not in the plan) or account_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. Wait Retry-After seconds.
  • 500server_error. Safe to retry with the same Idempotency-Key.

Alle Fehlercodes

Anfrage
curl "https://api.flavoso.com/v1/menu/option-groups/OPTION_GROUP_ID" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
Antwort
{
  "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

NameOrtBeschreibung
idstring (uuid)PflichtpathThe option group id (UUID).

Rumpfapplication/json

  • namestring

    New name.

    1 to 120 chars

  • min_selectinteger

    New minimum; must not exceed the (new) maximum.

    0 to 20

  • max_selectinteger

    New maximum.

    1 to 20

  • positioninteger

    New sort order.

    0 to 100000

  • external_idstringkann null sein

    Set or change your id; null removes it.

    1 to 100 chars

Antwort200

OK

  • objectstringPflicht

    Always option_group.

    Werte:"option_group"

  • idstring (uuid)Pflicht

    The option group id.

  • external_idstringPflichtkann null sein

    Your 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

  • namestringPflicht

    Name as the guest sees it, e.g. Size, Extras.

    max 120 chars

  • min_selectintegerPflicht

    How many options the guest must pick at least (0 = optional).

    0 to 20

  • max_selectintegerPflicht

    How many options the guest may pick at most.

    1 to 20

  • positionintegerPflicht

    Sort order among groups.

  • product_idstring (uuid)Pflichtkann null sein

    The product this group belongs to only; null for a shared group.

  • optionsOption[]Pflicht

    Its options in order, including sold-out ones.

    Felder darin (9)
    • objectstringPflicht

      Always option.

      Werte:"option"

    • idstring (uuid)Pflicht

      The option id.

    • external_idstringPflichtkann null sein

      Your 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)Pflicht

      The option group it belongs to.

    • namestringPflicht

      Name as the guest sees it.

      max 120 chars

    • price_deltaintegerPflicht

      Surcharge, may be negative. Integer amount in the smallest currency unit (cents).

    • availablebooleanPflicht

      false = sold out on every product that uses the group.

    • positionintegerPflicht

      Sort order within the group.

    • updated_atstring (date-time)Pflicht

      Last change of one of its fields. ISO 8601 in UTC (Z).

  • updated_atstring (date-time)Pflicht

    Last 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. param names the field.
  • 401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.
  • 403insufficient_scope, plan_required (with param when an order type is not in the plan) or account_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 this external_id for 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) or conflict (the same data was changed elsewhere at the same moment, three times in a row; retry unchanged after Retry-After seconds).
  • 413payload_too_large: the body is larger than the operation takes, counted in bytes: 64 KB, 2 MB for PUT /menu. Split the request.
  • 422idempotency_key_reused (the key was used with a different request) or limit_exceeded (too many entries in one request, such as products in PUT /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. Wait Retry-After seconds.
  • 500server_error. Safe to retry with the same Idempotency-Key.

Alle Fehlercodes

Anfrage
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
}'
Beispiel: Rumpf
{
  "max_select": 2
}
Antwort
{
  "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

NameOrtBeschreibung
idstring (uuid)PflichtpathThe option group id (UUID).

Antwort200

OK

  • objectstringPflicht

    The type of the deleted object.

    Werte:"product""option_group""option"

  • idstring (uuid)Pflicht

    The deleted object.

  • deletedbooleanPflicht

    Always 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. param names the field.
  • 401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.
  • 403insufficient_scope, plan_required (with param when an order type is not in the plan) or account_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 this external_id for 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) or conflict (the same data was changed elsewhere at the same moment, three times in a row; retry unchanged after Retry-After seconds).
  • 429rate_limited: more than 120 requests per minute on this connection. Wait Retry-After seconds.
  • 500server_error. Safe to retry with the same Idempotency-Key.

Alle Fehlercodes

Anfrage
curl -X DELETE "https://api.flavoso.com/v1/menu/option-groups/OPTION_GROUP_ID" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
Antwort
{
  "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

NameOrtBeschreibung
idstring (uuid)PflichtpathThe option group id (UUID).
Idempotency-KeystringheaderAny 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

  • namestringPflicht

    Name as the guest sees it.

    1 to 120 chars

  • price_deltainteger

    Surcharge in cents, may be negative. Default 0.

    -100000 to 100000

  • availableboolean

    false = sold out. Default true.

  • positioninteger

    Sort order. Default: after the last option of the group.

    0 to 100000

  • external_idstring

    Your own id for this option.

    1 to 100 chars

Antwort201

OK

  • objectstringPflicht

    Always option.

    Werte:"option"

  • idstring (uuid)Pflicht

    The option id.

  • external_idstringPflichtkann null sein

    Your 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)Pflicht

    The option group it belongs to.

  • namestringPflicht

    Name as the guest sees it.

    max 120 chars

  • price_deltaintegerPflicht

    Surcharge, may be negative. Integer amount in the smallest currency unit (cents).

  • availablebooleanPflicht

    false = sold out on every product that uses the group.

  • positionintegerPflicht

    Sort order within the group.

  • updated_atstring (date-time)Pflicht

    Last 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. param names the field.
  • 401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.
  • 403insufficient_scope, plan_required (with param when an order type is not in the plan) or account_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 this external_id for 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) or conflict (the same data was changed elsewhere at the same moment, three times in a row; retry unchanged after Retry-After seconds).
  • 413payload_too_large: the body is larger than the operation takes, counted in bytes: 64 KB, 2 MB for PUT /menu. Split the request.
  • 422idempotency_key_reused (the key was used with a different request) or limit_exceeded (too many entries in one request, such as products in PUT /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. Wait Retry-After seconds.
  • 500server_error. Safe to retry with the same Idempotency-Key.

Alle Fehlercodes

Anfrage
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"
}'
Beispiel: Rumpf
{
  "name": "Extra cheese",
  "price_delta": 150,
  "external_id": "MOD-CHEESE"
}
Antwort
{
  "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

NameOrtBeschreibung
limitintegerqueryPage size, 1 to 100. Default 25.1 to 100 · default 25
cursorstringquerynext_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)queryOnly 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_idstringqueryOnly the object that carries this external_id of your app (zero or one result).max 100 chars

Antwort200

OK

  • objectstringPflicht

    Always list.

    Werte:"list"

  • dataOption[]Pflicht

    This page, oldest change first.

    Felder darin (9)
    • objectstringPflicht

      Always option.

      Werte:"option"

    • idstring (uuid)Pflicht

      The option id.

    • external_idstringPflichtkann null sein

      Your 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)Pflicht

      The option group it belongs to.

    • namestringPflicht

      Name as the guest sees it.

      max 120 chars

    • price_deltaintegerPflicht

      Surcharge, may be negative. Integer amount in the smallest currency unit (cents).

    • availablebooleanPflicht

      false = sold out on every product that uses the group.

    • positionintegerPflicht

      Sort order within the group.

    • updated_atstring (date-time)Pflicht

      Last change of one of its fields. ISO 8601 in UTC (Z).

  • has_morebooleanPflicht

    Whether another page follows.

  • next_cursorstringPflichtkann null sein

    Pass as cursor for 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. param names the field.
  • 401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.
  • 403insufficient_scope, plan_required (with param when an order type is not in the plan) or account_inactive.
  • 429rate_limited: more than 120 requests per minute on this connection. Wait Retry-After seconds.
  • 500server_error. Safe to retry with the same Idempotency-Key.

Alle Fehlercodes

Anfrage
curl "https://api.flavoso.com/v1/menu/options" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
Antwort
{
  "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

NameOrtBeschreibung
idstring (uuid)PflichtpathThe option id (UUID).

Antwort200

OK

  • objectstringPflicht

    Always option.

    Werte:"option"

  • idstring (uuid)Pflicht

    The option id.

  • external_idstringPflichtkann null sein

    Your 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)Pflicht

    The option group it belongs to.

  • namestringPflicht

    Name as the guest sees it.

    max 120 chars

  • price_deltaintegerPflicht

    Surcharge, may be negative. Integer amount in the smallest currency unit (cents).

  • availablebooleanPflicht

    false = sold out on every product that uses the group.

  • positionintegerPflicht

    Sort order within the group.

  • updated_atstring (date-time)Pflicht

    Last 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. param names the field.
  • 401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.
  • 403insufficient_scope, plan_required (with param when an order type is not in the plan) or account_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. Wait Retry-After seconds.
  • 500server_error. Safe to retry with the same Idempotency-Key.

Alle Fehlercodes

Anfrage
curl "https://api.flavoso.com/v1/menu/options/OPTION_ID" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
Antwort
{
  "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

NameOrtBeschreibung
idstring (uuid)PflichtpathThe option id (UUID).

Rumpfapplication/json

  • namestring

    New name.

    1 to 120 chars

  • price_deltainteger

    New surcharge in cents, may be negative.

    -100000 to 100000

  • availableboolean

    false = sold out, true = available again. A target state, no toggling.

  • positioninteger

    New sort order.

    0 to 100000

  • group_idstring (uuid)

    Move the option into this option group.

  • external_idstringkann null sein

    Set or change your id; null removes it.

    1 to 100 chars

Antwort200

OK

  • objectstringPflicht

    Always option.

    Werte:"option"

  • idstring (uuid)Pflicht

    The option id.

  • external_idstringPflichtkann null sein

    Your 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)Pflicht

    The option group it belongs to.

  • namestringPflicht

    Name as the guest sees it.

    max 120 chars

  • price_deltaintegerPflicht

    Surcharge, may be negative. Integer amount in the smallest currency unit (cents).

  • availablebooleanPflicht

    false = sold out on every product that uses the group.

  • positionintegerPflicht

    Sort order within the group.

  • updated_atstring (date-time)Pflicht

    Last 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. param names the field.
  • 401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.
  • 403insufficient_scope, plan_required (with param when an order type is not in the plan) or account_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 this external_id for 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) or conflict (the same data was changed elsewhere at the same moment, three times in a row; retry unchanged after Retry-After seconds).
  • 413payload_too_large: the body is larger than the operation takes, counted in bytes: 64 KB, 2 MB for PUT /menu. Split the request.
  • 422idempotency_key_reused (the key was used with a different request) or limit_exceeded (too many entries in one request, such as products in PUT /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. Wait Retry-After seconds.
  • 500server_error. Safe to retry with the same Idempotency-Key.

Alle Fehlercodes

Anfrage
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
}'
Beispiel: Rumpf
{
  "available": false
}
Antwort
{
  "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

NameOrtBeschreibung
idstring (uuid)PflichtpathThe option id (UUID).

Antwort200

OK

  • objectstringPflicht

    The type of the deleted object.

    Werte:"product""option_group""option"

  • idstring (uuid)Pflicht

    The deleted object.

  • deletedbooleanPflicht

    Always 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. param names the field.
  • 401unauthorized: missing, malformed, expired or revoked access token, or the app was disconnected or suspended.
  • 403insufficient_scope, plan_required (with param when an order type is not in the plan) or account_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 this external_id for 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) or conflict (the same data was changed elsewhere at the same moment, three times in a row; retry unchanged after Retry-After seconds).
  • 429rate_limited: more than 120 requests per minute on this connection. Wait Retry-After seconds.
  • 500server_error. Safe to retry with the same Idempotency-Key.

Alle Fehlercodes

Anfrage
curl -X DELETE "https://api.flavoso.com/v1/menu/options/OPTION_ID" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
Antwort
{
  "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.