1. Start
  2. Für Entwickler
  3. API

DinnerSpots-API

Lies und schreibe Reservierungen, freie Zeiten, Betriebsferien und den Tischplan eines Restaurants aus deinen eigenen Systemen – etwa aus einem Chatbot für Instagram, Messenger oder WhatsApp, einem Kassensystem oder Zapier und Make.

Überblick

Die API gehört zum Reservierungssystem von Bontrivo. Jeder Schlüssel gilt für genau ein Restaurant. Alle Adressen beginnen mit:

https://dinnerspots.de/api/v1
  • Anfragen und Antworten sind JSON in UTF-8. Schicke bei POST, PATCH und PUT den Header Content-Type: application/json.
  • Datumsangaben im Format JJJJ-MM-TT, Uhrzeiten als HH:MM in der Ortszeit des Restaurants (Europe/Berlin). Zeitstempel nach ISO 8601 mit Zeitzone.
  • Reservierungen erkennst du an ihrem 8-stelligen Code, z. B. K7Q2MZ4P – derselbe Code, den Gast und Restaurant in den E-Mails sehen.
  • Neue Felder können jederzeit dazukommen. Ignoriere Felder, die du nicht kennst.

API-Schlüssel gehören auf deinen Server, nie in den Browser oder in eine App. Für Reservierungen auf deiner Website nimm das Widget – es braucht keinen Schlüssel.

Anmeldung

Schlüssel legst du im Bontrivo-Kundenportal unter Reservierungen → API & Webhooks an. Der Schlüssel wird nur einmal angezeigt – speichere ihn sofort an einem sicheren Ort. Verlorene Schlüssel widerrufst du dort und legst einen neuen an.

curl https://dinnerspots.de/api/v1/restaurant \
  -H "Authorization: Bearer bt_live_…"

Wenn dein Werkzeug keinen Authorization-Header setzen kann, geht auch X-Api-Key: bt_live_….

RechtErlaubt
LesenRestaurant, freie Zeiten, Reservierungen, Betriebsferien und Tischplan abrufen.
SchreibrechtZusätzlich Reservierungen anlegen, ändern, bestätigen und stornieren, Betriebsferien eintragen und Webhooks verwalten.

Einstellungen, Tischplan bearbeiten und Schlüssel verwalten geht nur im Kundenportal. Ein Restaurant kann bis zu 10 aktive Schlüssel haben – am besten einen pro angebundenem System.

Limits

  • 120 Anfragen pro Minute und Schlüssel. Jede Antwort enthält X-RateLimit-Limit und X-RateLimit-Remaining. Darüber antwortet die API mit 429 und Retry-After: 60.
  • Nach 20 fehlgeschlagenen Anmeldungen pro Minute ist eine Minute Pause.
  • Anfragen dürfen höchstens 256 KB groß sein.
  • Listen liefern höchstens 200 Einträge pro Seite.

Fehler

Fehler kommen immer im selben Format. code ist fest und für dein Programm gedacht, message ist ein deutscher Satz, den du Gästen oder Mitarbeitern zeigen kannst.

{
    "error": {
        "code": "validation_failed",
        "message": "Bitte die markierten Angaben prüfen.",
        "fields": {
            "guest.last_name": "Bitte einen Namen angeben.",
            "guest.email": "Bitte eine E-Mail-Adresse oder Telefonnummer angeben."
        }
    }
}
StatuscodeBedeutung
400invalid_jsonDer Body ist kein JSON-Objekt.
401unauthorizedKein oder ein ungültiger Schlüssel.
403insufficient_scopeDer Schlüssel darf nur lesen.
403restaurant_inactiveDas Kundenkonto des Restaurants ist nicht aktiv.
403internal_onlyGeht nur im Kundenportal.
404not_foundEndpunkt, Reservierung, Schließzeit oder Webhook gibt es nicht.
405method_not_allowedFalsche Methode. Der Header Allow nennt die richtigen.
409slot_unavailableDie Uhrzeit ist nicht (mehr) frei. alternatives enthält bis zu vier freie Zeiten am selben Tag.
409closedDas Restaurant hat an diesem Tag geschlossen. closure enthält den Zeitraum.
409booking_disabledDas Restaurant nimmt gerade keine Online-Reservierungen an.
409not_modifiable, not_cancellableDie Reservierung ist schon abgeschlossen, abgesagt oder storniert.
409invalid_transitionDieser Statuswechsel geht nicht. allowed nennt die möglichen.
409limit_reachedHöchstzahl an Schließzeiten oder Webhooks erreicht.
413payload_too_largeDie Anfrage ist größer als 256 KB.
422validation_failedAngaben fehlen oder sind falsch. fields nennt sie einzeln.
429rate_limitedZu viele Anfragen. Warte so viele Sekunden, wie Retry-After angibt.
500server_errorFehler bei uns. Versuche es später erneut.

