Entwicklerdokumentation

Specials

Die Specials einer Location tageweise lesen – Gerichte und Angebote mit Preis, Wochentagen und Zeitfenster, in der Ortszeit der Location.

Die Specials, die eine Location anbietet, nach Kalendertag gruppiert: ein Gericht, das nur als Special existiert, oder ein Angebot auf eine Position der Speisekarte, jeweils mit Preis, den Wochentagen, an denen es läuft, und einem optionalen Zeitfenster pro Tag. Es erscheinen nur veröffentlichte Specials. Der Lesezugriff folgt denselben Schlüsselregeln wie der Rest von /api/venues/** (Schlüssel und Header), steht jedem Tarif offen und antwortet mit 404 und einem Problem-Body, wenn keine Location diesen Slug hat.

Endpunkte

GET /api/venues/{venueSlug}/specials

Abfrageparameter

Parameter Typ Bedeutung
date yyyy-MM-dd Der erste Tag der Antwort. Optional: ohne Angabe gilt der heutige Tag der Location, in ihrer Zeitzone. Muss zwischen 2000-01-01 und 9998-12-31 liegen
days Ganzzahl Wie viele Tage zurückgegeben werden, date eingeschlossen. Optional, 1 bis 14, Standard 1

Antwort 200 OK

Feld Typ Bedeutung
slug string Die Location
timezone string IANA-Kennung der Zeitzone der Location; alle Daten und Uhrzeiten unten sind ihre Ortszeit
currency string ISO-4217-Währung aller Preise
days[] object[] Ein Eintrag je angefragtem Tag, in Reihenfolge, mit date und specials[]; die Liste kann leer sein

Jeder Eintrag in specials[]:

Feld Typ Bedeutung
id string Die Kennung des Specials
kind string Dish (ein Gericht mit eigenem Preis) oder MenuItem (eine Überschrift über einer vorhandenen Karten-Position)
title, description string Die Überschrift und eine optionale Beschreibung (nullable)
imageUrl string Das eigene Bild des Specials, sonst das der verknüpften Position (nullable)
price decimal Was der Gast zahlt
regularPrice decimal Der reguläre Preis der Position, nur vorhanden, wenn der Special-Preis niedriger ist, für eine Durchstreichung (nullable)
menuItemId, itemName, itemDescription string Die verknüpfte Karten-Position, aufgelöst (nullable; bei einem Gericht null)
tags string[] Die Tags der Position, etwa Ernährungshinweise; bei einem Gericht leer
windowStart, windowEnd string Das Zeitfenster pro Tag als HH:mm; beide null heißt ganztägig
endsNextDay boolean true, wenn das Fenster nach Mitternacht endet, etwa 22:00 bis 02:00
startsOn, endsOn string Erster und letzter Tag, an dem es läuft, als yyyy-MM-dd; null heißt keine Grenze auf dieser Seite
weekdays string[] Die Wochentage, an denen es läuft, etwa "Wednesday"; leer heißt jeden Tag
occurrenceDate string Der Tag, an dem dieses Vorkommen begann: der aufgeführte Tag oder der Vortag
continuesFromPreviousDay boolean true für das späte Fenster von gestern, das um Mitternacht noch läuft

Innerhalb eines Tages ist die Liste geordnet: zuerst die vom Vortag übernommenen Fenster, dann die Sortierung des Betreibers, dann ganztägige Specials vor solchen mit Zeitfenster, dann der Fensterbeginn.

Zeitzone

Alle Daten und Uhrzeiten sind Ortszeit der Location, in der timezone, die die Antwort nennt. Rechnen Sie auf Ihrer Seite um, nie umgekehrt.

  • Ein Fenster gehört zu dem Tag, an dem es beginnt. 22:00 bis 02:00 am Freitag ist das Special des Freitags und hat endsNextDay: true.
  • Die Liste jedes Tages beginnt mit den späten Fenstern des Vortags, die um Mitternacht noch laufen. Sie sind mit continuesFromPreviousDay: true markiert und tragen ihr occurrenceDate; es genügt also, am Morgen danach nur den heutigen Tag abzufragen.
  • Ein Special läuft gerade, wenn die Uhren der Location eine Zeit innerhalb des Fensters zeigen, gezählt ab seinem occurrenceDate. Vergleichen Sie mit der Ortszeit der Location, nicht mit der des Gastes.
  • In einer Nacht der Zeitumstellung kann ein Fenster eine Stunde kürzer oder länger sein. Eines, das in der wiederholten Herbststunde endet, schaltet sich aus und wieder ein.

Specials auf Karten-Positionen

Ein Special der Art MenuItem verweist über menuItemId auf eine Position der Speisekarten der Location (Speisekarten). Die Antwort löst sie auf: itemName, itemDescription und tags stammen von der Position, price ist der Special-Preis (oder der eigene Preis der Position, wenn das Special keinen setzt), und regularPrice wird nur gesendet, wenn der Special-Preis niedriger ist. Löscht der Betreiber die Position, verschwindet das Special aus dieser Antwort, statt einen ins Leere zeigenden Verweis zu zeigen.

Eine Antwort für demo-sportsbar kann so aussehen. Die Daten sind nur ein Beispiel: Die Specials der Demo-Location rollen weiter, die Datumsgrenzen, die Sie zurückbekommen, weichen also ab.

{
  "slug": "demo-sportsbar",
  "timezone": "Europe/Berlin",
  "currency": "EUR",
  "days": [
    {
      "date": "2026-10-28",
      "specials": [
        {
          "id": "5be0a4c1e2f84d5f9a6e0c1d2b3a4f51",
          "kind": "Dish",
          "title": "Lunch special",
          "description": "Soup of the day, burger or salad bowl, with a soft drink",
          "imageUrl": null,
          "price": 9.90,
          "regularPrice": null,
          "menuItemId": null,
          "itemName": null,
          "itemDescription": null,
          "tags": [],
          "windowStart": "11:30",
          "windowEnd": "14:00",
          "endsNextDay": false,
          "startsOn": null,
          "endsOn": null,
          "weekdays": [
            "Monday",
            "Tuesday",
            "Wednesday",
            "Thursday",
            "Friday"
          ],
          "occurrenceDate": "2026-10-28",
          "continuesFromPreviousDay": false
        },
        {
          "id": "5be0a4c1e2f84d5f9a6e0c1d2b3a4f52",
          "kind": "MenuItem",
          "title": "Wing Wednesday",
          "description": null,
          "imageUrl": "_content/BookDinePlay.App.Shared/menu/wings.webp",
          "price": 8.00,
          "regularPrice": 11.00,
          "menuItemId": "af1cc7fd0772f60da833b1612f46fa69",
          "itemName": "Buffalo wings",
          "itemDescription": "Blue-cheese dip, celery",
          "tags": [],
          "windowStart": "17:00",
          "windowEnd": "22:00",
          "endsNextDay": false,
          "startsOn": null,
          "endsOn": null,
          "weekdays": [
            "Wednesday"
          ],
          "occurrenceDate": "2026-10-28",
          "continuesFromPreviousDay": false
        },
        {
          "id": "5be0a4c1e2f84d5f9a6e0c1d2b3a4f53",
          "kind": "Dish",
          "title": "Burger of the week: smoky BBQ",
          "description": null,
          "imageUrl": null,
          "price": 14.50,
          "regularPrice": null,
          "menuItemId": null,
          "itemName": null,
          "itemDescription": null,
          "tags": [],
          "windowStart": null,
          "windowEnd": null,
          "endsNextDay": false,
          "startsOn": "2026-10-26",
          "endsOn": "2026-11-01",
          "weekdays": [],
          "occurrenceDate": "2026-10-28",
          "continuesFromPreviousDay": false
        }
      ]
    }
  ]
}

Fehler

  • 400 – ein Validierungsproblem unter errors.date, wenn date kein Datum im Format yyyy-MM-dd ist, außerhalb von 2000-01-01 bis 9998-12-31 liegt; unter errors.days, wenn days außerhalb von 1 bis 14 liegt. Sind beide falsch, werden beide gemeldet.
  • 401 – kein Schlüssel oder ein unbekannter.
  • 403 – der Schlüssel gehört zu einer anderen Location, ein veröffentlichbarer Schlüssel kam von einer Origin, die nicht auf seiner Liste steht, oder ein veröffentlichbarer Schlüssel wurde ohne Origin-Header gesendet. Siehe Was die API ablehnt.
  • 404 – keine Location mit diesem Slug.

curl

curl "https://api.bookdineplay.com/api/venues/your-venue/specials?date=2026-10-28" \
  -H "X-BookDinePlay-Key: bdp_pk_your_publishable_key" \
  -H "Origin: https://www.your-venue.example"

JavaScript

const client = BookDinePlay.createClient({
  apiBaseUrl: 'https://api.bookdineplay.com', venueSlug: 'your-venue', publishableKey: 'bdp_pk_your_publishable_key'
});
const response = await client.specials({ date: '2026-10-28', days: 1 }); // null, wenn die Location keine Specials anbietet
for (const day of response?.days ?? []) for (const special of day.specials) {
  console.log(day.date, special.title, special.price, response.timezone);
}

C#

// `client` ist der IBookDinePlayClient aus AddBookDinePlayClient, registriert mit dem GEHEIMEN Schlüssel der Location
// aus der Konfiguration (builder.Configuration["BookDinePlay:ApiKey"]). Ein Server sendet keinen Origin-Header.
var response = await client.GetSpecialsAsync("your-venue", new DateOnly(2026, 10, 28), days: 1, cancellationToken);
foreach (var day in response?.Days ?? [])
{
    foreach (var special in day.Specials)
    {
        Console.WriteLine($"{day.Date} {special.Title} {special.Price} ({response!.Timezone})");
    }
}

Das JavaScript-Beispiel nutzt den veröffentlichbaren Schlüssel. Das C#-Beispiel läuft auf einem Server und nutzt deshalb einen geheimen Schlüssel (bdp_sk_…) aus der Konfiguration und sendet keinen Origin-Header; liefern Sie einen geheimen Schlüssel nie an einen Browser aus. client.specials und GetSpecialsAsync antworten bei einer unbekannten Location mit null, der reine HTTP-Aufruf mit 404. renderSpecialsWidget zeichnet dieselben Daten ohne eigenen Code.

Betreiber: Specials speichern

Betreiber verwalten Specials über die Betreiber-API (Bearer-Token des Betreibers der Location), nicht über die öffentliche Oberfläche /api/venues/**. Senden Sie bei einem PUT immer kind und status: Er ersetzt den Datensatz vollständig, ein fehlender status wird zu Draft und nimmt das Special aus der Veröffentlichung, ein fehlendes kind wird zu Dish. Dasselbe gilt für die Datumsgrenzen: Lassen Sie endsOn (oder startsOn) weg, entfällt diese Grenze und das Special läuft unbegrenzt.

Nächste Schritte

  • Venue – die anderen sechs Lesezugriffe für diese Location.
  • Speisekarten – die Positionen, auf die ein Special der Art MenuItem verweist.