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
Venue
Read a venue's profile, business information, opening hours, menus, bookable resources and floor plan — the six GETs every integration starts with.
Six reads describe a venue completely. They are cheap, cacheable on your side, and change only when the operator edits something in the console. All of them take the venue's slug in the path, need a key (Keys and headers), and answer 404 with a problem body when no venue has that slug.
Endpoints
GET /api/venues/{venueSlug}
The venue's public profile: name, description, location, time zone, which resource types it offers, and — when the operator has set them up — paid extras and the booking durations a guest may choose per resource type.
Response 200 OK
| Field | Type | Meaning |
|---|---|---|
slug |
string | The identifier you used in the URL |
name, tagline, description |
string | Display text, in the venue's own language |
city, country |
string | Location as shown to guests |
timezone |
string | IANA time zone every date and time on this venue is in |
resourceTypes |
string[] | The types this venue offers: RestaurantTable, BilliardTable, DartBoard, Shuffleboard, BowlingLane, EventArea |
extras |
object[] or null | Paid add-ons: id, name, description, price, currency, basis (PerPerson or PerBooking), appliesTo (resource types) |
bookingDurations |
object[] or null | Per type: type, defaultMinutes, minMinutes, maxMinutes, stepMinutes, guestSelectable, options (the selectable minutes, ready for a picker) |
{
"slug": "demo-sportsbar",
"name": "The Neon Tap",
"tagline": "Sports bar, kitchen & game house",
"description": "A neighbourhood sports bar where guests come to dine, drink, watch the match, and play. Reserve a table for the big game, book a billiard table or dart board by the hour, or take the whole mezzanine for a private event.",
"city": "Berlin",
"country": "Germany",
"timezone": "Europe/Berlin",
"resourceTypes": [
"RestaurantTable",
"BilliardTable",
"DartBoard"
],
"extras": null,
"bookingDurations": [
{
"type": "RestaurantTable",
"defaultMinutes": 120,
"minMinutes": 60,
"maxMinutes": 240,
"stepMinutes": 30,
"guestSelectable": false,
"options": [
120
]
},
{
"type": "BilliardTable",
"defaultMinutes": 60,
"minMinutes": 60,
"maxMinutes": 240,
"stepMinutes": 30,
"guestSelectable": true,
"options": [
60,
90,
120
]
},
{
"type": "DartBoard",
"defaultMinutes": 60,
"minMinutes": 60,
"maxMinutes": 240,
"stepMinutes": 30,
"guestSelectable": true,
"options": [
60,
90,
120
]
}
]
}Lists in the example are shortened to three entries.
Errors — 404 no venue with that slug; key problems as on Errors.
curl
curl "https://api.bookdineplay.com/api/venues/your-venue" \
-H "X-BookDinePlay-Key: bdp_pk_your_publishable_key" \
-H "Origin: https://www.your-venue.example"JavaScript
const response = await fetch('https://api.bookdineplay.com/api/venues/your-venue', {
headers: { 'X-BookDinePlay-Key': 'bdp_pk_your_publishable_key' }
});
const venue = await response.json();
console.log(venue.name, venue.resourceTypes);C#
var venue = await client.GetVenueAsync("your-venue", cancellationToken);
if (venue is null) { /* no venue with that slug */ }GET /api/venues/{venueSlug}/business-info
Legal name, postal address and contact details — what an imprint, a map link or a confirmation e-mail needs.
Response 200 OK
| Field | Type | Meaning |
|---|---|---|
slug |
string | The venue |
legalName |
string | The operating company as it appears on receipts |
addressLine1, addressLine2, city, postalCode, country |
string (addressLine2 nullable) |
Postal address |
phone, email, website |
string (website nullable) |
Contact details the venue publishes |
timezone |
string | Same IANA zone as on the profile |
{
"slug": "demo-sportsbar",
"legalName": "Neon Tap Hospitality GmbH",
"addressLine1": "Boxhagener Straße 42",
"addressLine2": null,
"city": "Berlin",
"postalCode": "10245",
"country": "Germany",
"phone": "+49 30 5555 0142",
"email": "hey@theneontap.example",
"website": "https://theneontap.example",
"timezone": "Europe/Berlin"
}Errors — 404 no venue with that slug.
curl
curl "https://api.bookdineplay.com/api/venues/your-venue/business-info" \
-H "X-BookDinePlay-Key: bdp_pk_your_publishable_key" \
-H "Origin: https://www.your-venue.example"JavaScript
const info = await (await fetch('https://api.bookdineplay.com/api/venues/your-venue/business-info', {
headers: { 'X-BookDinePlay-Key': 'bdp_pk_your_publishable_key' }
})).json();
console.log(`${info.legalName}, ${info.addressLine1}, ${info.postalCode} ${info.city}`);C#
var info = await client.GetBusinessInfoAsync("your-venue", cancellationToken);GET /api/venues/{venueSlug}/opening-hours
The weekly schedule plus date-specific overrides (holidays, events, early closing). Availability already applies these; read them to display hours, not to compute slots.
Response 200 OK
| Field | Type | Meaning |
|---|---|---|
slug, timezone |
string | The venue and the zone the times are in |
regular |
object[] | One entry per weekday: day (Monday…Sunday), isClosed, opens, closes (HH:mm, null when closed) |
special |
object[] | Date overrides: date (yyyy-MM-dd), isClosed, opens, closes, note — an entry wins over the weekday for its date |
Hours that cross midnight (opens 18:00, closes 02:00) belong to the day they start on.
{
"slug": "demo-sportsbar",
"timezone": "Europe/Berlin",
"regular": [
{
"day": "Monday",
"isClosed": true,
"opens": null,
"closes": null
},
{
"day": "Tuesday",
"isClosed": false,
"opens": "16:00",
"closes": "23:00"
},
{
"day": "Wednesday",
"isClosed": false,
"opens": "16:00",
"closes": "23:00"
}
],
"special": [
{
"date": "2026-12-24",
"isClosed": true,
"opens": null,
"closes": null,
"note": "Christmas Eve — closed"
},
{
"date": "2026-12-31",
"isClosed": false,
"opens": "18:00",
"closes": "02:00",
"note": "New Year's Eve — late night"
}
]
}Errors — 404 no venue with that slug.
curl
curl "https://api.bookdineplay.com/api/venues/your-venue/opening-hours" \
-H "X-BookDinePlay-Key: bdp_pk_your_publishable_key" \
-H "Origin: https://www.your-venue.example"JavaScript
const hours = await (await fetch('https://api.bookdineplay.com/api/venues/your-venue/opening-hours', {
headers: { 'X-BookDinePlay-Key': 'bdp_pk_your_publishable_key' }
})).json();
for (const day of hours.regular) {
console.log(day.day, day.isClosed ? 'closed' : `${day.opens}–${day.closes}`);
}C#
var hours = await client.GetOpeningHoursAsync("your-venue", cancellationToken);GET /api/venues/{venueSlug}/menus
Every published menu with its groups and items, priced in the venue's currency. This is the display menu; what a seated guest can order from is the table session's menus (QR codes and table sessions).
Response 200 OK
| Field | Type | Meaning |
|---|---|---|
slug, currency |
string | The venue and the ISO 4217 currency of every price below |
menus[] |
object[] | id, name, description, groups[] |
groups[] |
object[] | name, description (nullable), items[] |
items[] |
object[] | name, description, price (decimal), currency, tags (string[], e.g. dietary labels), imageUrl (nullable) |
{
"slug": "demo-sportsbar",
"currency": "EUR",
"menus": [
{
"id": "kitchen",
"name": "Kitchen",
"description": "Served from open until one hour before close.",
"groups": [
{
"name": "Small plates",
"description": null,
"items": [
{
"name": "Loaded nachos",
"description": "Melted cheese, jalapeños, lime crema",
"price": 9.50,
"currency": "EUR",
"tags": [
"vegetarian"
],
"imageUrl": "_content/BookDinePlay.App.Shared/menu/nachos.webp"
},
{
"name": "Buffalo wings",
"description": "Blue-cheese dip, celery",
"price": 11.00,
"currency": "EUR",
"tags": [],
"imageUrl": "_content/BookDinePlay.App.Shared/menu/wings.webp"
},
{
"name": "Padrón peppers",
"description": "Sea salt, olive oil",
"price": 7.00,
"currency": "EUR",
"tags": [
"vegan",
"gluten-free"
],
"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",
"tags": [],
"imageUrl": "_content/BookDinePlay.App.Shared/menu/smash-burger.webp"
},
{
"name": "BBQ ribs",
"description": "Slow-cooked, slaw, fries",
"price": 19.00,
"currency": "EUR",
"tags": [],
"imageUrl": "_content/BookDinePlay.App.Shared/menu/bbq-ribs.webp"
},
{
"name": "Halloumi flatbread",
"description": "Grilled halloumi, harissa, greens",
"price": 13.00,
"currency": "EUR",
"tags": [
"vegetarian"
],
"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",
"tags": [
"vegetarian"
],
"imageUrl": "_content/BookDinePlay.App.Shared/menu/churros.webp"
}
]
}
]
},
{
"id": "bar",
"name": "Bar",
"description": "Draft, cocktails and zero-proof.",
"groups": [
{
"name": "Draft beer",
"description": null,
"items": [
{
"name": "House lager 0.3L",
"description": "Crisp pilsner",
"price": 4.50,
"currency": "EUR",
"tags": [],
"imageUrl": "_content/BookDinePlay.App.Shared/menu/lager.webp"
},
{
"name": "Local IPA 0.5L",
"description": "Hoppy, citrus finish",
"price": 6.50,
"currency": "EUR",
"tags": [],
"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",
"tags": [],
"imageUrl": "_content/BookDinePlay.App.Shared/menu/old-fashioned.webp"
},
{
"name": "Spicy margarita",
"description": "Tequila, chilli, lime",
"price": 10.50,
"currency": "EUR",
"tags": [],
"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",
"tags": [
"vegan"
],
"imageUrl": "_content/BookDinePlay.App.Shared/menu/craft-soda.webp"
}
]
}
]
}
]
}Errors — 404 no venue with that slug.
curl
curl "https://api.bookdineplay.com/api/venues/your-venue/menus" \
-H "X-BookDinePlay-Key: bdp_pk_your_publishable_key" \
-H "Origin: https://www.your-venue.example"JavaScript
const { menus, currency } = await (await fetch('https://api.bookdineplay.com/api/venues/your-venue/menus', {
headers: { 'X-BookDinePlay-Key': 'bdp_pk_your_publishable_key' }
})).json();
for (const menu of menus) for (const group of menu.groups) for (const item of group.items) {
console.log(menu.name, group.name, item.name, item.price, currency);
}C#
var menus = await client.GetMenusAsync("your-venue", cancellationToken);GET /api/venues/{venueSlug}/resources
Every bookable resource — tables, billiard tables, dart boards, lanes, event areas — with capacity, the booking length that applies to it, and how it is priced.
Response 200 OK
| Field | Type | Meaning |
|---|---|---|
slug |
string | The venue |
resources[] |
object[] | One per resource |
id, name, description |
string | id is what resourceId means everywhere else |
type |
string | One of the resource types |
capacity |
integer | Seats or players |
slotDurationMinutes |
integer | The effective booking length: the resource's own override when set, otherwise the venue's default for the type |
slotDurationOverrideMinutes |
integer or null | The override alone, null when the resource inherits |
pricing |
object | model (Free, PerHour, PerGame, PerSession, FixedFee, Deposit, MinimumSpend), amount, currency, depositAmount (nullable), minimumSpend (nullable), unitLabel (e.g. "per hour") |
allowsFoodOrdering |
boolean | Whether a seated guest can order from the table session |
{
"slug": "demo-sportsbar",
"resources": [
{
"id": "table-2a",
"name": "Window table",
"type": "RestaurantTable",
"capacity": 2,
"description": "Seats up to 2. Reserved free of charge.",
"slotDurationMinutes": 120,
"pricing": {
"model": "Free",
"amount": 0,
"currency": "EUR",
"depositAmount": null,
"minimumSpend": null,
"unitLabel": "booking"
},
"allowsFoodOrdering": true,
"slotDurationOverrideMinutes": null
},
{
"id": "table-4a",
"name": "Table 4",
"type": "RestaurantTable",
"capacity": 4,
"description": "Seats up to 4. Reserved free of charge.",
"slotDurationMinutes": 120,
"pricing": {
"model": "Free",
"amount": 0,
"currency": "EUR",
"depositAmount": null,
"minimumSpend": null,
"unitLabel": "booking"
},
"allowsFoodOrdering": true,
"slotDurationOverrideMinutes": null
},
{
"id": "table-4b",
"name": "Table 7",
"type": "RestaurantTable",
"capacity": 4,
"description": "Seats up to 4. Reserved free of charge.",
"slotDurationMinutes": 120,
"pricing": {
"model": "Free",
"amount": 0,
"currency": "EUR",
"depositAmount": null,
"minimumSpend": null,
"unitLabel": "booking"
},
"allowsFoodOrdering": true,
"slotDurationOverrideMinutes": null
}
]
}Errors — 404 no venue with that slug.
curl
curl "https://api.bookdineplay.com/api/venues/your-venue/resources" \
-H "X-BookDinePlay-Key: bdp_pk_your_publishable_key" \
-H "Origin: https://www.your-venue.example"JavaScript
const { resources } = await (await fetch('https://api.bookdineplay.com/api/venues/your-venue/resources', {
headers: { 'X-BookDinePlay-Key': 'bdp_pk_your_publishable_key' }
})).json();
const lanes = resources.filter(r => r.type === 'BowlingLane');C#
var resources = await client.GetResourcesAsync("your-venue", cancellationToken);GET /api/venues/{venueSlug}/floor-plan
Zones and placed resources with normalized geometry, for drawing a "pick your table" view. A venue without a plan answers 200 with empty lists — check tables.length before switching your UI to the floor plan.
Response 200 OK
| Field | Type | Meaning |
|---|---|---|
slug |
string | The venue |
zones[] |
object[] | id (GUID), name, ordinal (display order) |
tables[] |
object[] | resourceId, name, type, capacity, zoneId, x, y, width, height (all 0–1, relative to the zone), shape (Rectangle or Circle), rotation (degrees) |
{
"slug": "demo-sportsbar",
"zones": [],
"tables": []
}Errors — 404 no venue with that slug.
curl
curl "https://api.bookdineplay.com/api/venues/your-venue/floor-plan" \
-H "X-BookDinePlay-Key: bdp_pk_your_publishable_key" \
-H "Origin: https://www.your-venue.example"JavaScript
const plan = await (await fetch('https://api.bookdineplay.com/api/venues/your-venue/floor-plan', {
headers: { 'X-BookDinePlay-Key': 'bdp_pk_your_publishable_key' }
})).json();
const hasPlan = plan.tables.length > 0;C#
var plan = await client.GetFloorPlanAsync("your-venue", cancellationToken);Caching
None of these responses carries a cache header, because the operator can change them at any moment. A sensible integration caches them for minutes, not days, and refetches on a 404 or a schema it does not recognise. The widget itself reads the venue profile and floor plan once per page load and never caches across loads.
Next steps
- Availability — turn a resource type and a date into bookable slots.
- .NET SDK — the same six reads as typed methods.