Restaurant

GET/restaurantLesen

Stammdaten und Reservierungsregeln. booking.accepting ist false, solange das Restaurant keine Online-Reservierungen annimmt. booking.mode ist covers (feste Zahl an Plätzen pro Zeitfenster) oder tables (freie Tische laut Tischplan). booking_hours sind die Zeiten, zu denen reserviert werden kann (weekday 1 = Montag), last_seating ist der späteste Beginn.

Antwort
{
    "restaurant": {
        "slug": "trattoria-da-luca",
        "name": "Trattoria da Luca",
        "address": {
            "street": "Lange Reihe 12",
            "zip": "20099",
            "city": "Hamburg"
        },
        "phone": "+49 40 1234567",
        "website": "https://trattoria-da-luca.de",
        "dinnerspots_url": "https://dinnerspots.de/restaurant/trattoria-da-luca",
        "widget_url": "https://dinnerspots.de/widget/trattoria-da-luca",
        "booking": {
            "accepting": true,
            "auto_confirm": true,
            "max_party_size": 8,
            "slot_interval_minutes": 15,
            "duration_minutes": 120,
            "lead_minutes": 60,
            "horizon_days": 60,
            "mode": "tables",
            "note": null
        },
        "booking_hours": [
            {
                "weekday": 2,
                "day": "tuesday",
                "opens": "17:00",
                "closes": "23:00",
                "last_seating": "21:30"
            }
        ]
    }
}

Freie Zeiten

GET/availability?date=2026-10-17&party=4Lesen

Freie Uhrzeiten an einem Tag für eine Personenzahl. Es gelten dieselben Regeln wie bei der Online-Reservierung: Vorlaufzeit, Buchungszeitraum, Höchstzahl an Personen und die online buchbaren Tische. party ist 1 bis max_party_size, Standard 2. date ist standardmäßig heute.

Antwort
{
    "date": "2026-10-17",
    "party_size": 4,
    "accepting": true,
    "closed": false,
    "closure": null,
    "slots": [
        "18:00",
        "18:15",
        "19:30",
        "19:45",
        "21:00"
    ]
}

Bei Betriebsferien ist closed true und closure enthält z. B. {"from": "2026-12-23", "to": "2027-01-06", "kind": "holiday", "kind_label": "Betriebsferien", "note": null}. closed ist auch true, wenn an diesem Wochentag keine Reservierungszeiten eingetragen sind. Nimmt das Restaurant gerade keine Online-Reservierungen an, ist accepting false und slots leer.

GET/availability/next?party=4&from=2026-10-17&days=14Lesen

Der nächste Tag mit freien Zeiten ab from (Standard: heute), gesucht über days Tage (1 bis 60, Standard 14). next ist null, wenn in diesem Zeitraum nichts frei ist oder das Restaurant gerade keine Online-Reservierungen annimmt. from liegt nie in der Vergangenheit.

Antwort
{
    "from": "2026-10-17",
    "party_size": 4,
    "accepting": true,
    "next": {
        "date": "2026-10-18",
        "slots": [
            "18:00",
            "20:30"
        ]
    }
}

Reservierungen

So sieht eine Reservierung aus:

