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
Events
List a venue's published events, register for a free one or buy paid tickets via Stripe, look up a registration, and the keyless ticket and cancel routes.
A venue on Pro or higher publishes its events — a darts league night, a match screening, a weekly quiz — and guests register for them through the same API the widgets use. A registration produces one named ticket per attendee; each ticket has a code and a page with the code the venue checks in at the door. A Paid event (Premium) sells those tickets through a Stripe Checkout session on the venue's own connected account instead of confirming instantly. The list, detail and registration routes follow the same key and CORS rules as the rest of /api/venues/**. The last three routes on this page need no key at all: the ticket code and the cancel token in the URL are the credentials a guest received by e-mail, exactly like a printed QR token on QR codes and table sessions.
Events are created, published and cancelled in the console at app.bookdineplay.com; there is no public write route for them. The JavaScript SDK renders the whole flow as a widget or hands you the calls for your own markup; the .NET SDK wraps the same calls.
Plans and visibility
| Situation | List | Detail | Registration |
|---|---|---|---|
| Published event, plan includes Events | listed | 200 |
201, or 409 when the window is shut |
| Cancelled event | not listed | 200 with status: "Cancelled" and registration.open: false |
409 registration-closed, reason: "not-open" |
| Draft, or unknown slug | not listed | 404 |
404 |
| Venue's plan does not include Events (Starter) | 200 with an empty events array |
404 |
403 feature-not-in-plan with requiredPlan: "Pro" |
| Unknown venue | 404 |
404 |
404 |
A widget on a Starter venue therefore shows an empty state rather than an error the guest cannot act on. The ticket and cancel routes are not plan-gated: a guest who registered while the plan allowed it keeps their ticket page and cancel link after a downgrade.
The registration window
Every event detail carries a registration object that says whether a registration would be accepted right now, and why not otherwise:
| Field | Type | Meaning |
|---|---|---|
mode |
string | None (informational, nothing to register), Free (named tickets, no payment) or Paid (tickets with a price — Premium) |
price, currency |
decimal, string | Ticket price of a paid event, e.g. 12.5 and EUR; both null otherwise |
capacity |
integer or null | Maximum number of tickets; null when unlimited |
remaining |
integer or null | Tickets still available; null when unlimited. Counts down as registrations arrive and back up when one is cancelled |
maxPerRegistration |
integer | Tickets one registration may hold (the venue's setting, default 6). Ask for min(maxPerRegistration, remaining) at most |
closesAt |
ISO 8601 instant or null | When the venue closes registration early; null when it stays open until the event starts |
open |
boolean | true when a registration sent now would be accepted |
reason |
string or null | Why open is false: closed (past closesAt), sold-out (remaining is 0), past (the event has started), not-open (mode None, or the event was cancelled). Null while open |
refundPolicy |
object or null | Only set for Paid — see The refund policy. Null for every other mode |
The same four reasons come back in the registration-closed problem when a registration is refused — the window can shut between the detail read and the registration, so handle both.
The refund policy
Every Paid event carries a refund policy the venue sets when configuring the event, in both the event detail's registration.refundPolicy and a guest's own GET /api/registrations/{cancelToken}:
| Field | Type | Meaning |
|---|---|---|
mode |
string | NoOnlineRefund (the guest must contact the venue) or FullRefundUntil (a full online refund up to a fixed number of hours before the event starts) |
hoursBeforeStart |
integer | Only meaningful for FullRefundUntil, 0–720 |
refundableUntil |
ISO 8601 instant or null | The instant after which an online refund is no longer available; null for NoOnlineRefund or an event with no fixed start |
A guest's own cancel button should be shown only when their registration's canCancel is true — see GET /api/registrations/{cancelToken}. Attempting to cancel a Paid registration the policy no longer allows answers 409 refund-not-available and leaves the registration untouched; the operator can still refund it from the console at any time, regardless of the policy.
Endpoints
GET /api/venues/{venueSlug}/events
Published events in listing order: dated events ascending by date and start, ongoing events (no date) after them. Drafts and cancelled events are never listed. A venue whose plan does not include events returns an empty list, not an error.
Query — upcoming (true by default) drops dated events whose day has passed in the venue's time zone; upcoming=false returns every published event, past ones included.
Response 200 OK — events[], each:
| Field | Type | Meaning |
|---|---|---|
slug, title, excerpt |
string | Identity and teaser (excerpt nullable) |
imageUrl, imageAlt |
string or null | The event picture and its alt text |
status |
string | Published here; Cancelled on a detail read |
date |
string or null | Venue-local yyyy-MM-dd; null for an ongoing event |
start, end |
string or null | Venue-local HH:mm |
timeLabel |
string or null | Free text the venue shows next to, or instead of, the times (Doors 19:30, Every Thursday from 20:00) |
locationLabel |
string | Where it happens — the venue's label, or its name and address |
registrationMode |
string | None, Free or Paid |
language |
string | en or de — the language the event was written in |
{
"events": [
{
"slug": "darts-league-night",
"title": "Darts league night",
"excerpt": "Eight boards, one winner — sign up your team.",
"imageUrl": null,
"imageAlt": null,
"status": "Published",
"date": "2027-10-16",
"start": "20:00",
"end": "23:00",
"timeLabel": "Doors 19:30",
"locationLabel": "The Neon Tap, Boxhagener Straße 42, 10245 Berlin",
"registrationMode": "Free",
"language": "en"
},
{
"slug": "summer-cup-final",
"title": "Summer cup final",
"excerpt": "Watch the final on the big screens.",
"imageUrl": null,
"imageAlt": null,
"status": "Published",
"date": "2027-10-23",
"start": "18:00",
"end": null,
"timeLabel": null,
"locationLabel": "The Neon Tap, Boxhagener Straße 42, 10245 Berlin",
"registrationMode": "Free",
"language": "en"
},
{
"slug": "pub-quiz",
"title": "Pub quiz",
"excerpt": "Six rounds, free entry, prizes for the top three tables.",
"imageUrl": null,
"imageAlt": null,
"status": "Published",
"date": null,
"start": null,
"end": null,
"timeLabel": "Every Thursday from 20:00",
"locationLabel": "The Neon Tap, Boxhagener Straße 42, 10245 Berlin",
"registrationMode": "None",
"language": "en"
}
]
}Errors — 404 no venue with that slug.
curl
curl "https://api.bookdineplay.com/api/venues/your-venue/events" \
-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 events = await client.events(); // the array, upcoming only
const all = await client.events({ upcoming: false }); // past dated events tooC#
var events = await client.GetEventsAsync("your-venue", cancellationToken: cancellationToken);
var all = await client.GetEventsAsync("your-venue", upcoming: false, cancellationToken);GET /api/venues/{venueSlug}/events/{eventSlug}
One event with its description and registration window. A cancelled event still answers 200 with status: "Cancelled" so a bookmarked link can explain itself; a draft or unknown slug is 404.
Response 200 OK — the list fields plus venueSlug, venueName, descriptionHtml and registration (The registration window). descriptionHtml is rendered from the venue's Markdown and filtered on the server against a strict allowlist — paragraphs, headings, emphasis, lists, quotes, code, links (rel="noopener nofollow") and https images. Allowlisted inline HTML (<strong>, <a> …) the venue wrote in its Markdown survives the filter; nothing outside that allowlist survives — no scripts, styles, event handlers or raw HTML beyond it — so it is safe to insert into your page as HTML.
{
"venueSlug": "demo-sportsbar",
"venueName": "The Neon Tap",
"slug": "darts-league-night",
"title": "Darts league night",
"excerpt": "Eight boards, one winner — sign up your team.",
"descriptionHtml": "<p>Bring your <strong>own</strong> darts or borrow a set at the bar. Registration closes at the door.</p>\n",
"imageUrl": null,
"imageAlt": null,
"status": "Published",
"date": "2027-10-16",
"start": "20:00",
"end": "23:00",
"timeLabel": "Doors 19:30",
"locationLabel": "The Neon Tap, Boxhagener Straße 42, 10245 Berlin",
"language": "en",
"registration": {
"mode": "Free",
"price": null,
"currency": null,
"capacity": 64,
"remaining": 64,
"maxPerRegistration": 6,
"closesAt": null,
"open": true,
"reason": null
}
}Errors — 404 no such venue, or no published event with that slug (a draft, an unknown slug, or a venue whose plan lacks Events).
curl
curl "https://api.bookdineplay.com/api/venues/your-venue/events/darts-league-night" \
-H "X-BookDinePlay-Key: bdp_pk_your_publishable_key" \
-H "Origin: https://www.your-venue.example"JavaScript
const event = await client.event('darts-league-night'); // rejects with status 404 when unknown
if (!event.registration.open) console.log(event.registration.reason);C#
var detail = await client.GetEventAsync("your-venue", "darts-league-night", cancellationToken); // null when unknownPOST /api/venues/{venueSlug}/events/{eventSlug}/registrations
Register for a free event. quantity tickets are minted, one per attendee name in attendees (the first may be blank — it defaults to the buyer). The guest receives a confirmation e-mail with the tickets and a cancel link (What the guest receives).
Request body (Content-Type: application/json)
| Field | Required | Meaning |
|---|---|---|
buyerName |
yes | 1–120 characters — the person registering; also the first attendee when attendees[0] is blank |
email |
yes | A valid e-mail address — the tickets go there |
phone |
no | Up to 40 characters; a blank value counts as not supplied |
notes |
no | Up to 1000 characters, shown to the venue |
quantity |
yes | 1–100, and at most registration.maxPerRegistration and registration.remaining |
attendees |
no | One name per ticket, in ticket order, each up to 120 characters. Entry 0 may be blank (the buyer's name is used); every further ticket needs a name. More names than quantity is an error |
Response 201 Created with a Location header of the event detail URL.
| Field | Type | Meaning |
|---|---|---|
reference |
string | BDP-E-XXXX — what the guest quotes |
status |
string | Confirmed; Cancelled after the guest or the venue cancels |
payment |
string | NotRequired for a free event (Pending, Paid, Refunded are reserved for paid tickets) |
eventSlug, eventTitle |
string | Which event |
buyerName, email, quantity |
What was sent, normalized | |
tickets[] |
object[] | One per attendee: code (12 characters), attendeeName, ticketUrl — the ticket page with the QR code |
cancelUrl |
string | The guest's cancel page; the last path segment is the cancel token |
{
"reference": "BDP-E-ESJA",
"status": "Confirmed",
"payment": "NotRequired",
"eventSlug": "darts-league-night",
"eventTitle": "Darts league night",
"buyerName": "Ada Lovelace",
"email": "ada@example.com",
"quantity": 2,
"tickets": [
{
"code": "X7QZUP5AC3GH",
"attendeeName": "Ada Lovelace",
"ticketUrl": "https://app.bookdineplay.com/ticket/X7QZUP5AC3GH"
},
{
"code": "DR9HN4GDX9ZH",
"attendeeName": "Grace Hopper",
"ticketUrl": "https://app.bookdineplay.com/ticket/DR9HN4GDX9ZH"
}
],
"cancelUrl": "https://app.bookdineplay.com/e/dqN_AejenL2r7qXPdZuOxnLY9_BfVkTl"
}Accepts the same optional Idempotency-Key header as reservation create: mint one per registration attempt and reuse it on retry, and a transient 5xx/timeout replays the original registration instead of minting a second one with its own tickets and confirmation e-mail. See Idempotency problems for the one way the key itself can be refused.
Errors
| Status | When |
|---|---|
400 |
A field fails validation, or a registration rule does. errors maps the field name (Email, BuyerName, Quantity, …) to its messages; a broken rule — more tickets than maxPerRegistration or than are left, a missing attendee name, more names than tickets — sits under errors.registration and detail repeats it as one sentence |
403 |
The venue's plan does not include Events — feature-not-in-plan with requiredPlan: "Pro" |
404 |
No such venue, or no published event with that slug |
409 |
registration-closed: the window is shut. reason says why (closed, sold-out, past, not-open); remaining: 0 is added when sold out — including when the last seats went to someone else between the detail read and this call |
409 |
ticket-sales-not-enabled: the event is Paid and online ticket sales are not available yet |
409 |
idempotency-key-reused: the Idempotency-Key sent was already used for a request with a different body |
A rule failure looks like this — the same errors dictionary as a field failure, so a client handles one shape on 400:
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"detail": "You can register at most 6 tickets at once.",
"errors": {
"registration": [
"You can register at most 6 tickets at once."
]
},
"traceId": "0HNOK3A31C71S"
}Rule messages are localized by Accept-Language; the confirmation e-mail is sent in that language too, falling back to the event's own language.
curl
curl -X POST "https://api.bookdineplay.com/api/venues/your-venue/events/darts-league-night/registrations" \
-H "X-BookDinePlay-Key: bdp_pk_your_publishable_key" \
-H "Origin: https://www.your-venue.example" \
-H "Content-Type: application/json" \
-d '{ "buyerName": "Ada Lovelace", "email": "ada@example.com", "quantity": 2, "attendees": ["", "Grace Hopper"] }'JavaScript
try {
const registration = await client.registerForEvent('darts-league-night', {
buyerName: 'Ada Lovelace', email: 'ada@example.com', quantity: 2, attendees: ['', 'Grace Hopper']
});
console.log(registration.reference, registration.tickets.map((t) => t.code), registration.cancelUrl);
} catch (err) {
if (err.name === 'BookDinePlayError' && err.status === 409) console.log(err.reason); // 'sold-out', ...
if (err.name === 'BookDinePlayError' && err.status === 400) console.log(err.messages()); // the errors, flattened
}C#
try
{
var registration = await client.RegisterForEventAsync("your-venue", "darts-league-night",
new EventRegistrationRequest { BuyerName = "Ada Lovelace", Email = "ada@example.com", Quantity = 2, Attendees = ["", "Grace Hopper"] },
cancellationToken);
}
catch (BookDinePlayApiException ex) when (ex.Type == BookDinePlayApiException.RegistrationClosedType)
{
Console.WriteLine(ex.Reason); // "closed", "sold-out", "past" or "not-open"; ex.Remaining is 0 when sold out
}
catch (BookDinePlayApiException ex) when (ex.StatusCode == HttpStatusCode.BadRequest)
{
Console.WriteLine(string.Join(" ", ex.Messages)); // ex.Errors: field (or "registration") -> messages
}POST /api/venues/{venueSlug}/events/{eventSlug}/tickets/checkout
Buy tickets for a Paid event: opens a Stripe Checkout session on the venue's own connected Stripe account and returns the URL to send the guest to. The registration — tickets included — is minted as a payment hold before Stripe is even called, so the seats are reserved the instant the guest commits rather than only once they pay; the hold — and the Checkout session — expire after 30 minutes if the guest never pays, and the seats are released automatically. The confirmation e-mail carrying the tickets waits for the payment to actually confirm: poll or land the guest back on successUrl to find out (What the guest receives). Refused with 409 ticket-sales-not-enabled unless the tenant's plan includes ticket sales, the venue's Stripe Connect payout account is Enabled, and the event is actually Paid — see the note at the top of this page.
Request body (Content-Type: application/json) — the same fields as registration, plus two optional return URLs and an optional gift voucher:
| Field | Required | Meaning |
|---|---|---|
buyerName, email, phone, notes, quantity, attendees |
as above | Same rules as registration |
successUrl |
no | Where Stripe Checkout sends the guest after paying. Must be https and either on this API key's origin allowlist or the platform's own guest app, or the request is refused with 400 (errors.successUrl); omitted uses the built-in /r/{cancelToken} registration page |
cancelUrl |
no | Where Checkout sends the guest if they abandon it. Same rule as successUrl (errors.cancelUrl on failure) |
voucherCode |
no | A gift voucher of this venue to pay part or all of the tickets with. Only an amount voucher pays tickets. Part of what an Idempotency-Key guards; never log it |
Response 201 Created — a Checkout session, not yet paid:
| Field | Type | Meaning |
|---|---|---|
reference |
string | BDP-E-XXXX — display only |
checkoutUrl |
string | Send the guest here — a redirect, or window.location.assign — to pay |
expiresAt |
ISO 8601 instant | When the Checkout session, and the seats it holds, expire unpaid |
cancelToken |
string | The credential for GET /api/registrations/{cancelToken} and the cancel route; keep it out of logs |
voucherAmount |
decimal or null | What the voucher pays; null without one |
amountDue |
decimal or null | What the card pays when a voucher was given. 0 means the voucher paid everything: no payment page was opened, the tickets are issued, and checkoutUrl is the success page |
{
"reference": "BDP-E-K7QM",
"checkoutUrl": "https://checkout.stripe.com/c/pay/cs_test_a1b2c3…",
"expiresAt": "2027-10-16T18:30:00+00:00",
"cancelToken": "dqN_AejenL2r7qXPdZuOxnLY9_BfVkTl"
}With a voucher, the voucher's value is held together with the seats and taken when the payment lands; the card pays the rest, at least €0.50 (Paying with a voucher). A voucher that covers everything confirms the registration at once, without Stripe and without the payout account the card needs; checkoutUrl is then your successUrl, or the built-in registration page. A refund — by the venue, on a guest's cancel the refund policy allows, or when the event is cancelled — puts the voucher part back on the voucher.
Also accepts the optional Idempotency-Key header — the sharpest use of it in this API: the seat hold is minted before Stripe is ever called, so a byte-identical retry (a resilience handler's own retry on a 5xx/timeout, or a guest pressing "buy" twice) replays the SAME checkout URL rather than orphaning a second 30-minute hold on an event that may be near sold out. See Idempotency problems.
Errors
| Status | When |
|---|---|
400 |
A field fails validation, or a registration rule does — same errors shape as registration |
400 |
successUrl or cancelUrl is not https, or its origin is not on this key's allowlist — errors.successUrl / errors.cancelUrl |
404 |
No such venue, or no published event with that slug |
409 |
registration-closed: the window is shut |
409 |
ticket-sales-not-enabled: plan, payout account or event mode refuses the sale |
409 |
idempotency-key-reused: the Idempotency-Key sent was already used for a request with a different body — the first attempt's Checkout session is left untouched |
409 |
checkout-in-progress: a retry landed while the first attempt's Checkout session was still being created — wait a moment and retry with the same key |
400, 404, 409 |
With a voucher: one of the gift voucher problems, or 409 with reason below-card-minimum when the total is €0.50 or less and the voucher does not cover it |
curl
curl -X POST "https://api.bookdineplay.com/api/venues/your-venue/events/gala-night/tickets/checkout" \
-H "X-BookDinePlay-Key: bdp_pk_your_publishable_key" \
-H "Origin: https://www.your-venue.example" \
-H "Content-Type: application/json" \
-d '{ "buyerName": "Ada Lovelace", "email": "ada@example.com", "quantity": 2, "attendees": ["", "Grace Hopper"], "successUrl": "https://www.your-venue.example/event/gala-night?paid=1", "cancelUrl": "https://www.your-venue.example/event/gala-night?cancelled=1" }'JavaScript
const response = await fetch('https://api.bookdineplay.com/api/venues/your-venue/events/gala-night/tickets/checkout', {
method: 'POST',
headers: { 'X-BookDinePlay-Key': 'bdp_pk_your_publishable_key', 'Content-Type': 'application/json' },
body: JSON.stringify({
buyerName: 'Ada Lovelace', email: 'ada@example.com', quantity: 2, attendees: ['', 'Grace Hopper'],
successUrl: location.origin + '/event/gala-night?paid=1',
cancelUrl: location.origin + '/event/gala-night?cancelled=1'
})
});
if (response.ok) {
const checkout = await response.json();
window.location.assign(checkout.checkoutUrl);
}C#
using var response = await http.PostAsync("api/venues/your-venue/events/gala-night/tickets/checkout", JsonContent.Create(new
{
buyerName = "Ada Lovelace", email = "ada@example.com", quantity = 2, attendees = new[] { "", "Grace Hopper" },
successUrl = "https://example.com/event/gala-night?paid=1",
cancelUrl = "https://example.com/event/gala-night?cancelled=1"
}), cancellationToken);GET /api/tickets/{code}
Everything the ticket page shows, by the 12-character code. No key: the code is the credential. Input is normalized — abcd-efgh-jklm resolves like ABCDEFGHJKLM. Not plan-gated.
Response 200 OK
| Field | Type | Meaning |
|---|---|---|
code, attendeeName |
string | This ticket |
status |
string | Valid, CheckedIn (scanned at the door) or Cancelled |
checkedInAt |
ISO 8601 instant or null | When it was scanned |
reference, registrationStatus |
string | The registration it belongs to and its status (Confirmed, Cancelled) |
venueSlug, venueName |
string | The venue |
event |
object | The list shape of the event, status included — a ticket for a cancelled event still reads Valid, so check event.status too |
ticketUrl |
string | The ticket page on app.bookdineplay.com |
qrSvg |
string | An inline SVG of a QR code that encodes ticketUrl; insert it as HTML |
{
"code": "X7QZUP5AC3GH",
"attendeeName": "Ada Lovelace",
"status": "Valid",
"checkedInAt": null,
"reference": "BDP-E-ESJA",
"registrationStatus": "Confirmed",
"venueSlug": "demo-sportsbar",
"venueName": "The Neon Tap",
"event": {
"slug": "darts-league-night",
"title": "Darts league night",
"excerpt": "Eight boards, one winner — sign up your team.",
"imageUrl": null,
"imageAlt": null,
"status": "Published",
"date": "2027-10-16",
"start": "20:00",
"end": "23:00",
"timeLabel": "Doors 19:30",
"locationLabel": "The Neon Tap, Boxhagener Straße 42, 10245 Berlin",
"registrationMode": "Free",
"language": "en"
},
"ticketUrl": "https://app.bookdineplay.com/ticket/X7QZUP5AC3GH",
"qrSvg": "<svg …>…</svg>"
}(attributes shortened)
Errors — 404 for an unknown or malformed code.
curl
curl "https://api.bookdineplay.com/api/tickets/ABCDEFGHJKLM"JavaScript
const ticket = await client.ticket('ABCDEFGHJKLM'); // sends no key
document.querySelector('#qr').innerHTML = ticket.qrSvg;C#
var ticket = await client.GetTicketAsync("ABCDEFGHJKLM", cancellationToken); // sends no key; null when unknownGET /api/registrations/{cancelToken}
A guest's own registration and ticket page in one call: status, payment state, tickets (once issued) and the refund policy that applies — everything /r/{cancelToken} on app.bookdineplay.com renders, and what a ?session_id= return from Checkout polls while payment is Pending. No key: the cancel token in the URL is itself the credential, the same reasoning as the ticket and cancel routes. Not plan-gated.
Response 200 OK
| Field | Type | Meaning |
|---|---|---|
reference |
string | BDP-E-XXXX |
eventTitle, eventSlug, venueSlug |
string | Which event, and where |
status |
string | Confirmed, Cancelled, or PendingPayment while still completing Stripe Checkout |
payment |
string | NotRequired (free), Pending, Paid or Refunded |
quantity |
integer | Tickets bought |
tickets[] |
object[] | Minted with the payment hold itself, so codes exist even while payment is still Pending — capacity is reserved from the moment the guest commits, not only once they pay. One per attendee, same shape as registration's tickets[] |
refundPolicy |
object | See The refund policy |
canCancel |
boolean | Whether the guest's own cancel button should be shown right now |
cancelNote |
string or null | Why not, when canCancel is false: already cancelled, a ticket checked in, or the refund policy no longer allows an online refund for a Paid registration |
{
"reference": "BDP-E-K7QM",
"eventTitle": "Gala night",
"eventSlug": "gala-night",
"venueSlug": "demo-sportsbar",
"status": "Confirmed",
"payment": "Paid",
"quantity": 2,
"tickets": [
{ "code": "X7QZUP5AC3GH", "attendeeName": "Ada Lovelace", "ticketUrl": "https://app.bookdineplay.com/ticket/X7QZUP5AC3GH" },
{ "code": "DR9HN4GDX9ZH", "attendeeName": "Grace Hopper", "ticketUrl": "https://app.bookdineplay.com/ticket/DR9HN4GDX9ZH" }
],
"refundPolicy": { "mode": "FullRefundUntil", "hoursBeforeStart": 24, "refundableUntil": "2027-10-15T20:00:00+00:00" },
"canCancel": true,
"cancelNote": null
}Errors — 404 for an unknown or malformed token.
curl
curl "https://api.bookdineplay.com/api/registrations/dqN_AejenL2r7qXPdZuOxnLY9_BfVkTl"JavaScript
const registration = await client.registration(cancelToken); // sends no key
if (registration.payment === 'Pending') { /* poll again in a few seconds */ }C#
var registration = await client.GetRegistrationAsync(cancelToken, cancellationToken); // sends no key; null when unknownPOST /api/event-registrations/{cancelToken}/cancel
Cancels the registration behind a cancel link and frees its seats; the guest receives a confirmation e-mail. For a Paid registration, this also refunds it — but only when the event's refund policy allows an online refund right now; otherwise the registration is left untouched (nothing cancelled, no charge touched) and the call answers 409 refund-not-available instead. Check canCancel on GET /api/registrations/{cancelToken} first to avoid the round trip. 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, status (Cancelled), eventSlug, eventTitle.
{
"reference": "BDP-E-ESJA",
"status": "Cancelled",
"eventSlug": "darts-league-night",
"eventTitle": "Darts league night"
}Errors — 404 for an unknown token; 409 refund-not-available for a Paid registration the refund policy no longer allows an online refund for; 409 once a ticket of the registration has been checked in (only the venue can cancel then).
curl
curl -X POST "https://api.bookdineplay.com/api/event-registrations/<cancelToken>/cancel"JavaScript
const token = location.pathname.split('/').pop();
const result = await client.cancelEventRegistration(token); // sends no keyC#
var result = await client.CancelEventRegistrationAsync(cancelToken, cancellationToken); // sends no key; null when unknownWhy three routes take no key
Every /api/venues/** route needs a key because a page on the venue's site makes the call, and that page carries the venue's publishable key. A guest who opens a ticket link, their registration link, or a cancel link from their e-mail has no such page: they land on app.bookdineplay.com, or on a page of yours that only knows the code or token from the URL. Requiring a key there would either force a credential into an e-mailed URL or break the link. The ticket code and the cancel token are already per-registration credentials — the same reasoning as the printed QR token behind QR codes and table sessions — so these three routes hang off the API root, outside the venue prefix, and both SDKs deliberately send no key on them.
What the guest receives
- On registration or a completed paid checkout — one e-mail with every ticket (code, QR code, a link to its ticket page) and the cancel link. Sent in the language of the request (
Accept-Language), falling back to the event'slanguage. - While a paid checkout is still pending — nothing yet; the guest is on
successUrl, or on the built-in registration page when the request named none, and it pollsGET /api/registrations/{cancelToken}untilpaymentleavesPending. The built-in page has the token in its path; asuccessUrlof your own is passed through unchanged, so carry thecancelTokenfrom the checkout response yourself if that page has to poll. - On cancellation — a confirmation e-mail, whether the guest cancelled through the link or the venue cancelled the registration in the console.
- When the venue cancels the event — every confirmed registrant is told by e-mail.
The pages behind those links live on app.bookdineplay.com and need nothing from your site:
| Page | URL | Shows |
|---|---|---|
| Ticket | https://app.bookdineplay.com/ticket/{code} |
The ticket with its QR code, the attendee, the event and the venue — what the venue checks in at the door |
| Registration | https://app.bookdineplay.com/r/{cancelToken} |
Status, payment state (polling while pending), tickets once issued, the refund policy and a cancel button — what a checkout's default successUrl sends the guest to |
| Cancel | https://app.bookdineplay.com/e/{cancelToken} |
The registration and a button that cancels it |
A site with its own event pages can render all three itself from the keyless routes above; ticketUrl and cancelUrl in the registration response keep pointing at the BookDinePlay pages, so the e-mails work either way.
Next steps
- JavaScript SDK: Events — the events widget, and the client calls for your own markup.
- Errors —
registration-closed,ticket-sales-not-enabledandrefund-not-availablein detail. - .NET SDK — the same calls from a server.