Anmelden um den Slug und den veröffentlichbaren Schlüssel Ihrer Location in jedem Beispiel zu sehen.
Ihr Konto hat noch keine Location, die Beispiele behalten daher ihre Platzhalter. Abmelden
Angemeldet als · . Legen Sie in der Konsole einen veröffentlichbaren Schlüssel an und laden Sie die Seite neu, um ihn hier zu sehen. Abmelden
Angemeldet als · . Die Beispiele zeigen den veröffentlichbaren Schlüssel Ihrer Location. Abmelden
Entwicklerdokumentation
QR-Codes und Tischsitzungen
QR-Token zu seinem Tisch auflösen, sehen, was der Gast bestellen kann und auf der Rechnung hat, bestellen und die Rechnung schließen – ohne API-Schlüssel.
Jeder Tisch, jede Bahn, jede Scheibe kann einen gedruckten QR-Code tragen. Wer ihn scannt, öffnet eine Seite mit einem Token in der URL; dieses Token ist das Zugangsmerkmal für alles auf dieser Seite – es identifiziert Location und Ressource, lässt sich in der Konsole widerrufen und neu drucken und braucht keinen zusätzlichen API-Schlüssel. Eine Tischsitzung ist die offene Rechnung des Gastes an dieser Ressource: Sie beginnt mit der ersten Bestellung und endet, wenn die Rechnung geschlossen wird.
Endpunkte
GET /api/qr/{token}
Worauf der QR-Code zeigt – genug, um „Tisch 7 im The Neon Tap“ anzuzeigen, bevor der Gast etwas tut.
Antwort 200 OK
| Feld | Typ | Bedeutung |
|---|---|---|
venueSlug, venueName |
string | Die Location, für die weiteren Aufrufe und zur Anzeige |
resourceCode, resourceName |
string | Die Ressource (resourceCode ist ihre id in den Ressourcen der Location) |
resourceType |
string | Einer der Ressourcentypen |
{
"venueSlug": "demo-sportsbar",
"venueName": "The Neon Tap",
"resourceCode": "table-4a",
"resourceName": "Table 4",
"resourceType": "RestaurantTable"
}Fehler – 404 das Token ist unbekannt, widerrufen oder seine Location ist nicht mehr für QR-Codes berechtigt.
curl
curl "https://api.bookdineplay.com/api/qr/<token>"JavaScript
const token = new URL(location.href).searchParams.get('t');
const table = await (await fetch(`https://api.bookdineplay.com/api/qr/${encodeURIComponent(token)}`)).json();
document.title = `${table.resourceName} · ${table.venueName}`;C#
var table = await client.ResolveQrTokenAsync(token, cancellationToken);GET /api/qr/{token}/session
Die Sicht des Gastes auf den Tisch: die Speisekarten, aus denen er hier bestellen darf, und die laufende Rechnung. Vor der ersten Bestellung ist die Sitzung noch nicht offen – isOpen ist false und tab leer –, so entscheidet eine Seite auch, ob sie „Bestellen“ oder „Ihre Rechnung“ zeigt.
Antwort 200 OK
| Feld | Typ | Bedeutung |
|---|---|---|
resourceCode, resourceName, resourceType |
string | Der Tisch |
isOpen |
boolean | Ob eine Rechnung offen ist |
currency |
string | Währung jedes Betrags darunter |
menus[] |
object[] | Bestellbare Karten für diese Ressource: id, name, kind (Food oder Drinks), groups[] → items[] mit name, description, price, currency, taxRate (nullable), imageUrl (nullable). Ein Billardtisch bekommt ggf. nur die Getränkekarte, ein Restauranttisch beide |
tab[] |
object[] | Zusammengefasste Positionen über alle Bestellungen der Sitzung: itemName, quantity, unitPrice, lineTotal |
runningTotal |
decimal | Summe der Rechnung |
paymentPending, paymentUrl |
boolean, string oder null | Eine Online-Zahlung dieser Rechnung läuft, und der Checkout, den sie geöffnet hat – siehe POST …/payment |
voucherCredits |
object[] oder null | Geschenkgutscheine, die das Personal am Tisch auf diese Rechnung angewendet hat: label und amount. Null, wenn es keine gibt |
amountDue |
decimal oder null | runningTotal abzüglich voucherCredits, nie unter null – was noch zu zahlen ist. Null, wenn kein Gutschein angewendet wurde; dann ist runningTotal fällig |
{
"resourceCode": "table-4a",
"resourceName": "Table 4",
"resourceType": "RestaurantTable",
"isOpen": false,
"currency": "EUR",
"menus": [
{
"id": "kitchen",
"name": "Kitchen",
"kind": "Food",
"groups": [
{
"name": "Small plates",
"description": null,
"items": [
{
"name": "Loaded nachos",
"description": "Melted cheese, jalapeños, lime crema",
"price": 9.50,
"currency": "EUR",
"taxRate": 0.19,
"imageUrl": "_content/BookDinePlay.App.Shared/menu/nachos.webp"
},
{
"name": "Buffalo wings",
"description": "Blue-cheese dip, celery",
"price": 11.00,
"currency": "EUR",
"taxRate": 0.19,
"imageUrl": "_content/BookDinePlay.App.Shared/menu/wings.webp"
},
{
"name": "Padrón peppers",
"description": "Sea salt, olive oil",
"price": 7.00,
"currency": "EUR",
"taxRate": 0.19,
"imageUrl": "_content/BookDinePlay.App.Shared/menu/padron-peppers.webp"
}
]
},
{
"name": "Mains",
"description": null,
"items": [
{
"name": "Smash burger",
"description": "Double patty, house sauce, fries",
"price": 14.50,
"currency": "EUR",
"taxRate": 0.19,
"imageUrl": "_content/BookDinePlay.App.Shared/menu/smash-burger.webp"
},
{
"name": "BBQ ribs",
"description": "Slow-cooked, slaw, fries",
"price": 19.00,
"currency": "EUR",
"taxRate": 0.19,
"imageUrl": "_content/BookDinePlay.App.Shared/menu/bbq-ribs.webp"
},
{
"name": "Halloumi flatbread",
"description": "Grilled halloumi, harissa, greens",
"price": 13.00,
"currency": "EUR",
"taxRate": 0.19,
"imageUrl": "_content/BookDinePlay.App.Shared/menu/halloumi-flatbread.webp"
}
]
},
{
"name": "Sweet",
"description": null,
"items": [
{
"name": "Churros",
"description": "Cinnamon sugar, chocolate",
"price": 7.00,
"currency": "EUR",
"taxRate": 0.19,
"imageUrl": "_content/BookDinePlay.App.Shared/menu/churros.webp"
}
]
}
]
},
{
"id": "bar",
"name": "Bar",
"kind": "Drinks",
"groups": [
{
"name": "Draft beer",
"description": null,
"items": [
{
"name": "House lager 0.3L",
"description": "Crisp pilsner",
"price": 4.50,
"currency": "EUR",
"taxRate": 0.19,
"imageUrl": "_content/BookDinePlay.App.Shared/menu/lager.webp"
},
{
"name": "Local IPA 0.5L",
"description": "Hoppy, citrus finish",
"price": 6.50,
"currency": "EUR",
"taxRate": 0.19,
"imageUrl": "_content/BookDinePlay.App.Shared/menu/ipa.webp"
}
]
},
{
"name": "Cocktails",
"description": null,
"items": [
{
"name": "Old Fashioned",
"description": "Bourbon, bitters, orange",
"price": 11.00,
"currency": "EUR",
"taxRate": 0.19,
"imageUrl": "_content/BookDinePlay.App.Shared/menu/old-fashioned.webp"
},
{
"name": "Spicy margarita",
"description": "Tequila, chilli, lime",
"price": 10.50,
"currency": "EUR",
"taxRate": 0.19,
"imageUrl": "_content/BookDinePlay.App.Shared/menu/margarita.webp"
}
]
},
{
"name": "Zero proof",
"description": null,
"items": [
{
"name": "Craft soda",
"description": "House-made, ask for today's flavour",
"price": 4.00,
"currency": "EUR",
"taxRate": 0.19,
"imageUrl": "_content/BookDinePlay.App.Shared/menu/craft-soda.webp"
}
]
}
]
}
],
"tab": [],
"runningTotal": 0
}Listen im Beispiel sind auf drei Einträge gekürzt.
Fehler – 404 Token nicht aktiv.
curl
curl "https://api.bookdineplay.com/api/qr/<token>/session"JavaScript
const session = await (await fetch(`https://api.bookdineplay.com/api/qr/${encodeURIComponent(token)}/session`)).json();
const drinks = session.menus.find(m => m.kind === 'Drinks');C#
var session = await client.GetTableSessionAsync(token, cancellationToken);POST /api/qr/{token}/orders
Eine Bestellung aufgeben: eine oder mehrere Positionen, jede mit einer Karte per id und einer Position per name, wie sie in den menus der Sitzung stehen. Die erste Bestellung öffnet die Sitzung; jede Bestellung hält den Preis zum Bestellzeitpunkt auf der Rechnung fest. Die Antwort ist die aktualisierte Sitzung.
Request-Body (Content-Type: application/json)
| Feld | Erforderlich | Bedeutung |
|---|---|---|
lines[] |
ja, mindestens eine | menuId (eine Karten-id aus der Sitzung), itemName (ein Positions-name in dieser Karte), quantity (positiv) |
Antwort 200 OK – dieselbe Struktur wie GET …/session, jetzt mit den neuen Positionen auf tab.
{
"resourceCode": "table-4a",
"resourceName": "Table 4",
"resourceType": "RestaurantTable",
"isOpen": true,
"currency": "EUR",
"menus": [
{
"id": "kitchen",
"name": "Kitchen",
"kind": "Food",
"groups": [
{
"name": "Small plates",
"description": null,
"items": [
{
"name": "Loaded nachos",
"description": "Melted cheese, jalapeños, lime crema",
"price": 9.50,
"currency": "EUR",
"taxRate": 0.19,
"imageUrl": "_content/BookDinePlay.App.Shared/menu/nachos.webp"
},
{
"name": "Buffalo wings",
"description": "Blue-cheese dip, celery",
"price": 11.00,
"currency": "EUR",
"taxRate": 0.19,
"imageUrl": "_content/BookDinePlay.App.Shared/menu/wings.webp"
},
{
"name": "Padrón peppers",
"description": "Sea salt, olive oil",
"price": 7.00,
"currency": "EUR",
"taxRate": 0.19,
"imageUrl": "_content/BookDinePlay.App.Shared/menu/padron-peppers.webp"
}
]
},
{
"name": "Mains",
"description": null,
"items": [
{
"name": "Smash burger",
"description": "Double patty, house sauce, fries",
"price": 14.50,
"currency": "EUR",
"taxRate": 0.19,
"imageUrl": "_content/BookDinePlay.App.Shared/menu/smash-burger.webp"
},
{
"name": "BBQ ribs",
"description": "Slow-cooked, slaw, fries",
"price": 19.00,
"currency": "EUR",
"taxRate": 0.19,
"imageUrl": "_content/BookDinePlay.App.Shared/menu/bbq-ribs.webp"
},
{
"name": "Halloumi flatbread",
"description": "Grilled halloumi, harissa, greens",
"price": 13.00,
"currency": "EUR",
"taxRate": 0.19,
"imageUrl": "_content/BookDinePlay.App.Shared/menu/halloumi-flatbread.webp"
}
]
},
{
"name": "Sweet",
"description": null,
"items": [
{
"name": "Churros",
"description": "Cinnamon sugar, chocolate",
"price": 7.00,
"currency": "EUR",
"taxRate": 0.19,
"imageUrl": "_content/BookDinePlay.App.Shared/menu/churros.webp"
}
]
}
]
},
{
"id": "bar",
"name": "Bar",
"kind": "Drinks",
"groups": [
{
"name": "Draft beer",
"description": null,
"items": [
{
"name": "House lager 0.3L",
"description": "Crisp pilsner",
"price": 4.50,
"currency": "EUR",
"taxRate": 0.19,
"imageUrl": "_content/BookDinePlay.App.Shared/menu/lager.webp"
},
{
"name": "Local IPA 0.5L",
"description": "Hoppy, citrus finish",
"price": 6.50,
"currency": "EUR",
"taxRate": 0.19,
"imageUrl": "_content/BookDinePlay.App.Shared/menu/ipa.webp"
}
]
},
{
"name": "Cocktails",
"description": null,
"items": [
{
"name": "Old Fashioned",
"description": "Bourbon, bitters, orange",
"price": 11.00,
"currency": "EUR",
"taxRate": 0.19,
"imageUrl": "_content/BookDinePlay.App.Shared/menu/old-fashioned.webp"
},
{
"name": "Spicy margarita",
"description": "Tequila, chilli, lime",
"price": 10.50,
"currency": "EUR",
"taxRate": 0.19,
"imageUrl": "_content/BookDinePlay.App.Shared/menu/margarita.webp"
}
]
},
{
"name": "Zero proof",
"description": null,
"items": [
{
"name": "Craft soda",
"description": "House-made, ask for today's flavour",
"price": 4.00,
"currency": "EUR",
"taxRate": 0.19,
"imageUrl": "_content/BookDinePlay.App.Shared/menu/craft-soda.webp"
}
]
}
]
}
],
"tab": [
{
"itemName": "Loaded nachos",
"quantity": 2,
"unitPrice": 9.50,
"lineTotal": 19.00
},
{
"itemName": "House lager 0.3L",
"quantity": 1,
"unitPrice": 4.50,
"lineTotal": 4.50
}
],
"runningTotal": 23.50
}Fehler
| Status | Wann |
|---|---|
400 |
lines fehlt oder ist leer (errors.lines); oder eine Position nennt eine Karte oder Position, die an dieser Ressource nicht bestellbar ist – nichts wird aufgegeben |
404 |
Token nicht aktiv |
409 |
Die Rechnung wurde zwischen dem Lesen des Gastes und dieser Bestellung geschlossen. Sitzung neu lesen; die nächste Bestellung öffnet eine neue Rechnung |
curl
curl -X POST "https://api.bookdineplay.com/api/qr/<token>/orders" \
-H "Content-Type: application/json" \
-d '{ "lines": [ { "menuId": "kitchen", "itemName": "Smash burger", "quantity": 2 }, { "menuId": "bar", "itemName": "House lager", "quantity": 1 } ] }'JavaScript
const updated = await (await fetch(`https://api.bookdineplay.com/api/qr/${encodeURIComponent(token)}/orders`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ lines: [{ menuId: 'kitchen', itemName: 'Smash burger', quantity: 2 }] })
})).json();
console.log(updated.runningTotal, updated.currency);C#
var updated = await client.PlaceOrderAsync(token, new PlaceOrderRequest([
new OrderLineRequest("kitchen", "Smash burger", 2),
new OrderLineRequest("bar", "House lager", 1)
]), cancellationToken);Die Positionsnamen in den Beispielen sind illustrativ – verwenden Sie die Namen aus den menus der Sitzung.
POST /api/qr/{token}/close
Die Rechnung schließen. Die Antwort ist die Rechnung: jede Position, die Zwischensumme, die darin enthaltene Steuer und die Gesamtsumme. Die Sitzung endet; die nächste Bestellung des Gastes beginnt eine neue.
Antwort 200 OK
| Feld | Typ | Bedeutung |
|---|---|---|
lines[] |
object[] | itemName, quantity, lineTotal |
subtotal, taxTotal, total |
decimal | Preise verstehen sich inklusive Steuer: total ist, was der Gast zahlt, taxTotal die darin enthaltene Steuer |
currency |
string | |
issuedAt |
ISO-8601-Zeitpunkt | Wann die Rechnung geschlossen wurde |
{
"lines": [
{
"itemName": "Loaded nachos",
"quantity": 2,
"lineTotal": 19.00
},
{
"itemName": "House lager 0.3L",
"quantity": 1,
"lineTotal": 4.50
}
],
"subtotal": 19.75,
"taxTotal": 3.75,
"total": 23.50,
"currency": "EUR",
"issuedAt": "2026-09-15T12:55:15.3387817+00:00"
}Fehler – 404 Token nicht aktiv oder keine offene Sitzung zum Schließen.
curl
curl -X POST "https://api.bookdineplay.com/api/qr/<token>/close"JavaScript
const invoice = await (await fetch(`https://api.bookdineplay.com/api/qr/${encodeURIComponent(token)}/close`, { method: 'POST' })).json();
console.log(invoice.total, invoice.currency);C#
var invoice = await client.CloseTableSessionAsync(token, cancellationToken);POST /api/qr/{token}/payment
Lässt den Gast die Rechnung per Karte bezahlen statt an der Bar. Die Rechnung wird 31 Minuten lang auf ihre aktuelle Summe eingefroren und eine Stripe-Checkout-Sitzung auf dem Stripe-Konto der Location eröffnet; schicken Sie den Gast zu checkoutUrl. Solange das Einfrieren gilt, wird POST …/orders mit 409 abgelehnt – die abzubuchende Summe steht bereits fest – und GET …/session meldet paymentPending: true mit derselben paymentUrl, sodass ein zweites Gerät am Tisch die laufende Zahlung sieht statt eine weitere zu starten.
Ein erneuter Aufruf während einer laufenden Zahlung gibt dieselbe Checkout-Sitzung zurück, keine zweite. Die Rechnung selbst ist der Anspruch, das Telefon des Gastes braucht also keinen Idempotency-Key-Header. Bricht der Gast den Checkout ab, läuft das Einfrieren von selbst aus und die Rechnung nimmt wieder Bestellungen an.
Ist die Zahlung abgeschlossen, wird die Rechnung automatisch geschlossen – POST …/close muss nicht zusätzlich aufgerufen werden.
Dieser Aufruf braucht wie der Rest dieser Seite keinen API-Schlüssel. Im Unterschied dazu setzt er aber voraus, dass der Tarif der Location Online-Zahlungen umfasst, Zahlungen eingeschaltet sind und das Stripe-Auszahlungs-Onboarding abgeschlossen ist; bezahlen an der Bar ist immer möglich.
Ein Geschenkgutschein. Der Body ist optional. Senden Sie { "voucherCode": "…" }, und ein Betrags- oder Prozentgutschein dieser Location bezahlt, was er kann; die Karte bezahlt den Rest. Die Rechnungssumme ist, was nach einem vom Personal bereits angewendeten Gutschein noch offen ist, und ein Prozentgutschein ist ein Rabatt auf die ganze Rechnung, begrenzt auf den offenen Betrag. Der Wert des Gutscheins wird zusammen mit dem Einfrieren der Rechnung reserviert und abgebucht, wenn die Zahlung eintrifft; bricht der Gast ab, verfällt beides. Der Kartenteil beträgt mindestens 0,50 € (Mit einem Gutschein bezahlen). Ein Gutschein, der alles deckt, braucht keine Karte, also gilt keine der Kartenbedingungen oben: Die Rechnung ist sofort bezahlt und geschlossen, paidInFull ist true, und checkoutUrl ist die Rechnungsseite des Gasts. Eine Erstattung im Stripe-Dashboard der Location für eine teilweise per Gutschein bezahlte Rechnung bucht den Gutscheinteil auf den Gutschein zurück.
Antwort 200 OK
| Feld | Typ | Bedeutung |
|---|---|---|
checkoutUrl |
string | Stripe-Checkout-URL – leiten Sie den Gast hierhin weiter |
amount |
decimal | Die eingefrorene Rechnungssumme, inkl. Steuer |
currency |
string | |
expiresAt |
ISO-8601-Zeitpunkt | Wann das Einfrieren ausläuft und der Checkout nicht mehr bezahlbar ist |
voucherAmount |
decimal oder null | Was der Gutschein bezahlt; null ohne Gutschein. amount ist dann, was die Karte bezahlt |
paidInFull |
boolean | Der Gutschein hat alles bezahlt: Es wurde keine Stripe-Sitzung geöffnet, und die Rechnung ist schon geschlossen |
{
"checkoutUrl": "https://checkout.stripe.com/c/pay/cs_test_a1b2c3",
"amount": 23.50,
"currency": "EUR",
"expiresAt": "2026-09-15T13:26:15.3387817+00:00"
}Ein Gutschein, der die ganze Rechnung bezahlt hat:
{
"checkoutUrl": "https://app.bookdineplay.com/t/table-4a-token?paid=1",
"amount": 0.00,
"currency": "EUR",
"expiresAt": "2026-10-09T13:26:15.3387817+00:00",
"voucherAmount": 23.50,
"paidInFull": true
}Fehler
| Status | Wann |
|---|---|
404 |
Token nicht aktiv |
409 |
Nichts auf der Rechnung zu bezahlen; die Rechnung ist bereits bezahlt; eine Zahlung läuft schon, ihr Checkout konnte aber nicht zurückgegeben werden; oder die Location kann Rechnungen gerade nicht per Karte annehmen |
404, 409 |
Mit einem Gutschein: eines der Gutschein-Probleme oder 409 mit reason below-card-minimum, wenn die Rechnung 0,50 € oder weniger beträgt und der Gutschein sie nicht deckt |
curl
curl -X POST "https://api.bookdineplay.com/api/qr/<token>/payment" \
-H "Content-Type: application/json" \
-d '{ "voucherCode": "K7QM-2XRP-9DTA-HV3N-8W4F" }'JavaScript
const payment = await (await fetch(`https://api.bookdineplay.com/api/qr/${encodeURIComponent(token)}/payment`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ voucherCode }) // oder ganz ohne Body, um nur per Karte zu zahlen
})).json();
location.href = payment.checkoutUrl; // bei paidInFull die RechnungsseiteC#
var payment = await client.StartTabPaymentAsync(token, voucherCode, cancellationToken); // voucherCode darf null seinNächste Schritte
- Speisekarten und Ressourcen – die Anzeige-Karten und welche Ressourcen Bestellungen erlauben.
- .NET-SDK – die fünf Aufrufe als typisierte Methoden;
PlaceOrderAsyncundStartTabPaymentAsyncgeben bei400/404nullzurück und werfen bei409BookDinePlayApiException.