{
    "reservation": {
        "id": "K7Q2MZ4P",
        "status": "confirmed",
        "date": "2026-10-17",
        "time": "19:30",
        "party_size": 4,
        "duration_minutes": 120,
        "guest": {
            "first_name": "Lena",
            "last_name": "Brandt",
            "email": "[email protected]",
            "phone": "+49 151 2345678"
        },
        "occasion": "geburtstag",
        "notes": "Mit Kinderstuhl, bitte",
        "internal_note": null,
        "source": "api",
        "channel": "instagram",
        "tables": [
            {
                "id": 12,
                "label": "T4",
                "kind": "table"
            }
        ],
        "cancelled_by": null,
        "cancel_reason": null,
        "created_at": "2026-10-04T14:02:11+02:00",
        "updated_at": "2026-10-04T14:02:11+02:00",
        "manage_url": "https://dinnerspots.de/reservierung/3f9c…"
    }
}
FeldBedeutung
statuspending (wartet auf Bestätigung), confirmed, seated (Gast ist da), completed, no_show, declined (abgelehnt), cancelled.
occasiongeburtstag, jahrestag, date, business, feier oder null.
notesWünsche des Gastes, höchstens 600 Zeichen.
internal_noteInterne Notiz des Restaurants. Gäste sehen sie nie.
sourceWoher sie kam: online (DinnerSpots.de), widget (Website), api, phone, walk_in, partner (vom Restaurant eingetragen).
channelKanal für die Statistik, z. B. instagram oder website.
tablesZugewiesene Tische und Barplätze aus dem Tischplan.
manage_urlLink, unter dem der Gast seine Reservierung ändert oder storniert. Gib ihn nur an diesen Gast weiter.

GET/reservationsLesen

Ohne Filter: alle Reservierungen von heute bis in 30 Tagen, sortiert nach Datum und Uhrzeit.

ParameterBedeutung
dateEin Tag.
from, toZeitraum, höchstens 92 Tage.
updated_sinceAlles, was sich seit diesem Zeitpunkt geändert hat, sortiert nach updated_at – ideal zum Abgleich. Ersetzt die Datumsfilter.
statusEiner oder mehrere, mit Komma getrennt: pending,confirmed.
qSuche in Name, E-Mail, Telefon und Code.
limit, offsetSeitenweise abrufen, limit 1 bis 200, Standard 50.
Antwort
{
    "data": [
        "…"
    ],
    "has_more": true,
    "next_offset": 50
}

Ist has_more true, frag mit offset=next_offset die nächste Seite ab.

GET/reservations/{id}Lesen

Eine Reservierung über ihren Code.

POST/reservationsSchreibrecht

Legt eine Reservierung im Namen eines Gastes an. Es gelten dieselben Regeln wie bei der Online-Reservierung. Bestätigt das Restaurant automatisch, ist der Status confirmed, sonst pending.

curl -X POST https://dinnerspots.de/api/v1/reservations \
  -H "Authorization: Bearer bt_live_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: ig-msg-7f3a91" \
  -d '{"date":"2026-10-17","time":"19:30","party_size":4,"guest":{"first_name":"Lena","last_name":"Brandt","email":"[email protected]","phone":"+49 151 2345678"},"occasion":"geburtstag","notes":"Mit Kinderstuhl, bitte","channel":"instagram"}'
FeldPflichtBedeutung
date, timejaAm besten eine Zeit aus /availability.
party_sizeja1 bis max_party_size.
guest.last_namejaHöchstens 80 Zeichen, ebenso guest.first_name.
guest.email oder guest.phoneeins davonMit E-Mail-Adresse bekommt der Gast eine Bestätigung mit Link zum Ändern und Stornieren.
occasion, notesneinSiehe oben.
internal_noteneinNotiz nur für das Restaurant.
channelneinKanal für die Statistik. Ohne Angabe wird der Name des Schlüssels verwendet.

Antwort: 201 mit der Reservierung. Ist die Zeit inzwischen vergeben, kommt 409 slot_unavailable mit Alternativen – biete sie dem Gast direkt an:

{
    "error": {
        "code": "slot_unavailable",
        "message": "Diese Uhrzeit ist nicht frei.",
        "alternatives": [
            "19:00",
            "19:15",
            "20:00",
            "20:15"
        ]
    }
}

Tische zuweisen und die Verweildauer ändern kann nur das Restaurant im Kundenportal. Schickst du tables, duration_minutes oder source mit, antwortet die API mit 422.

Doppelte Buchungen vermeiden

Schicke bei POST /reservations einen Header Idempotency-Key mit, z. B. die ID der Chat-Nachricht (1 bis 64 Zeichen: Buchstaben, Ziffern, _ . : -). Kommt dieselbe Anfrage noch einmal, etwa nach einem Timeout, legt die API keine zweite Reservierung an. Sie antwortet mit 200, der ersten Reservierung und dem Header Idempotent-Replayed: true.

PATCH/reservations/{id}Schreibrecht

Ändert nur die Felder, die du mitschickst: date, time, party_size, guest, occasion, notes, internal_note. Geht bei pending, confirmed und seated, sonst 409 not_modifiable. Neue Zeiten müssen frei sein, sonst 409 slot_unavailable mit Alternativen.

