↑↓select↵openescclose

Core concepts

The menu

The menu is a file under a stable URL; menu.updated tells you when it changed.

7.1 How you get it

How the menu reaches you
How the menu reaches you
sequenceDiagram
    participant R as Restaurant till
    participant A as Partner API
    participant F as Menu file
    participant P as Partner
    R->>A: syncs the menu, or switches an item on or off
    A->>F: writes the full menu
    A-)P: webhook menu.updated (url, hash)
    P->>P: hash already known? then skip the download
    P->>F: GET url (no API key)
    F-->>P: full menu, always the newest file
How the menu reaches you

There is no menu endpoint. Each connection's menu is a file under a stable URL, which you can GET without the API key.

  • You learn the URL from menu.updated (data.url) or from GET /ping (menuUrl). Treat it as opaque; it does not change for the connection.
  • We write the file again when the restaurant syncs its menu, and when it switches a product or option on or off. Each write sends you menu.updated.
  • Download the file on every menu.updated, at start-up, and whenever you have lost your copy. Compare the SHA-256 of the copy you hold with hash / menuHash to skip a download you do not need.
  • The URL always serves the newest file. If an event arrives late, the file may already be newer than the event. Take it anyway; it is the current menu.
  • Do not cache the file beyond its Cache-Control: no-store.
  • The file is deleted when the restaurant deletes the connection.

7.2 menu.updated

JSON
{
  "url": "https://cdn.horeca-24.de/partner-api/menus/pc_3f1c0a9e5b7d2c4f6a81/menu.json",
  "hash": "9b2f…",
  "publishedAt": "2026-10-15T09:58:12.000Z",
  "updatedAt": "2026-10-15T10:04:40.000Z"
}
FieldMeaning
urlWhere the menu file is.
hashSHA-256 (hex) of the file's bytes at the time the event was sent.
publishedAtThe sync that last changed the menu. A sync that changes nothing leaves the file, its publishedAt and its hash as they were.
updatedAtThe last write of the file, including on/off switches.

Answer 2xx as soon as you have stored the event. Nothing on our side waits for you to download the file.

7.3 The file

JSON
{
  "schemaVersion": "1",
  "publishedAt": "2026-10-15T09:58:12.000Z",
  "currency": "EUR",
  "timeZone": "Europe/Berlin",
  "priceIncludesTax": true,
  "categories": [ { "id": "c1", "name": "Pizza", "position": 0 } ],
  "products": [
    { "id": "p1", "name": "Margherita", "categoryIds": ["c1"], "position": 0, "available": true,
      "prices": { "pickup": 1200, "delivery": 1300 }, "taxRate": 7, "modifierGroupIds": ["g1"],
      "description": "Tomato, mozzarella, basil", "imageUrl": "https://images.example/p1.jpg" }
  ],
  "modifierGroups": [
    { "id": "g1", "name": "Extras", "minQuantity": 0, "maxQuantity": 2,
      "options": [ { "id": "o1", "name": "Cheese", "unitPrice": 150, "available": true, "maxQuantity": 1 } ] }
  ],
  "openingHours": [ { "day": "MONDAY", "open": "11:00", "close": "22:00" } ]
}
  • It is always the full menu, never a delta. It is at most 3 MiB.
  • Prices are gross (tax included), in cents. priceIncludesTax is always true.
  • taxRate is the product's take-away tax rate in percent.
  • prices.pickup and prices.delivery are the restaurant's prices per order type.
  • A product with variants comes as one product per variant, e.g. "Pasta – Large". Order the variant's id.
  • Switched-off products and options stay in the file with available: false; they are never left out, so their ids stay valid.
  • position is the display order. categoryIds and modifierGroupIds refer to the lists in the same file.
  • A modifier group's minQuantity and maxQuantity say how many of its options a customer must or may choose. An option's maxQuantity is how often it can be chosen. Use them for your shop; orders are not checked against them.
  • description and imageUrl are optional. imageUrl is only present as an absolute https URL.
  • openingHours is for information only and may be empty. open / close are local times (HH:mm) in timeZone.