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"
}

Fehler404 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
{
  "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.

Fehler404 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"
}

Fehler404 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);

Nächste Schritte

  • Venue – die Anzeige-Karten und welche Ressourcen Bestellungen erlauben.
  • .NET-SDK – die vier Aufrufe als typisierte Methoden; PlaceOrderAsync gibt bei 400/404 null zurück statt zu werfen.