Body
{
    "time": "20:00",
    "party_size": 5,
    "notify_guest": true
}

Ändern sich Datum, Uhrzeit oder Personenzahl, bekommt der Gast eine E-Mail. Mit "notify_guest": false unterbleibt sie.

POST/reservations/{id}/statusSchreibrecht

Bestätigen, ablehnen, Gast als angekommen oder nicht erschienen markieren.

Body
{
    "status": "confirmed",
    "notify_guest": true
}
VonNach
pendingconfirmed, declined
confirmedseated, completed, no_show
seatedcompleted, confirmed
completedno_show, seated
no_showcompleted, confirmed

Beim Bestätigen oder Ablehnen einer offenen Anfrage bekommt der Gast eine E-Mail, außer mit "notify_guest": false. Zum Stornieren gibt es einen eigenen Endpunkt.

POST/reservations/{id}/cancelSchreibrecht

Storniert eine offene oder bestätigte Reservierung, sonst 409 not_cancellable.

Body
{
    "by": "guest",
    "reason": "Gast hat per Instagram abgesagt",
    "notify": true
}
  • by: restaurant (Standard) oder guest, wenn der Gast selbst abgesagt hat.
  • reason: freiwillig, höchstens 255 Zeichen.
  • notify: Standard true. Bei "by": "restaurant" bekommt der Gast eine E-Mail über die Stornierung. Bei "by": "guest" bekommt der Gast immer eine Stornobestätigung, und das Restaurant wird per E-Mail informiert. Mit "notify": false entfällt nur die E-Mail ans Restaurant.

Betriebsferien und Schließtage

An eingetragenen Tagen sind online keine Reservierungen möglich, und Gäste sehen den Hinweis auf DinnerSpots.de und im Widget.

GET/closuresLesen

Alle aktuellen und kommenden Zeiträume. Mit ?include_past=true auch vergangene.

Antwort
{
    "data": [
        {
            "id": 31,
            "from": "2026-12-23",
            "to": "2027-01-06",
            "kind": "holiday",
            "kind_label": "Betriebsferien",
            "note": "Wir sind ab 7. Januar wieder da.",
            "created_at": "2026-10-04T10:15:00+02:00"
        }
    ]
}

POST/closuresSchreibrecht

Body
{
    "from": "2026-12-23",
    "to": "2027-01-06",
    "kind": "holiday",
    "note": "Wir sind ab 7. Januar wieder da."
}
  • kind: holiday (Betriebsferien, Standard), closed (geschlossen) oder event (geschlossene Gesellschaft).
  • to ist standardmäßig gleich from – für einen einzelnen Tag reicht from.
  • note sehen Gäste, höchstens 160 Zeichen. Ein Zeitraum umfasst höchstens 366 Tage, bis zu 200 kommende Zeiträume sind möglich.

Die Antwort (201) enthält neben closure auch affected_count und bis zu 50 affected_reservations: Reservierungen, die in den Zeitraum fallen. Sie werden nicht automatisch storniert – kläre sie mit den Gästen und storniere sie bei Bedarf einzeln.

DELETE/closures/{id}Schreibrecht

Entfernt einen Zeitraum. Antwort: {"deleted": true, "closure": {…}}.

Tischplan

GET/floorplanLesen

Bereiche (z. B. Gastraum, Terrasse) mit ihren Tischen, Barplätzen und Deko-Elementen. Alle Maße sind in Zentimetern: width × height ist die Größe des Bereichs, x/y die linke obere Ecke eines Elements vor der Drehung, w/h seine Größe. rotation (0 bis 359 Grad) dreht im Uhrzeigersinn um die Mitte des Elements. Den Tischplan bearbeitet das Restaurant im Kundenportal.

Antwort
{
    "floorplan": {
        "areas": [
            {
                "id": 1,
                "name": "Gastraum",
                "width": 1000,
                "height": 700,
                "online_bookable": true,
                "items": [
                    {
                        "id": 12,
                        "kind": "table",
                        "shape": "round",
                        "label": "T4",
                        "x": 200,
                        "y": 200,
                        "w": 100,
                        "h": 100,
                        "rotation": 0,
                        "seats_min": 2,
                        "seats_max": 4,
                        "online_bookable": true
                    },
                    {
                        "id": 20,
                        "kind": "bar",
                        "shape": "rect",
                        "label": "Bar",
                        "x": 600,
                        "y": 60,
                        "w": 300,
                        "h": 60,
                        "rotation": 0,
                        "seats_min": 1,
                        "seats_max": 6,
                        "online_bookable": false
                    }
                ]
            }
        ],
        "summary": {
            "tables": 1,
            "bar_seats": 6,
            "seats": 10,
            "online_seats": 4
        }
    }
}

