Authentifizierung
Alle Endpoints außer /v1/health erfordern HTTP Basic Auth über HTTPS. Die Zugangsdaten werden von tech2solutions vergeben — Anfragen dazu bitte an info@book2eat.eu. Jeder API-User ist für einen oder mehrere Standorte freigeschaltet — Requests für nicht freigeschaltete Standorte werden mit 403 abgelehnt.
curl -u benutzername:passwort https://supplier-api.book2eat.eu/v1/locations
Damit lässt sich der eigene Zugang testen, ohne bereits eine echte Speiseplan-ID zu kennen — die Antwort zeigt die freigeschalteten Standorte (siehe unten).
Datenformat
Alle Request- und Response-Bodies sind JSON (application/json; charset=utf-8). Datumsangaben: YYYY-MM-DD, Zeitstempel: YYYY-MM-DD HH:MM:SS (lokale Zeit, Europa/Wien).
orderDeadline kann pro menuItem mitgegeben werden und übersteuert dann den Wert auf Speiseplan-Ebene. cancelDeadline ist optional — fehlt sie, wird automatisch orderDeadline + 7 Tage gesetzt. minOrderQuantity wird entgegengenommen, aber derzeit nicht ausgewertet. Sobald bei einem menuItem sowohl orderDeadline als auch cancelDeadline verstrichen sind, gilt es als final (final: true) und kann über die API nicht mehr geändert oder gelöscht werden (409 ITEM_LOCKED) — die Bestellzahl steht dann endgültig fest.
Preis:
price ist auf jedem menuItem optional — und wird nur übernommen, wenn der Standort in book2eat dafür freigeschaltet ist. Standardmäßig ist die Übernahme aus: der Preis kommt dann wie bisher aus dem in book2eat gepflegten Menütyp, ein mitgeschicktes price wird ignoriert. Ist die Übernahme aktiv, gilt zusätzlich die Standort-Einstellung, ob die gesendeten Beträge netto oder brutto sind — bei netto rechnet book2eat mit der im Menütyp hinterlegten Umsatzsteuer auf brutto hoch. Fehlende, nicht numerische oder negative Beträge fallen immer auf den Menütyp-Preis zurück. Umsatzsteuer (vat) kommt immer aus book2eat und kann nicht per API gesetzt werden.
Gesamtmenü: In book2eat kann ein buchbares Menü aus mehreren Menülinien zusammengesetzt sein — etwa Suppe und Nachspeise, die immer zum Hauptgericht gehören. Solche Bestandteile werden nicht separat bestellt: Ihre Bestellzahl ist die Summe der Menüs, denen sie zugeordnet sind. Ist die Preisübernahme aktiv, werden sie mit 0 € ausgewiesen, und ob ihr gesendeter Preis zusätzlich auf das Menü aufgeschlagen wird oder der Menüpreis unverändert gilt, ist je Menü eingestellt. Ohne Preisübernahme gelten — wie bei allen Linien — die in book2eat gepflegten Preise. Die Berechnung umfasst immer den ganzen Speiseplan: Wird ein einzelner Eintrag über
/items angelegt, geändert oder gelöscht, rechnet book2eat die Gesamtmenüs neu durch — mit den zuletzt gesendeten Preisen der übrigen Einträge. Eine Linie kann Bestandteil mehrerer Menüs sein — dieselbe Suppe etwa von Menü 1 und Menü 2, während ein Allergiker- oder Glutenmenü eine eigene hat. Am Payload ändert das nichts: Alle Linien werden weiterhin ganz normal mitgeschickt, die Zuordnung pflegt tech2solutions und sie ist im Menülinien-Mapping einsehbar.
Freigeschaltete Standorte abrufen
Listet die Standorte, für die der authentifizierte API-User freigeschaltet ist — damit lässt sich vorab prüfen, ob die eigene locationId korrekt mit book2eat verknüpft ist, bevor ein Speiseplan gesendet wird. linked: false bedeutet, dass der Standort zwar freigeschaltet, aber noch keiner locationId zugeordnet ist — in dem Fall bitte tech2solutions kontaktieren. active spiegelt das bestehende Aktiv-Flag der Einrichtung in book2eat: ist eine Einrichtung hier false, schlägt POST/PUT mit 422 UNKNOWN_LOCATION fehl, auch wenn linked: true ist — book2eat findet die deaktivierte Einrichtung über locationId dann nicht mehr.
[
{
"canteen_ID": 271,
"canteenName": "Kindergarten Obersulz",
"locationId": "642",
"active": true,
"linked": true
}
]
Menülinien-Mapping abrufen
Listet die für diesen Standort in book2eat gepflegten Menülinien-Mappings (menuLines[].id → book2eat-Menütyp inkl. aktuellem Preis/USt.) — damit lässt sich vorab prüfen, ob eine ADAM-Menülinie korrekt verknüpft ist, bevor ein Speiseplan gesendet wird. Fehlt eine extLineId hier, liefert POST/PUT für genau diese Linie später 422 UNKNOWN_MENU_LINE — in dem Fall bitte tech2solutions kontaktieren, damit die Zuordnung ergänzt wird.
Die Liste zeigt außerdem, wie book2eat die jeweilige Linie behandelt: ob ein mitgeschickter Preis überhaupt übernommen wird und ob die Linie Bestandteil eines Gesamtmenüs ist. Damit lässt sich ein unerwarteter Preis im Bestellsystem hier nachvollziehen, ohne nachfragen zu müssen.
[
{
"extLineId": "4056",
"lineName": "Tagessuppe",
"typ_ID": 1,
"name": "Suppe",
"standard_price": "0.00",
"vat": 10,
"component_price_mode": "menu_only",
"component_quantity_mode": "sum_of_menus",
"part_of_typ_IDs": [2, 3],
"supplier_price_use": true,
"supplier_price_is_net": true
},
{
"extLineId": "4057",
"lineName": "Hauptspeise",
"typ_ID": 2,
"name": "Hauptgericht",
"standard_price": "8.90",
"vat": 10,
"component_price_mode": "sum",
"component_quantity_mode": "sum_of_menus",
"part_of_typ_IDs": [],
"supplier_price_use": true,
"supplier_price_is_net": true
},
{
"extLineId": "4058",
"lineName": "Alternativmenü",
"typ_ID": 3,
"name": "Alternativmenü",
"standard_price": "8.90",
"vat": 10,
"component_price_mode": "sum",
"component_quantity_mode": "sum_of_menus",
"part_of_typ_IDs": [],
"supplier_price_use": true,
"supplier_price_is_net": true
}
]
Im Beispiel ist die Tagessuppe Bestandteil beider Menüs (part_of_typ_IDs: [2, 3]): Sie wird nicht einzeln bestellt, kostet nichts extra, und als Bestellmenge wird ihr die Summe aus Hauptspeise und Alternativmenü gemeldet.
| Feld | Bedeutung |
|---|---|
extLineId | Die ID, die im Speiseplan als menuLines[].id gesendet wird — der Anker für alle weiteren Requests. |
lineName | Letzter bekannter Name der Linie, nur zur Orientierung — maßgeblich ist die extLineId. |
typ_ID · name | Der book2eat-Menütyp, auf den die Linie verweist. Alle Preis- und Fristen-Einstellungen hängen an diesem Typ. |
standard_price · vat | Der in book2eat gepflegte Bruttopreis und die Umsatzsteuer. Dieser Preis gilt immer dann, wenn kein price übernommen wird. vat kann nie per API gesetzt werden. |
part_of_typ_IDs | Die Menüs, deren Bestandteil diese Linie ist (Gesamtmenü), als Liste von typ_IDs. Leere Liste = eigenständige Linie. Mehrere Einträge bedeuten, dass dieselbe Linie zu mehreren Menüs gehört — die Suppe im Beispiel zu Hauptspeise und Alternativmenü. |
component_price_mode | Gilt am buchbaren Menü: sum = die Preise seiner Bestandteile werden auf den Menüpreis aufgeschlagen, der Gast zahlt einen Gesamtpreis. menu_only = der Menüpreis gilt unverändert, die Bestandteile sind darin schon enthalten. In beiden Fällen wird der Bestandteil selbst mit 0,00 ausgewiesen. Wirkt nur bei supplier_price_use: true — sonst gilt je Linie standard_price. |
component_quantity_mode | Gilt am Bestandteil: sum_of_menus = gemeldet wird die Summe der Bestellmengen aller Menüs aus part_of_typ_IDs. own_orders = gemeldet werden nur die Bestellungen, die wirklich auf dieser Linie liegen. |
supplier_price_use | false (Standard) = ein mitgeschicktes price wird ignoriert, es gilt standard_price. true = der gesendete Preis wird übernommen. |
supplier_price_is_net | Nur bei supplier_price_use: true relevant: true = die gesendeten Beträge sind netto und werden mit vat auf brutto hochgerechnet, false = sie sind bereits brutto. |
part_of_typ_IDs), die beiden component_*-Modi und die supplier_price_*-Schalter werden von tech2solutions gepflegt und können nicht per API geändert werden — diese Liste gibt sie nur zum Nachlesen aus. Weicht ein Preis oder eine Bestellzahl von der Erwartung ab, steht hier die Erklärung.
Speiseplan anlegen
Legt einen Wochenspeiseplan an. Die Speiseplan-ID (id) sowie alle menuItem-IDs sind die IDs des sendenden Systems und bleiben in allen weiteren Requests die Referenz. Da es sich um einen neuen Speiseplan handelt, existieren noch keine Bestellungen dazu — es wird keine Benachrichtigung verschickt.
{
"id": "717304",
"locationId": "642",
"locationName": "Kindergarten Obersulz",
"week": "17",
"year": "2026",
"startDate": "2026-04-20",
"endDate": "2026-04-26",
"orderDeadline": "2026-04-06 23:59:59",
"menuLines": [
{
"id": "4056",
"name": "Tagessuppe",
"minOrderQuantity": "0",
"menuItems": [
{
"id": "15323144",
"name": "Klare Hühnersuppe mit Reibteig",
"date": "2026-04-20",
"isExtraMenu": false,
"allergens": ["A", "C"],
"additives": [],
"attributes": ["enthält Gluten"],
"price": 3.50,
"nutritionValues": { "kJ": 203, "kcal": 49 }
}
]
}
]
}
{ "id": "717304", "status": "created", "itemsImported": 10 }
Existiert die Speiseplan-ID bereits, antwortet die API mit 409 Conflict — Änderungen laufen über PUT.
Speiseplan abrufen
Liefert den aktuell in book2eat hinterlegten Stand des Speiseplans {id} — Name, Datum, Allergene und Deadlines je menuItem, so wie sie aktuell gespeichert sind (nicht zwingend identisch mit dem ursprünglich gesendeten Payload, falls es zwischenzeitlich Änderungen gab). additives, attributes und nutritionValues werden von book2eat nicht gespeichert und sind daher hier nicht enthalten; der Preis ist ebenfalls nicht Teil der Antwort.
{
"id": "717304",
"locationId": "642",
"week": 17,
"year": 2026,
"startDate": "2026-04-20",
"endDate": "2026-04-26",
"menuLines": [
{
"id": "4056",
"name": "Tagessuppe",
"menuItems": [
{
"id": 15323144,
"name": "Klare Hühnersuppe mit Reibteig",
"date": "2026-04-20",
"allergens": ["A", "C"],
"orderDeadline": "2026-04-06 23:59:59",
"cancelDeadline": "2026-04-13 23:59:59",
"orderDeadlinePassed": true,
"cancelDeadlinePassed": true,
"final": true
}
]
}
]
}
Bestellzahlen abrufen
Liefert die Bestellzahlen je menuItem für den Speiseplan {id}. Einträge ohne Bestellungen werden mit quantity: 0 ausgegeben. Für Bestandteile eines Gesamtmenüs wird die Summe der zugeordneten Menüs desselben Tages gemeldet (bzw. bei component_quantity_mode: own_orders die eigenen Bestellungen der Linie). final zeigt an, ob die Zahl schon endgültig ist — also sowohl orderDeadline als auch cancelDeadline bereits verstrichen sind und sich die Bestellzahl nicht mehr ändern kann.
[
{
"id": 15323144,
"date": "2026-04-20",
"quantity": 15,
"orderDeadline": "2026-04-06 23:59:59",
"cancelDeadline": "2026-04-13 23:59:59",
"orderDeadlinePassed": true,
"cancelDeadlinePassed": true,
"final": true
}
]
Bestellzahlen über Woche/Zeitraum abrufen
Liefert Bestellzahlen kantinenweit über mehrere Speisepläne hinweg — wahlweise für eine ganze Woche oder einen freien Datumsbereich, ohne dass die einzelne Speiseplan-ID bekannt sein muss. Erfordert locationId sowie entweder week+year oder from+to (YYYY-MM-DD) als Query-Parameter. planId im Ergebnis verweist auf die zugehörige Speiseplan-ID.
week+year anhand der tatsächlichen Kalenderwoche geprüft). Außerhalb dieser Grenzen antwortet die API mit 400 VALIDATION_ERROR.
curl -u benutzername:passwort \ "https://supplier-api.book2eat.eu/v1/menus/quantities?locationId=642&week=17&year=2026" curl -u benutzername:passwort \ "https://supplier-api.book2eat.eu/v1/menus/quantities?locationId=642&from=2026-04-20&to=2026-04-26"
[
{
"id": 15323144,
"planId": "717304",
"date": "2026-04-20",
"quantity": 15,
"orderDeadline": "2026-04-06 23:59:59",
"cancelDeadline": "2026-04-13 23:59:59",
"orderDeadlinePassed": true,
"cancelDeadlinePassed": true,
"final": true
}
]
Speiseplan komplett ersetzen
Ersetzt den Speiseplan durch den mitgeschickten Body (gleiche Struktur wie POST). Neue Einträge werden angelegt, bestehende (gleiche menuItem-ID) aktualisiert. Einträge, die im Body fehlen, werden entfernt — vorhandene Bestellungen dazu werden storniert. menuItems, die bereits final sind (siehe oben), bleiben unangetastet — weder Änderung noch Entfernung — und zählen in der Antwort als protected. Der restliche Speiseplan wird ganz normal verarbeitet.
menuItems werden Besteller mit einer aktiven Bestellung (nicht bereits stornierte) automatisch von book2eat per E-Mail benachrichtigt. Für neu angelegte Einträge sowie für protected Einträge gibt es keine Benachrichtigung.
Die Mail nennt die Einrichtung, den Namen des Bestellers, Datum und Gericht — und sagt, was passiert ist: Bei einem aktualisierten Eintrag bleibt die Bestellung bestehen und soll geprüft werden, bei einem entfernten wurde sie storniert. Das ist relevant, weil die Mail bei Kinderbuchungen an das Elternkonto geht: Ohne den Namen wäre bei mehreren Kindern nicht erkennbar, wessen Bestellung betroffen ist.
{ "id": "717304", "status": "replaced", "created": 1, "updated": 8, "removed": 1, "protected": 0, "cancelledOrders": 12 }
Einzelnen Eintrag anlegen
Fügt einem bestehenden Speiseplan ein einzelnes neues menuItem hinzu — Alternative zu PUT auf den ganzen Plan, wenn nur ein einzelner Eintrag dazukommt (z. B. nachträglich ergänztes Angebot für einen Tag), statt den kompletten Speiseplan erneut zu senden.
Pflichtfelder: menuLineId (die bestehende Menülinie, der das Item zugeordnet wird — muss für den Standort bereits über ein Menülinien-Mapping verknüpft sein), id, name, date (muss innerhalb der Speiseplanwoche liegen). Optional: menuLineName, isExtraMenu, allergens, additives, attributes, orderDeadline, cancelDeadline, price (nur bei freigeschalteter Preisübernahme), nutritionValues. Wie bei POST /v1/menus muss die menuItem-ID global eindeutig sein — sonst 409 ITEM_ID_EXISTS.
POST /v1/menus — keine Benachrichtigung verschickt.
{
"menuLineId": "2120",
"menuLineName": "Tagessuppe",
"id": "15323199",
"name": "Kürbiscremesuppe mit Kernöl",
"date": "2026-04-25",
"allergens": ["G"],
"orderDeadline": "2026-04-11 23:59:59"
}
{ "id": "15323199", "status": "created" }
Einzelnen Eintrag ändern
Detailänderung eines einzelnen Speiseplaneintrags — z. B. Komponententausch oder geändertes Bestellende. Erlaubte Felder: name, date, allergens, orderDeadline, cancelDeadline, price (optional, nur bei freigeschalteter Preisübernahme). Nur mitgeschickte Felder werden geändert — fehlt price, bleibt der zuletzt gesendete Preis bestehen. Besteller mit einer aktiven Bestellung für diesen Eintrag werden automatisch von book2eat per E-Mail benachrichtigt. Ist das Item bereits final, antwortet die API mit 409 ITEM_LOCKED.
{
"name": "Kürbiscremesuppe mit Kernöl",
"allergens": ["G"],
"orderDeadline": "2026-04-08 23:59:59"
}
{ "id": "15323144", "status": "updated" }
Löschen
Löscht den kompletten Speiseplan. Alle offenen Bestellungen werden storniert. Bereits finale menuItems (siehe oben) werden dabei übersprungen statt gelöscht/storniert und zählen in der Antwort als protected.
menuItem werden automatisch von book2eat per E-Mail benachrichtigt. Für protected Einträge gibt es keine Benachrichtigung.
{ "id": "717304", "deleted": true, "items": 9, "protected": 1, "cancelledOrders": 33 }
Löscht einen einzelnen Speiseplaneintrag inklusive Storno der zugehörigen Bestellungen. Besteller mit einer aktiven Bestellung für diesen Eintrag werden automatisch von book2eat per E-Mail benachrichtigt. Ist das Item bereits final, antwortet die API mit 409 ITEM_LOCKED.
{ "id": "15323144", "deleted": true, "cancelledOrders": 4 }
Fehlercodes
| Status | Code | Bedeutung |
|---|---|---|
| 400 | VALIDATION_ERROR / INVALID_JSON | Body entspricht nicht dem Schema bzw. ist kein gültiges JSON. details enthält die Feldfehler. |
| 401 | UNAUTHORIZED | Fehlende oder falsche Zugangsdaten. |
| 403 | CANTEEN_NOT_ALLOWED | Der API-User ist für diesen Standort nicht freigeschaltet. |
| 404 | MENU_NOT_FOUND / ITEM_NOT_FOUND | Speiseplan bzw. Eintrag existiert nicht. |
| 409 | MENU_EXISTS | Speiseplan-ID bereits vorhanden — PUT verwenden. |
| 409 | ITEM_ID_EXISTS | Die menuItem-ID wird bereits in einem anderen Speiseplan verwendet — menuItem-IDs müssen global eindeutig sein, nicht nur innerhalb eines Speiseplans. |
| 409 | ITEM_LOCKED | order- und cancelDeadline des menuItems sind bereits verstrichen — keine Änderung/Löschung mehr möglich. |
| 422 | UNKNOWN_LOCATION, UNKNOWN_MENU_LINE, UNKNOWN_MENU_TYPE, DATE_OUT_OF_RANGE, DUPLICATE_ITEM_ID | Fachlich ungültig — Meldung beschreibt das Problem. UNKNOWN_MENU_LINE: menuLineId/menuLines[].id ist für diesen Standort keinem book2eat-Menütyp zugeordnet, siehe Menülinien-Mapping. UNKNOWN_MENU_TYPE: die Linie ist zugeordnet, der Menütyp existiert in book2eat aber nicht (mehr) — bitte tech2solutions kontaktieren. |
| 429 | — | Rate-Limit erreicht. Kurz warten und erneut senden. |
{
"error": {
"code": "UNKNOWN_LOCATION",
"message": "Der Standort mit locationId 699 ist in book2eat nicht angelegt.",
"details": null
}
}
Health-Check
Ohne Authentifizierung erreichbar, für Monitoring.
{ "status": "ok", "db": "ok", "time": "2026-07-14T09:00:00.000Z" }