Mittagsmenü-API
Tagesmenüs und Wochenhits von Schweizer Restaurants abrufen, auf der eigenen Website zeigen oder direkt aus dem Kassensystem übertragen. Offenes Format, ohne Anmeldung zum Lesen.
Format: schema.org, kein eigenes
Es gibt keinen eigenen Standard für Mittagsmenüs, aber einen allgemeinen, der sie vollständig abdeckt: schema.org Menu, MenuItem und Offer als JSON-LD. Google liest es, viele Website-Baukästen erzeugen es schon. Wir legen nur fest, welche Felder Pflicht sind und wie sie gelesen werden:
| Feld | Bedeutung |
|---|---|
MenuSection.name | Angebotsart, z. B. «Mittagsmenü», «Wochenhit», «Vegi-Menü». Ohne Section gilt Menu.name, sonst «Mittagsmenü». |
MenuItem.name Pflicht | Das Gericht, höchstens 160 Zeichen. |
MenuItem.description | Beilagen, Hinweise, höchstens 500 Zeichen. |
MenuItem.suitableForDiet | VegetarianDiet, VeganDiet, GlutenFreeDiet (als URL oder Kurzname, einzeln oder als Liste). Andere Werte werden ignoriert. |
Offer.price | Zahl von 0 bis 999, z. B. "24.50". |
Offer.priceCurrency | ISO 4217, Standard CHF. |
Offer.validFrom, Offer.validThrough Pflicht |
ISO 8601. Nur ein Datum (2026-10-06) heisst: der ganze Tag.
Mit Uhrzeit (2026-10-06T11:30:00+02:00) zählt die Uhrzeit als Servicezeit.
Ohne Zeitzonenangabe gilt Schweizer Zeit. Höchstens 31 Tage pro Angebot.
|
Angenommen werden ein Menu, ein Restaurant mit hasMenu oder ein @graph, der eines davon enthält – so, wie es viele Websites schon ausgeben.
Menü lesen
Ohne Anmeldung. Liefert die heutigen und kommenden Angebote eines Restaurants. Die Restaurant-ID steht in der Adresse der Menüseite in Tavatella.
curl https://app.tavatella.com/api/v1/restaurants/12345/menu
Antwort (Content-Type: application/ld+json):
{
"@context": "https://schema.org",
"@type": "Restaurant",
"@id": "https://app.tavatella.com/api/v1/restaurants/12345/menu",
"identifier": "12345",
"name": "Restaurant Beispiel",
"address": "Marktgasse 1, 9000 St. Gallen",
"hasMenu": {
"@type": "Menu",
"name": "Mittagsangebote",
"hasMenuSection": [
{
"@type": "MenuSection",
"name": "Mittagsmenü",
"hasMenuItem": [
{
"@type": "MenuItem",
"identifier": "881",
"name": "Zürcher Geschnetzeltes mit Rösti",
"description": "mit Saisonsalat",
"offers": {
"@type": "Offer",
"price": "24.50",
"priceCurrency": "CHF",
"validFrom": "2026-10-06T11:30:00+02:00",
"validThrough": "2026-10-06T14:00:00+02:00"
}
}
]
}
]
}
}
In JavaScript:
const res = await fetch('https://app.tavatella.com/api/v1/restaurants/12345/menu');
const restaurant = await res.json();
for (const section of restaurant.hasMenu.hasMenuSection) {
for (const item of section.hasMenuItem) {
console.log(section.name, item.name, item.offers.price);
}
}
Menü schreiben
Für Kassensysteme und Websites, die das Menü ohnehin pflegen. Das Token erstellt der Betreiber in Tavatella: im Restaurant auf «Betreibst du dieses Restaurant?», nach der Bestätigung unter «Automatisch übertragen (API)». Ein Token gilt für genau ein Restaurant.
curl -X PUT https://app.tavatella.com/api/v1/restaurants/12345/menu \
-H "Authorization: Bearer tvt_…" \
-H "Content-Type: application/ld+json" \
--data @menu.json
menu.json:
{
"@context": "https://schema.org",
"@type": "Menu",
"hasMenuSection": [
{
"@type": "MenuSection",
"name": "Mittagsmenü",
"hasMenuItem": [
{
"@type": "MenuItem",
"name": "Zürcher Geschnetzeltes mit Rösti",
"description": "mit Saisonsalat",
"offers": {
"@type": "Offer",
"price": "24.50",
"priceCurrency": "CHF",
"validFrom": "2026-10-06T11:30:00+02:00",
"validThrough": "2026-10-06T14:00:00+02:00"
}
},
{
"@type": "MenuItem",
"name": "Älplermagronen mit Apfelmus",
"suitableForDiet": "https://schema.org/VegetarianDiet",
"offers": {
"@type": "Offer",
"price": "21.00",
"validFrom": "2026-10-06",
"validThrough": "2026-10-06"
}
}
]
},
{
"@type": "MenuSection",
"name": "Wochenhit",
"hasMenuItem": [
{
"@type": "MenuItem",
"name": "Thai-Curry mit Jasminreis",
"suitableForDiet": [
"https://schema.org/VeganDiet"
],
"offers": {
"@type": "Offer",
"price": "23.00",
"validFrom": "2026-10-05",
"validThrough": "2026-10-09"
}
}
]
}
]
}
PUT ersetzt alle heutigen und kommenden Angebote, die über die API kamen. Was der Betreiber von Hand
in Tavatella erfasst hat, bleibt stehen. Die Antwort ist dieselbe wie beim Lesen. Zum Leeren, etwa an einem Ruhetag, ein Menu ohne Gerichte schicken:
{"@context":"https://schema.org","@type":"Menu","hasMenuItem":[]}
| Status | Bedeutung |
|---|---|
| 200 | Übernommen, Antwort enthält das neue Menü. |
| 400 | Kein JSON im Body. |
| 401 | Token fehlt oder ist ungültig (widerrufen?). |
| 403 | Token gehört zu einem anderen Restaurant. |
| 404 | Restaurant unbekannt oder geschlossen. |
| 422 | Ungültige Daten. Jeder Fehler steht mit seinem JSON-Pfad da, nichts wurde gespeichert. |
| 429 | Zu viele Anfragen, siehe Retry-After. |
Beispiel 422:
{
"message": "validFrom ist Pflicht (ISO 8601, z. B. \"2026-10-06\" oder \"2026-10-06T11:30:00+02:00\"). (and 1 more error)",
"errors": {
"hasMenuSection.0.hasMenuItem.1.offers.validFrom": [
"validFrom ist Pflicht (ISO 8601, z. B. \"2026-10-06\" oder \"2026-10-06T11:30:00+02:00\")."
],
"hasMenuSection.1.hasMenuItem.0.offers.price": [
"price ist eine Zahl zwischen 0 und 999, z. B. \"24.50\"."
]
}
}
Widget für die eigene Website
Zwei Zeilen, kein Framework, keine Cookies. Zeigt das heutige Menü; mit data-tavatella-show="all" auch die kommenden Angebote. Die Darstellung übernimmt die Schrift der Website.
<div data-tavatella-menu="12345"></div>
<script src="https://app.tavatella.com/widget/menu.js" async></script>
Grenzen und Caching
- Lesen: 120 Anfragen pro Minute und IP. Antworten dürfen 5 Minuten gecacht werden (
Cache-Control). - Schreiben: 30 Anfragen pro Minute, höchstens 50 Angebote, je höchstens 31 Tage gültig.
- Lesen ist von jeder Website aus erlaubt (CORS).
- Fragen und Wünsche: app@tavatella.com