kind ist table, bar oder deco, shape ist rect oder round. Online buchbar ist ein Platz nur, wenn er selbst und sein Bereich online_bookable sind.

Webhooks

Statt regelmäßig nachzufragen, kann dich die API bei jeder Änderung sofort benachrichtigen. Sie schickt dann ein POST mit JSON an deine Adresse. Webhooks verwaltest du im Kundenportal oder mit einem Schlüssel mit Schreibrecht.

EreignisWannZusätzlich in data
reservation.createdNeue Reservierung – egal ob online, über das Widget, die API oder vom Restaurant eingetragen.–
reservation.updatedDatum, Uhrzeit, Personenzahl, Gastdaten oder Notizen geändert.previous mit date, time, party_size
reservation.status_changedBestätigt, abgelehnt, angekommen, abgeschlossen, nicht erschienen.previous_status
reservation.cancelledVom Gast oder vom Restaurant storniert.–
closure.createdBetriebsferien oder Schließtag eingetragen.–
closure.deletedSchließzeit entfernt.–
So kommt ein Ereignis an
{
    "id": "evt_5c1e9a0b7d2f4e6a8b3c1d0e",
    "event": "reservation.created",
    "created_at": "2026-10-04T14:02:11+02:00",
    "restaurant": {
        "slug": "trattoria-da-luca",
        "name": "Trattoria da Luca"
    },
    "data": {
        "reservation": {
            "id": "K7Q2MZ4P",
            "status": "confirmed",
            "date": "2026-10-17",
            "time": "19:30",
            "party_size": 4,
            "…": "…"
        }
    }
}

POST/webhooksSchreibrecht

Body
{
    "url": "https://example.com/hooks/dinnerspots",
    "events": [
        "reservation.created",
        "reservation.cancelled"
    ]
}

Ohne events oder mit ["*"] bekommst du alle Ereignisse. Die Antwort enthält das secret zum Prüfen der Signatur – nur hier und beim Erneuern, also gleich speichern. Bis zu 5 Webhooks pro Restaurant.

Die Adresse muss mit https:// beginnen, öffentlich erreichbar sein (Port 443 oder 8443) und darf keine Zugangsdaten enthalten. Weiterleitungen folgen wir nicht.

Weitere Endpunkte

GET /webhooksAlle Webhooks mit Zustand (active, failure_count, letzte Erfolge und Fehler).
PATCH /webhooks/{id}url, events oder active ändern.
DELETE /webhooks/{id}Webhook löschen.
POST /webhooks/{id}/testSchickt sofort ein Ereignis webhook.test und liefert das Ergebnis. Höchstens 10 pro Minute.
POST /webhooks/{id}/secretNeues Secret erzeugen. Das alte gilt sofort nicht mehr.
GET /webhooks/{id}/deliveriesDie letzten Zustellungen mit Status (pending, delivered, failed), Versuchen und Antwortcode.

Signatur prüfen

Jede Zustellung hat diese Header:

X-Bontrivo-EventName des Ereignisses.
X-Bontrivo-DeliveryID der Zustellung, z. B. dlv_4711.
X-Bontrivo-TimestampUnix-Zeit in Sekunden, neu bei jedem Versuch.
X-Bontrivo-Signaturev1= und HMAC-SHA256 über Zeitstempel + "." + Body mit deinem Secret, hexadezimal.

Prüfe die Signatur über den unveränderten Body, bevor du ihn verarbeitest. Lehne Zeitstempel ab, die älter als 5 Minuten sind.

PHP
<?php
$secret = getenv('DINNERSPOTS_WEBHOOK_SECRET');
$body = file_get_contents('php://input');
$ts = $_SERVER['HTTP_X_BONTRIVO_TIMESTAMP'] ?? '';
$sig = $_SERVER['HTTP_X_BONTRIVO_SIGNATURE'] ?? '';

$expected = 'v1=' . hash_hmac('sha256', $ts . '.' . $body, $secret);
if (!ctype_digit($ts) || abs(time() - (int)$ts) > 300 || !hash_equals($expected, $sig)) {
    http_response_code(401);
    exit;
}

