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
Availability
Ask which slots a venue can offer for a resource type, a date, a party size and an optional booking length — the call that precedes every reservation.
Availability turns a resource type, a date and a party size into the list of slots a guest can pick. It already accounts for opening hours, existing reservations, each resource's capacity and booking length, and the venue's pricing — so a slot marked available can be reserved as-is, and a reservation for a slot that was not offered is refused.
Endpoints
GET /api/venues/{venueSlug}/availability
Query parameters
| Parameter | Required | Meaning |
|---|---|---|
date |
yes | Venue-local date, yyyy-MM-dd (Dates, times and time zones) |
partySize |
no, default 2 |
Guests or players. 0 or a negative value is clamped up to 1 rather than rejected — unlike reservations, which validates it as a 1–200 range. Larger parties may be offered group slots that span several resources |
resourceType |
no, default RestaurantTable |
One of the venue's resourceTypes — RestaurantTable, BilliardTable, DartBoard, Shuffleboard, BowlingLane, EventArea. The default is always RestaurantTable, regardless of what a given venue actually offers — it is not derived from the venue's own resourceTypes, so an unchanged request always means the same thing. If the venue owns none of the requested type, slots is empty and notice is no-resources-of-type; read a venue's own types off the venue profile rather than assuming this default fits |
durationMinutes |
no | A booking length the venue offers for that type (see bookingDurations on the venue profile). Omitted, 0, or a length the venue does not offer all fall back to the venue's default for that type — check durationMinutes in the response to see which one was used |
groupBy |
no | Set to start (case-insensitive — Start/START also match) to receive slotsByStart instead of the flat slots[] array — see Grouping by start. Any other value (including omitting the parameter) leaves today's flat shape exactly as it is |
resourceGroup |
no | A ResourceGroup id (see resourceGroupId on Resources) — narrows slots/slotsByStart to resources in that group, e.g. "Standard" rather than every billiard table. Composes with groupBy — see Filtering by resource group. Omitting it leaves today's unfiltered shape exactly as it is |
Response 200 OK
| Field | Type | Meaning |
|---|---|---|
slug, date, resourceType, partySize |
The request, echoed | |
durationMinutes |
integer | The booking length the slots represent — the default when you did not send one |
slots[] |
object[] | Every candidate slot for the day, available or not. Empty ([]) when groupBy=start was requested — see slotsByStart |
notice |
string or null | Set when slots is empty for a reason you would otherwise have to guess at: venue-closed, party-too-large, no-resources-of-type, resource-type-not-offered, or no-resources-in-group (see Slots). Absent otherwise — including an ordinary fully-booked day, which sets no notice at all |
slotsByStart[] |
object[] or null | The ?groupBy=start projection of slots[] — one entry per distinct start time. null unless you requested it |
Slots
| Field | Type | Meaning |
|---|---|---|
start, end |
string | Venue-local HH:mm |
resourceId, resourceName |
string | The resource this slot is on — send resourceId back when reserving to get exactly this one |
available |
boolean | false for a slot that exists on the grid but is already taken; show it greyed out, never book it |
priceEstimate, currency |
decimal, string | What the booking would cost under the resource's pricing model; 0 for free resources |
pricingModel |
string or null | Free, PerHour, PerGame, PerSession, FixedFee, Deposit, MinimumSpend — tells you what the estimate is per |
groupResourceCount, groupResourceNames |
integer, string[] (both null for single-resource slots) | A party too large for one resource is offered a group slot across several; resourceId is then the first member |
resourceGroupId, resourceGroupName, resourceGroupDescription |
string (GUID), string, string (all null for an ungrouped resource) | The ResourceGroup this slot's resource belongs to — same fields, same null-iff-ungrouped rule, as on Resources. For a combined slot (groupResourceCount set), this is the first member's group |
seatsRemaining |
integer or absent | Seats still free on this resource at this start time — "27 of 31 left" — for a seat-sold resource (seatSold on Resources). Absent entirely, not merely null, for an exclusive resource: there available already says everything there is to say, and omitting the field keeps every existing venue's response byte-identical |
resourceName for a group slot is groupResourceNames joined with " + " — readable for a receipt line, but for five tables that is a ~100-character string no dropdown can show. Prefer the structured groupResourceCount/groupResourceNames fields for UI you build yourself, and keep the joined resourceName for a tooltip or a plain-text confirmation.
An empty slots list with 200 is not an error, but it has more than one honest cause — check notice to tell them apart instead of guessing from context (an extra opening-hours lookup to distinguish "closed" from "nothing free" is exactly what this field removes):
notice |
When |
|---|---|
venue-closed |
The venue has no open segment at all on date — a weekly closing day, a holiday, or a special closed day. Pick another date |
party-too-large |
No resource of resourceType — and, for a groupable type, no combination of them — could ever seat partySize, regardless of what is booked when. Offer a smaller party or a different resourceType |
no-resources-of-type |
The venue owns no resources of resourceType at all — same as resource-type-not-offered below from a caller's point of view, just for a different reason (the venue itself, not its plan) |
resource-type-not-offered |
The venue's plan does not include resourceType |
no-resources-in-group |
?resourceGroup= matched none of the venue's resources of resourceType — an id the venue never issued, one from a different venue, or a real group that simply has no resources of this type. See Filtering by resource group |
| absent | Every other empty response — there is nothing to tell the caller beyond "nothing free right now" |
Note that an ordinary fully-booked day does not usually mean a literally empty slots[]: a taken slot is still returned, marked available: false (see available below), as long as the party fits the resource — that is what lets you grey it out instead of hiding it. A literally empty list on a busy day only happens for a party that needs a group booking across several resources, where nothing offered is included at all once no combination of currently-free resources can seat it; a small party on a fully-booked single resource still gets a populated, all-false array.
{
"slug": "demo-sportsbar",
"date": "2026-10-16",
"resourceType": "RestaurantTable",
"partySize": 4,
"slots": [
{
"start": "15:00",
"end": "17:00",
"resourceId": "table-6a",
"resourceName": "Corner table",
"available": true,
"priceEstimate": 0,
"currency": "EUR",
"groupResourceCount": null,
"groupResourceNames": null,
"pricingModel": "Free",
"resourceGroupId": null,
"resourceGroupName": null,
"resourceGroupDescription": null
},
{
"start": "15:00",
"end": "17:00",
"resourceId": "table-4a",
"resourceName": "Table 4",
"available": true,
"priceEstimate": 0,
"currency": "EUR",
"groupResourceCount": null,
"groupResourceNames": null,
"pricingModel": "Free",
"resourceGroupId": null,
"resourceGroupName": null,
"resourceGroupDescription": null
},
{
"start": "15:00",
"end": "17:00",
"resourceId": "table-4b",
"resourceName": "Table 7",
"available": true,
"priceEstimate": 0,
"currency": "EUR",
"groupResourceCount": null,
"groupResourceNames": null,
"pricingModel": "Free",
"resourceGroupId": null,
"resourceGroupName": null,
"resourceGroupDescription": null
}
],
"durationMinutes": 120
}Lists in the example are shortened to three entries.
Errors — 400 with errors.date when date is not yyyy-MM-dd; 404 no venue with that slug; key problems as on Errors.
curl
curl "https://api.bookdineplay.com/api/venues/your-venue/availability?date=2026-10-16&partySize=4&resourceType=RestaurantTable" \
-H "X-BookDinePlay-Key: bdp_pk_your_publishable_key" \
-H "Origin: https://www.your-venue.example"JavaScript
const params = new URLSearchParams({ date: '2026-10-16', partySize: '4', resourceType: 'RestaurantTable' });
const availability = await (await fetch(`https://api.bookdineplay.com/api/venues/your-venue/availability?${params}`, {
headers: { 'X-BookDinePlay-Key': 'bdp_pk_your_publishable_key' }
})).json();
const open = availability.slots.filter(s => s.available);
console.log(`${open.length} slots of ${availability.durationMinutes} min`);C#
var availability = await client.GetAvailabilityAsync(
"your-venue", new DateOnly(2026, 10, 16), partySize: 4, BookableResourceType.RestaurantTable,
cancellationToken: cancellationToken);
var open = availability?.Slots.Where(s => s.Available).ToList() ?? [];Grouping by start
The default slots[] shape is the cross product of every start time and every resource of resourceType — a venue with nine billiard tables open for nine start times returns 81 rows, one per (start × resource). Every client that renders a time picker ends up grouping those rows by start itself before it can show one row per time. Send groupBy=start to get that projection from the server instead:
{
"slug": "demo-sportsbar",
"date": "2026-10-16",
"resourceType": "BilliardTable",
"partySize": 2,
"slots": [],
"durationMinutes": 60,
"slotsByStart": [
{
"start": "18:00",
"resources": [
{ "end": "19:00", "resourceId": "billiard-1", "resourceName": "Billiard Table 1", "available": true, "priceEstimate": 12, "currency": "EUR", "groupResourceCount": null, "groupResourceNames": null, "pricingModel": "PerHour", "resourceGroupId": null, "resourceGroupName": null, "resourceGroupDescription": null },
{ "end": "19:00", "resourceId": "billiard-2", "resourceName": "Billiard Table 2", "available": false, "priceEstimate": 12, "currency": "EUR", "groupResourceCount": null, "groupResourceNames": null, "pricingModel": "PerHour", "resourceGroupId": null, "resourceGroupName": null, "resourceGroupDescription": null }
]
}
]
}slots is empty in a grouped response — the data moves to slotsByStart, not both, which is what actually shrinks the payload. Each entry under resources has every field slots[] had except start, which the group already carries; a resource's own end still travels with it, since a per-resource booking-length override can make it differ from its neighbours at the same start. Omitting groupBy, or sending anything other than start, returns exactly today's flat shape — slotsByStart is then null.
The .NET SDK exposes this as a separate GetAvailabilityByStartAsync method, rather than a raw query string — GetAvailabilityAsync itself is unchanged:
var grouped = await client.GetAvailabilityByStartAsync(
"your-venue", new DateOnly(2026, 10, 16), partySize: 2, BookableResourceType.BilliardTable,
cancellationToken: cancellationToken);
var byStart = grouped?.SlotsByStart ?? [];Filtering by resource group
A venue with several resources of one type often prices or presents them differently — "Standard" vs. "Premium" billiard tables, say (see ResourceGroup on Resources). Send ?resourceGroup=<id> to narrow slots/slotsByStart to just that group instead of every resource of resourceType:
curl "https://api.bookdineplay.com/api/venues/your-venue/availability?date=2026-10-16&partySize=2&resourceType=BilliardTable&resourceGroup=8f14e45f-ceea-4b9c-8c1a-0e3c2b9a1d4f" \
-H "X-BookDinePlay-Key: bdp_pk_your_publishable_key" \
-H "Origin: https://www.your-venue.example"Every returned slot's resourceGroupId then equals the id you asked for (resourceGroupName/resourceGroupDescription travel with it, so you rarely need a separate lookup to label the results). Omitting resourceGroup — or a request from before this parameter existed — returns exactly today's shape: every resource of resourceType, unfiltered.
An id that matches nothing of resourceType — one the venue never issued, one from a different venue, or a real group that simply has no resources of this type — is not an error: slots (and slotsByStart, if requested) comes back empty with notice: "no-resources-in-group", the same treatment no-resources-of-type already gives a type the venue owns none of. That is deliberately distinct from a real group that is simply fully booked: a fully booked group still returns its ordinary populated array with entries marked available: false (or, for a party that needs a group booking spanning several resources, an empty array with no notice — see Slots) — never the no-resources-in-group notice. Check notice, not just whether slots is empty, to tell "nothing in this group" apart from "everything in this group is taken right now".
The filter composes with groupBy=start in either order — resourceGroup narrows which resources are considered before the start-time projection is built, so a grouped response only ever lists resources from the requested group:
curl "https://api.bookdineplay.com/api/venues/your-venue/availability?date=2026-10-16&partySize=2&resourceType=BilliardTable&resourceGroup=8f14e45f-ceea-4b9c-8c1a-0e3c2b9a1d4f&groupBy=start" \
-H "X-BookDinePlay-Key: bdp_pk_your_publishable_key" \
-H "Origin: https://www.your-venue.example"The .NET SDK exposes resourceGroup as a trailing optional parameter on both GetAvailabilityAsync and GetAvailabilityByStartAsync, so it composes with groupBy=start there too — pass both to get a grouped, filtered board in one call:
var filtered = await client.GetAvailabilityAsync(
"your-venue", new DateOnly(2026, 10, 16), partySize: 2, BookableResourceType.BilliardTable,
resourceGroup: standardGroupId, cancellationToken: cancellationToken);
var filteredAndGrouped = await client.GetAvailabilityByStartAsync(
"your-venue", new DateOnly(2026, 10, 16), partySize: 2, BookableResourceType.BilliardTable,
resourceGroup: standardGroupId, cancellationToken: cancellationToken);Choosing a booking length
When the venue lets guests choose (guestSelectable on the profile's bookingDurations), offer the options list for the type, pass the choice as durationMinutes, and send the same value with the reservation. Asking for a length the venue does not offer is answered with the default, not an error — compare durationMinutes in the response with what you asked for.
Next steps
- Reservations — book the slot the guest picked.
- Profile — the resource types and durations you can ask for.
- Rate limits — how many of these calls to design against, and why debouncing matters here specifically.