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
Reservations
Create a reservation for a slot the availability call offered — request fields, the reservation you get back, deposits, group bookings and every refusal.
One call books a slot. Send the resource type, date and start the guest chose from Availability, the party size and the guest's contact details; you get back a reservation with a reference the guest can quote, a status, and the price and deposit that apply.
Endpoints
POST /api/venues/{venueSlug}/reservations
Request headers
| Header | Required | Meaning |
|---|---|---|
Idempotency-Key |
no | An opaque token you choose (up to 200 characters), scoped to this venue AND to your BookDinePlay account, and honoured for 24 hours. Any API key of your account can replay a key another of your keys created, so rotating a key mid-flight is safe. All of your own keys deliberately share one namespace — they are one account — so keep minting a key per booking attempt rather than relying on separation between two of your own integrations; what is separate is another account's keys, and bookings taken for your venue through BookDinePlay's own hosted pages, which can neither replay nor block yours. A repeat with the SAME key and the SAME request body returns the ORIGINAL reservation — same reference, same cancelToken — instead of creating a second one. Once a key has produced a reservation this way, reusing it with a DIFFERENT body is refused with idempotency-key-reused, never silently replayed or silently allowed through. A key whose only attempt so far FAILED (validation, no slot free) has nothing stored under it yet — a corrected retry under that same key is simply a normal new attempt, not a mismatch. Omit the header and nothing changes: every call creates a new reservation, exactly as it always has. |
Request body (Content-Type: application/json)
| Field | Required | Meaning |
|---|---|---|
resourceType |
yes | The type of the slot, e.g. RestaurantTable |
resourceId |
no | The slot's resourceId to get exactly that resource. Omitted: the first available resource of the type at that time |
date |
yes | Venue-local yyyy-MM-dd |
start |
yes | Venue-local HH:mm, a start the availability call offered |
end |
no | Venue-local HH:mm; defaults to start plus the booking length |
durationMinutes |
no | 15–1440. Send the same value you queried availability with, or omit for the venue's default |
partySize |
yes | 1–200 |
games |
no | 1–10, for resources priced per game; defaults to 1 |
customerName |
yes | 2–120 characters |
email |
yes | A valid e-mail address — the confirmation goes there |
phone |
no | 3–40 characters when supplied; a blank value counts as not supplied |
notes |
no | Up to 500 characters, shown to the venue |
depositOptIn |
no | true when the guest agrees to pay a deposit; only has an effect when the venue enables deposits |
extras |
no | [{ "extraId": "…", "quantity": 1 }] — extras from the venue profile that apply to this resource type, quantity 1–100 |
Response 201 Created — no Location header. The token in the body is a credential (see cancelToken below); putting it in a header would put it in access logs, proxy logs and Referer chains, so nothing about the reservation is derivable from the response headers.
| Field | Type | Meaning |
|---|---|---|
id |
GUID | Internal id |
reference |
string | BDP-XXXXXX — what the guest quotes and what a payment intent refers to. Treat it as an opaque token: bookings made before references grew to six characters carry four (BDP-XXXX) and stay valid |
status |
string | Confirmed, or Pending when a deposit is required and not yet paid; later Cancelled, Completed, NoShow |
venueSlug, resourceType, resourceName |
string | Where |
date, start, end |
string | When, venue-local |
partySize, customerName |
Who | |
priceEstimate, currency |
decimal, string | The booking's price under the resource's model |
depositRequired, depositAmount |
boolean, decimal or null | Whether the guest must pay a deposit to confirm, and how much |
createdAt |
ISO 8601 instant | When the reservation was made |
groupReference, groupResources |
string or null, object[] or null | For a group booking: the shared reference and each member (resourceId, resourceName, partyShare) |
games |
integer or null | For per-game pricing |
extras, extrasTotal |
object[] or null, decimal or null | Priced extra lines (extraId, name, unitPrice, quantity, lineTotal, currency, basis) and their sum |
cancelToken |
string or null | The guest's own credential for GET /api/reservations/{cancelToken} and the cancel route — keep it out of logs. Shared by every member of a group booking. Null only for a reservation created before this field existed |
{
"id": "cad35696-0a75-45b6-b986-8bed7eb00ac5",
"reference": "BDP-KSQS7M",
"status": "Confirmed",
"venueSlug": "demo-sportsbar",
"resourceType": "RestaurantTable",
"resourceName": "Corner table",
"date": "2026-10-16",
"start": "15:00",
"end": "17:00",
"partySize": 4,
"customerName": "Ada Lovelace",
"priceEstimate": 0,
"currency": "EUR",
"depositRequired": false,
"depositAmount": null,
"createdAt": "2026-09-15T12:55:15.1526956+00:00",
"groupReference": null,
"groupResources": null,
"games": null,
"extras": null,
"extrasTotal": null,
"cancelToken": "dqN_AejenL2r7qXPdZuOxnLY9_BfVkTl"
}Errors
| Status | When |
|---|---|
400 |
A field fails validation (errors names it); date/start malformed; the venue is closed then; the duration is not one the venue offers for that type; an extra does not apply |
404 |
No venue with that slug |
409 |
No resource of that type is free at that time — it was taken since the availability call. Re-query and offer the next slot |
409 |
An Idempotency-Key you sent was already used for a request with a different body — see idempotency-key-reused |
curl
curl -X POST "https://api.bookdineplay.com/api/venues/your-venue/reservations" \
-H "X-BookDinePlay-Key: bdp_pk_your_publishable_key" \
-H "Origin: https://www.your-venue.example" \
-H "Content-Type: application/json" \
-d '{
"resourceType": "RestaurantTable",
"date": "2026-10-16",
"start": "19:00",
"partySize": 4,
"customerName": "Ada Lovelace",
"email": "ada@example.com",
"phone": "+49 30 1234567",
"notes": "Window seat if possible"
}'JavaScript
const response = await fetch('https://api.bookdineplay.com/api/venues/your-venue/reservations', {
method: 'POST',
headers: { 'X-BookDinePlay-Key': 'bdp_pk_your_publishable_key', 'Content-Type': 'application/json' },
body: JSON.stringify({
resourceType: 'RestaurantTable', date: '2026-10-16', start: '19:00', partySize: 4,
customerName: 'Ada Lovelace', email: 'ada@example.com', phone: '+49 30 1234567',
notes: 'Window seat if possible'
})
});
if (response.status === 409) { /* slot just taken — re-query availability */ }
const reservation = await response.json();
console.log(reservation.reference, reservation.status);C#
try
{
var reservation = await client.CreateReservationAsync("your-venue", new CreateReservationRequest
{
ResourceType = BookableResourceType.RestaurantTable,
Date = "2026-10-16",
Start = "19:00",
PartySize = 4,
CustomerName = "Ada Lovelace",
Email = "ada@example.com",
Phone = "+49 30 1234567",
Notes = "Window seat if possible"
}, cancellationToken);
Console.WriteLine($"{reservation.Reference} {reservation.Status}");
}
catch (BookDinePlayApiException ex) when (ex.StatusCode == HttpStatusCode.Conflict)
{
// slot just taken — re-query availability
}Retrying safely with Idempotency-Key — mint the key once per booking attempt and reuse it for every retry of that SAME attempt:
curl
key="resv-$(uuidgen)"
curl -X POST "https://api.bookdineplay.com/api/venues/your-venue/reservations" \
-H "X-BookDinePlay-Key: bdp_pk_your_publishable_key" \
-H "Origin: https://www.your-venue.example" \
-H "Idempotency-Key: $key" \
-H "Content-Type: application/json" \
-d '{ "resourceType": "RestaurantTable", "date": "2026-10-16", "start": "19:00", "partySize": 4, "customerName": "Ada Lovelace", "email": "ada@example.com" }'
# On a timeout or 502/504, retry the exact same request with the SAME $key.JavaScript
const idempotencyKey = crypto.randomUUID();
async function reserve() {
const response = await fetch('https://api.bookdineplay.com/api/venues/your-venue/reservations', {
method: 'POST',
headers: {
'X-BookDinePlay-Key': 'bdp_pk_your_publishable_key',
'Content-Type': 'application/json',
'Idempotency-Key': idempotencyKey
},
body: JSON.stringify({
resourceType: 'RestaurantTable', date: '2026-10-16', start: '19:00', partySize: 4,
customerName: 'Ada Lovelace', email: 'ada@example.com'
})
});
return response.json();
}
// Call reserve() again on a timeout/502/504 — same idempotencyKey each time.C#
var idempotencyKey = Guid.NewGuid().ToString();
var reservation = await client.CreateReservationAsync("your-venue", new CreateReservationRequest
{
ResourceType = BookableResourceType.RestaurantTable,
Date = "2026-10-16",
Start = "19:00",
PartySize = 4,
CustomerName = "Ada Lovelace",
Email = "ada@example.com"
}, idempotencyKey);
// A retry after a timeout/502/504 passes the SAME idempotencyKey and gets back the SAME reservation.GET /api/reservations/{cancelToken}
A guest's own booking summary by their cancel token: what, when, where, party size, status, and whether the guest's own cancel button should be shown right now. No key: the token in the URL is itself the credential the confirmation e-mail carried, the same reasoning as the event registration/ticket routes. Not plan-gated — the guest booked while the venue's plan allowed it. A group booking (several resources sharing one token) is reported as a single summary: partySize is the whole party and groupResources lists every member.
Response 200 OK
| Field | Type | Meaning |
|---|---|---|
reference |
string | The booking's own reference (a group booking's first member) |
groupReference |
string or null | The shared reference guests are shown for a group booking |
venueSlug, venueName |
string | Where |
status |
string | Confirmed, Pending, Cancelled, Completed or NoShow |
resourceType, resourceName |
string | The (first) resource |
date, start, end |
string | When, venue-local |
partySize |
integer | The whole party across every group member |
groupResources |
object[] or null | For a group booking: each member (resourceId, resourceName, partyShare) |
canCancel |
boolean | Whether the guest's own cancel button should work right now |
cancelNote |
string or null | Why not, when canCancel is false: already cancelled, resolved by the venue (a no-show or completed visit), a paid deposit with no online refund path yet, or the venue's cancellation policy no longer allows it (switched off, or past its cut-off) |
{
"reference": "BDP-KSQS7M",
"groupReference": null,
"venueSlug": "demo-sportsbar",
"venueName": "The Neon Tap",
"status": "Confirmed",
"resourceType": "RestaurantTable",
"resourceName": "Corner table",
"date": "2026-10-16",
"start": "15:00",
"end": "17:00",
"partySize": 4,
"groupResources": null,
"canCancel": true,
"cancelNote": null
}Errors — 404 for an unknown or malformed token.
curl
curl "https://api.bookdineplay.com/api/reservations/dqN_AejenL2r7qXPdZuOxnLY9_BfVkTl"JavaScript
const booking = await (await fetch('https://api.bookdineplay.com/api/reservations/' + cancelToken)).json(); // sends no keyC#
using var response = await http.GetAsync($"api/reservations/{cancelToken}", cancellationToken); // sends no keyPOST /api/reservations/{cancelToken}/cancel
Cancels the booking behind a cancel link and frees its slot immediately — every member of a group booking at once, since they share one token. No key: the token is the credential. Idempotent — a second call after a successful cancel answers 200 again. Not plan-gated. No request body.
Response 200 OK — reference, groupReference, status (Cancelled).
{
"reference": "BDP-KSQS7M",
"groupReference": null,
"status": "Cancelled"
}Errors — 404 for an unknown token; 409 reservation-cancellation-not-allowed once the venue's cancellation policy no longer allows an online cancel (switched off entirely, or past its cut-off — the operator controls both); 409 reservation-deposit-not-refundable for a booking whose deposit was paid online at a venue that has chosen to sort deposits out by phone rather than through a cancel link, leaving the booking untouched; and 502 reservation-refund-failed at a venue that DOES refund, when the refund itself did not go through — also leaving the booking untouched, but worth retrying rather than explaining away. Check canCancel on GET /api/reservations/{cancelToken} first to avoid the round trip either way.
curl
curl -X POST "https://api.bookdineplay.com/api/reservations/<cancelToken>/cancel"JavaScript
const result = await client.cancelReservation(cancelToken); // sends no keyC#
var result = await client.CancelReservationAsync(cancelToken, cancellationToken); // sends no key; null when unknownDeposits
A reservation needs a deposit when three things are true: the guest sent depositOptIn: true, the venue has deposits enabled, and the resource (or the venue's default for game resources) carries a deposit amount. Then depositRequired is true, depositAmount says how much, and status is Pending until the venue confirms the booking. Create a payment intent for that amount next. Without a deposit the reservation is Confirmed immediately.
Group bookings
When partySize exceeds one resource's capacity and the availability call offered a group slot, the reservation spans several resources: groupReference is the shared reference and groupResources lists each member with its share of the party. Extras are charged once for the whole group.
Next steps
- Payment intents — take the deposit.
- Availability — where every
startcomes from.