$event = json_decode($body, true);
// Jedes Ereignis nur einmal verarbeiten: $event['id'] bleibt bei Wiederholungen gleich
http_response_code(200);
Node.js
// Node.js mit Express
const crypto = require('crypto');
const express = require('express');
const app = express();

app.post('/hooks/dinnerspots', express.raw({ type: 'application/json' }), (req, res) => {
  const ts = req.get('X-Bontrivo-Timestamp') || '';
  const sig = Buffer.from(req.get('X-Bontrivo-Signature') || '');
  const expected = Buffer.from('v1=' + crypto
    .createHmac('sha256', process.env.DINNERSPOTS_WEBHOOK_SECRET)
    .update(ts + '.' + req.body)
    .digest('hex'));
  const fresh = Math.abs(Date.now() / 1000 - Number(ts)) <= 300;
  if (!fresh || sig.length !== expected.length || !crypto.timingSafeEqual(sig, expected)) {
    return res.sendStatus(401);
  }
  const event = JSON.parse(req.body);
  // … verarbeiten, dann schnell antworten
  res.sendStatus(200);
});

Zustellung und Wiederholungen

  • Jede Antwort mit Status 200 bis 299 gilt als zugestellt. Antworte innerhalb von 10 Sekunden und erledige längere Arbeit danach.
  • Bei Fehlern oder Timeout versuchen wir es erneut nach 1, 5 und 15 Minuten, dann nach 1, 3, 6, 12 und 24 Stunden.
  • Die id des Ereignisses (evt_…) bleibt bei Wiederholungen gleich. Verarbeite jede id nur einmal.
  • Die Reihenfolge ist nicht garantiert. Im Zweifel gilt updated_at – oder du lädst die Reservierung frisch über die API.
  • Nach 10 endgültig fehlgeschlagenen Zustellungen in Folge pausieren wir den Webhook. Mit PATCH /webhooks/{id} und {"active": true} schaltest du ihn wieder ein.

Beispiel: Chatbot für Instagram, Messenger und WhatsApp

Über die Meta-Plattform (Messenger-API für Instagram und Facebook, WhatsApp Business Platform) empfängt dein Server Nachrichten von Gästen und antwortet darauf. Die DinnerSpots-API liefert die freien Zeiten und nimmt die Reservierung an. So greift beides ineinander:

  1. Der Gast schreibt z. B. „Tisch für 4 am Samstag?“. Meta schickt die Nachricht an deinen Webhook bei Meta.
  2. Dein Bot fragt GET /availability?date=…&party=4 ab und schlägt die freien Zeiten vor. Ist nichts frei, hilft GET /availability/next.
  3. Nach Auswahl der Uhrzeit fragt der Bot nach Namen und Telefonnummer oder E-Mail und legt mit POST /reservations die Reservierung an – mit "channel": "instagram", "facebook" oder "whatsapp" und der Nachrichten-ID als Idempotency-Key.
  4. Kommt 409 slot_unavailable, bietet der Bot die alternatives an.
  5. Über den Webhook reservation.status_changed erfährt der Bot, wenn das Restaurant eine Anfrage bestätigt oder ablehnt, und kann dem Gast antworten.
Node.js
// Ausschnitt: freie Zeiten für den Chat holen (Node.js 18+)
const API = 'https://dinnerspots.de/api/v1';
const headers = { Authorization: 'Bearer ' + process.env.DINNERSPOTS_API_KEY };

async function freieZeiten(datum, personen) {
  const res = await fetch(API + '/availability?date=' + datum + '&party=' + personen, { headers });
  const data = await res.json();
  if (!res.ok) throw new Error(data.error.message);
  if (data.closed) return { text: 'An diesem Tag haben wir leider geschlossen.' };
  if (!data.slots.length) return { text: 'Da ist leider schon alles voll.' };
  return { text: 'Frei sind: ' + data.slots.slice(0, 6).join(', ') + ' Uhr.' };
}

Die Meta-Seite (App, Zugriffstoken, Freigabe durch Meta) richtest du selbst oder mit deinem Dienstleister ein. Gästedaten aus dem Chat landen über die API beim Restaurant – denk an den Hinweis in deiner Datenschutzerklärung.

Fragen?

Schreib uns an [email protected]. Die Übersicht der API bekommst du jederzeit ohne Schlüssel mit GET https://dinnerspots.de/api/v1.