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
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 fileThere 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 fromGET /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 withhash/menuHashto 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"
}| Field | Meaning |
|---|---|
url | Where the menu file is. |
hash | SHA-256 (hex) of the file's bytes at the time the event was sent. |
publishedAt | The sync that last changed the menu. A sync that changes nothing leaves the file, its publishedAt and its hash as they were. |
updatedAt | The 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.
priceIncludesTaxis alwaystrue. taxRateis the product's take-away tax rate in percent.prices.pickupandprices.deliveryare 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. positionis the display order.categoryIdsandmodifierGroupIdsrefer to the lists in the same file.- A modifier group's
minQuantityandmaxQuantitysay how many of its options a customer must or may choose. An option'smaxQuantityis how often it can be chosen. Use them for your shop; orders are not checked against them. descriptionandimageUrlare optional.imageUrlis only present as an absolute https URL.openingHoursis for information only and may be empty.open/closeare local times (HH:mm) intimeZone.