Sign in to see your venue's slug and publishable key in every sample.
Your account has no venue yet, so the samples keep their placeholders. Sign out
Signed in as · . Create a publishable key in the console and reload to see it here. Sign out
Signed in as · . The samples show your venue's publishable key. Sign out
Developer docs
Specials
Read the specials a venue runs, day by day — dishes and menu-item deals with their price, weekdays and daily time window, in venue-local time.
The specials a venue runs, grouped by calendar day: a dish that exists only as a special, or a deal on an item from the menu, each with its price, the weekdays it runs on and an optional daily time window. Only published specials appear. It follows the same key rules as the rest of /api/venues/** (Keys and headers), is open to every plan, and answers 404 with a problem body when no venue has that slug.
Endpoints
GET /api/venues/{venueSlug}/specials
Query parameters
| Parameter | Type | Meaning |
|---|---|---|
date |
yyyy-MM-dd |
The first day to return. Optional: omitted means the venue's today, in the venue's time zone. Must lie between 2000-01-01 and 9998-12-31 |
days |
integer | How many days to return, counting date itself. Optional, 1 to 14, default 1 |
Response 200 OK
| Field | Type | Meaning |
|---|---|---|
slug |
string | The venue |
timezone |
string | IANA id of the venue's time zone; every date and time below is local to it |
currency |
string | ISO 4217 currency of every price |
days[] |
object[] | One entry per requested day, in order, with date and specials[]; the list can be empty |
Each entry in specials[]:
| Field | Type | Meaning |
|---|---|---|
id |
string | The special's id |
kind |
string | Dish (a dish with its own price) or MenuItem (a headline over an existing menu item) |
title, description |
string | The headline and an optional description (nullable) |
imageUrl |
string | The special's own picture, else the referenced item's (nullable) |
price |
decimal | What the guest pays |
regularPrice |
decimal | The item's regular price, present only when the special price is lower, for a strike-through (nullable) |
menuItemId, itemName, itemDescription |
string | The referenced menu item, resolved (nullable; null for a dish) |
tags |
string[] | The item's tags, for example dietary labels; empty for a dish |
windowStart, windowEnd |
string | The daily window as HH:mm; both null means all day |
endsNextDay |
boolean | true when the window ends after midnight, for example 22:00 to 02:00 |
startsOn, endsOn |
string | First and last day it runs as yyyy-MM-dd; null means no bound on that side |
weekdays |
string[] | The days of the week it runs on, such as "Wednesday"; empty means every day |
occurrenceDate |
string | The day this occurrence started: the listed day, or the day before |
continuesFromPreviousDay |
boolean | true for yesterday's late window that is still running at midnight |
Within a day the list is ordered: windows carried over from the previous day first, then the operator's sort order, then all-day specials before windowed ones, then window start.
Time zone
All dates and times are venue-local, in the timezone the response names. Convert on your side, never the other way round.
- A window belongs to the day it starts.
22:00to02:00on Friday is Friday's special and hasendsNextDay: true. - Each day's list begins with the previous day's late windows that are still running at midnight. They are marked
continuesFromPreviousDay: trueand carry theiroccurrenceDate, so asking for today alone is enough the morning after. - A special is on now when the venue's clocks show a time inside its window, counted from its
occurrenceDate. Compare against the venue's local time, not the guest's. - On a daylight-saving night a window can be an hour shorter or longer. One that ends inside the repeated autumn hour switches off and on again.
Menu-item specials
A special of kind MenuItem points at an item of the venue's menus (Menus) by menuItemId. The response resolves it: itemName, itemDescription and tags come from the item, price is the special price (or the item's own price when the special sets none), and regularPrice is sent only when the special price is lower. If the operator deletes the item, the special disappears from this response rather than showing a dangling reference.
A response for demo-sportsbar could look like this. The dates are only for example: the demo venue's specials roll forward, so the date bounds you get back will differ.
{
"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
}
]
}
]
}Errors
400— a validation problem undererrors.datewhendateis not ayyyy-MM-dddate, or lies outside 2000-01-01 to 9998-12-31; undererrors.dayswhendaysis outside 1 to 14. Both are reported together when both are wrong.401— no key, or an unknown one.403— the key belongs to another venue, a publishable key was sent from an origin that is not on its allowlist, or a publishable key was sent without anOriginheader. See What the API refuses.404— no venue with that 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 when the venue offers no specials
for (const day of response?.days ?? []) for (const special of day.specials) {
console.log(day.date, special.title, special.price, response.timezone);
}C#
// `client` is the IBookDinePlayClient from AddBookDinePlayClient, registered with the venue's SECRET key
// read from configuration (builder.Configuration["BookDinePlay:ApiKey"]). A server sends no 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})");
}
}The JavaScript sample uses the publishable key. The C# sample runs on a server, so it uses a secret key (bdp_sk_…) read from configuration and sends no Origin header; never ship a secret key to a browser. client.specials and GetSpecialsAsync both answer null for an unknown venue, where the plain HTTP call is a 404. renderSpecialsWidget draws the same data without any code of your own.
Operator: saving specials
Operators manage specials through the operator API (bearer token of the venue's operator), not through the public /api/venues/** surface. Always send kind and status on a PUT: it is a full replace, so an omitted status binds to Draft and unpublishes the special, and an omitted kind binds to Dish. The same goes for the date bounds: omitting endsOn (or startsOn) removes that bound and makes the special open-ended.