{"openapi":"3.1.0","info":{"title":"BookMyVenue public partner API","version":"1","description":"The public partner API of BookMyVenue: read a venue's services and availability, create and manage bookings, and receive booking events by webhook.\n\n## Authentication\nEvery path except this document needs an API key in the `Authorization` header: `Authorization: Bearer bmv_sk_live_…` or `bmv_sk_test_…` (the prefix plus 40 letters and digits). There are two kinds of key. A **venue key** belongs to exactly ONE venue, which is why the appointment paths carry no venue id; it is issued to the venue by its owner or manager inside BookMyVenue. An **agent key** (for AI booking agents) is issued by BookMyVenue itself, is not tied to one venue, and names the venue on every call (`venue` = the venue's id or booking-page slug); agent keys can use the stays, tables and events paths. Issuing keys is not part of this API.\n\n**Keys travel in the Authorization header ONLY.** A request that carries any of the query parameters `api_key`, `apikey`, `key`, `access_token` or `token` is REFUSED with 400 `key_in_query` — query strings are written to access logs and forwarded in Referer headers. Treat a key sent that way as compromised and rotate it.\n\n## Scopes\nEach key holds one or more of: `read:venue`, `read:services`, `read:availability`, `read:bookings`, `write:bookings`, `webhooks:manage`, `read:stays`, `write:stays`, `read:tables`, `write:tables`, `read:events`, `write:events`. Each operation names its scope in `security` and in `x-bmv-scope`. A key without it gets 403 `insufficient_scope`.\n\n## Order of checks\nVersion (404 `unknown_api_version` for any version but v1) → key in the URL (400 `key_in_query`) → key present (401 `auth_required`) → key valid (401 `invalid_key`) → version sunset (410 `api_version_sunset`) → rate limit and daily quota (429) → scope (403) → the operation. An unknown path with a valid key answers 404 `not_found` and counts against the quota.\n\n## Limits\nPer KEY, not per IP. Each key has a per-minute rate and a daily quota (defaults 120 per minute and 10,000 per day; set per key). Over the rate: 429 `rate_limited` with `retry-after`. Quota spent: 429 `quota_exceeded`; it resets at the venue's local midnight. Successful authenticated responses carry `bmv-quota-remaining`.\n\n## Versioning\nThe version is in the path. Deprecation and sunset follow RFC 8594 (`deprecation`, `sunset`, `link rel=\"successor-version\"` headers). After the sunset date the version answers 410.\n\n## Errors\nEvery error body is `{\"error\": \"<code>\", \"detail\": \"<text>\"}`. `error` is stable — code against it. `detail` is prose for humans, is often omitted, and may change. Booking rules are enforced by the database; a rule violation arrives as 409 with the rule's name as `error`. Each response lists its known codes in `x-bmv-error-codes`. Any fault the API does not map answers 500 `internal_error` with no detail.\n\n## Conventions\n- Money is integer cents. The currency is not returned by this API.\n- Timestamps in responses are UTC ISO 8601 (RFC 3339). Some are formatted by the database without fractional seconds (`2026-01-02T03:04:05Z`); others are serialised by the server with milliseconds (`2026-01-02T03:04:05.000Z`). Parse both.\n- Request bodies are JSON, at most 16 KiB (413 `payload_too_large`); a body that is not JSON answers 400 `invalid_json`.\n- Idempotency: `POST /bookings` takes an optional uuid key and replays the original booking; reschedule and cancel REQUIRE a key and replay their stored response. See each operation.\n\n## Accommodation stays and AI agents (`x-bmv-served-by: next`)\nThe `/api/public/v1/stays/*` paths are served by the app host. Errors there are `{\"error\", \"message\", \"detail\"}` (`message` and `detail` carry the same sentence). Read `x-bmv-agent` at the top of this document before booking on someone's behalf: bookings made by an agent key are INSTANT and FINAL at most venues, the guest and the venue are told it was booked by an AI agent, and the guest gets a link to cancel.\n\n## One implementation (P2)\nEvery kind (appointments, stays, tables and events) is served by ONE implementation, the Next app: operations marked `x-bmv-served-by: both` are also still answered, identically, by the legacy node API on the apex until the owner-approved deploy, when nginx routes `/api/public/` on both hosts to Next and the legacy apex process is retired. Agent keys (issued by the platform, never by a venue) work on stays, tables and events; venue keys work on everything their scopes allow."},"servers":[{"url":"https://bookmyvenue.com.au","description":"Apex. /api/public/v1/* here is the legacy node API until the P2 nginx block routes it to the Next app; the stays paths are not served here until then."},{"url":"https://app.bookmyvenue.com.au","description":"App host (Next.js). Serves this document and every path marked `x-bmv-served-by: next` (the stays paths)."}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Meta"},{"name":"Venue"},{"name":"Services"},{"name":"Availability"},{"name":"Bookings"},{"name":"Webhooks"},{"name":"Tables","description":"Restaurant / hospitality tables (app host). Agent keys: read x-bmv-agent."},{"name":"Events","description":"Event tickets (app host). Agent keys: read x-bmv-agent. The guest pays; the agent never does."},{"name":"Stays","description":"Accommodation stays (app host). Agent keys: read x-bmv-agent."}],"paths":{"/api/public/v1/openapi.json":{"get":{"tags":["Meta"],"operationId":"getOpenApiSpec","summary":"This document","description":"Unauthenticated on purpose: a machine consumer must be able to learn the interface before it holds a key. No rate limit, quota or version headers apply to this path.","security":[],"x-bmv-served-by":"both","responses":{"200":{"description":"The OpenAPI document (this file).","content":{"application/json":{"schema":{"type":"object"}}}},"500":{"description":"Server error. `error` is `spec_unavailable`: the server could not read its copy of the spec. No `detail` is sent.","x-bmv-error-codes":["spec_unavailable"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/public/v1/venue":{"get":{"tags":["Venue"],"operationId":"getVenue","summary":"The venue this key belongs to","description":"A key resolves to exactly one venue; there is no venue id in any path.","security":[{"bearerAuth":["read:venue"]}],"x-bmv-scope":"read:venue","x-bmv-served-by":"both","responses":{"200":{"description":"The venue.","headers":{"bmv-api-version":{"$ref":"#/components/headers/BmvApiVersion"},"bmv-quota-remaining":{"$ref":"#/components/headers/BmvQuotaRemaining"},"deprecation":{"$ref":"#/components/headers/Deprecation"},"sunset":{"$ref":"#/components/headers/Sunset"},"link":{"$ref":"#/components/headers/Link"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VenueResponse"}}}},"400":{"$ref":"#/components/responses/KeyInQuery"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/InsufficientScope"},"404":{"description":"Not found. `error` is one of: `venue_not_found`. `venue_not_found`: the venue is no longer active.","x-bmv-error-codes":["venue_not_found"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"$ref":"#/components/responses/VersionSunset"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/public/v1/services":{"get":{"tags":["Services"],"operationId":"listServices","summary":"Bookable services with their practitioners","description":"Active services with online booking enabled, ordered by the venue's sort order then name. Each service lists the active, online-bookable practitioners qualified to deliver it (empty array when none).","security":[{"bearerAuth":["read:services"]}],"x-bmv-scope":"read:services","x-bmv-served-by":"both","responses":{"200":{"description":"The service catalogue.","headers":{"bmv-api-version":{"$ref":"#/components/headers/BmvApiVersion"},"bmv-quota-remaining":{"$ref":"#/components/headers/BmvQuotaRemaining"},"deprecation":{"$ref":"#/components/headers/Deprecation"},"sunset":{"$ref":"#/components/headers/Sunset"},"link":{"$ref":"#/components/headers/Link"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceList"}}}},"400":{"$ref":"#/components/responses/KeyInQuery"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/InsufficientScope"},"410":{"$ref":"#/components/responses/VersionSunset"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/public/v1/availability":{"get":{"tags":["Availability"],"operationId":"getAvailability","summary":"Open start times for one service on one day","description":"Slots come from the database function `appt_availability`, which applies the venue's roster, existing bookings, closures and notice rules. Ordered by start time, then practitioner name. One entry per (start time, practitioner).","security":[{"bearerAuth":["read:availability"]}],"x-bmv-scope":"read:availability","x-bmv-served-by":"both","parameters":[{"$ref":"#/components/parameters/AvailabilityService"},{"$ref":"#/components/parameters/AvailabilityDate"},{"$ref":"#/components/parameters/AvailabilityPractitioner"}],"responses":{"200":{"description":"Open slots.","headers":{"bmv-api-version":{"$ref":"#/components/headers/BmvApiVersion"},"bmv-quota-remaining":{"$ref":"#/components/headers/BmvQuotaRemaining"},"deprecation":{"$ref":"#/components/headers/Deprecation"},"sunset":{"$ref":"#/components/headers/Sunset"},"link":{"$ref":"#/components/headers/Link"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Availability"}}}},"400":{"description":"Bad request. `error` is one of: `key_in_query`, `bad_service`, `bad_date`, `bad_practitioner`, `bad_value`. `key_in_query`: a key was sent in the query string (see the API description); treat that key as compromised. `bad_service` / `bad_practitioner`: not a uuid. `bad_date`: not `YYYY-MM-DD`. `bad_value`: a value the database could not cast (e.g. an impossible calendar date).","x-bmv-error-codes":["key_in_query","bad_service","bad_date","bad_practitioner","bad_value"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/InsufficientScope"},"404":{"description":"Not found. `error` is one of: `venue_not_found`.","x-bmv-error-codes":["venue_not_found"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"$ref":"#/components/responses/VersionSunset"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/public/v1/bookings":{"get":{"tags":["Bookings"],"operationId":"listBookings","summary":"List bookings (cursor-paginated, no contact details)","description":"Ordered by start time, then booking id. The list carries the guest's NAME only — no phone, email, notes, tags or intake; fetch a single booking for contact details. Page with `next_cursor`: pass it back as `cursor` with the SAME filters; `next_cursor: null` means the last page.","security":[{"bearerAuth":["read:bookings"]}],"x-bmv-scope":"read:bookings","x-bmv-served-by":"both","parameters":[{"$ref":"#/components/parameters/ListFrom"},{"$ref":"#/components/parameters/ListTo"},{"$ref":"#/components/parameters/ListStatus"},{"$ref":"#/components/parameters/ListCursor"},{"$ref":"#/components/parameters/ListLimit"}],"responses":{"200":{"description":"One page of bookings.","headers":{"bmv-api-version":{"$ref":"#/components/headers/BmvApiVersion"},"bmv-quota-remaining":{"$ref":"#/components/headers/BmvQuotaRemaining"},"deprecation":{"$ref":"#/components/headers/Deprecation"},"sunset":{"$ref":"#/components/headers/Sunset"},"link":{"$ref":"#/components/headers/Link"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingList"}}}},"400":{"description":"Bad request. `error` is one of: `key_in_query`, `bad_date`, `bad_cursor`, `bad_value`. `key_in_query`: a key was sent in the query string (see the API description); treat that key as compromised. `bad_date`: `from`/`to` not `YYYY-MM-DD`. `bad_cursor`: a cursor this server did not issue, or a mangled one.","x-bmv-error-codes":["key_in_query","bad_date","bad_cursor","bad_value"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/InsufficientScope"},"410":{"$ref":"#/components/responses/VersionSunset"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"post":{"tags":["Bookings"],"operationId":"createBooking","summary":"Create a booking","description":"Calls the database write path `api_book_appointment`, the same one every other channel uses; the venue's rules (confirmation gate, deposits, practitioner validity, notice, intake) apply there. When no `practitioner_id` is sent the venue picks a qualified practitioner, and the server retries its pick up to 3 times if the slot is taken in a race.\n\nIdempotency: send a uuid as the `Idempotency-Key` header or as `idempotency_key` in the body (the body value wins). Repeating the request with the same key replays the ORIGINAL booking (200, `replayed: true`) instead of making a second one. Without a key a retry can create a duplicate booking.\n\nStatus of the new booking: `confirmed`; `requested` when the venue confirms bookings by hand; `pending` when a payment is due, in which case `payment.checkout_url` is a Stripe Checkout page for the guest.","security":[{"bearerAuth":["write:bookings"]}],"x-bmv-scope":"write:bookings","x-bmv-served-by":"both","x-bmv-next-difference":"On the Next implementation a service that owes a deposit and is not behind the venue’s confirmation gate answers 503 `payments_not_configured` and nothing is written (the legacy process minted a Stripe Checkout when it had a Stripe key); gated venues and deposit-free services book exactly as before.","parameters":[{"$ref":"#/components/parameters/IdempotencyKeyCreate"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingCreateRequest"}}},"description":"JSON, at most 16 KiB."},"responses":{"200":{"description":"Replay: a booking with this idempotency key already exists; it is returned unchanged (`replayed: true`).","headers":{"bmv-api-version":{"$ref":"#/components/headers/BmvApiVersion"},"bmv-quota-remaining":{"$ref":"#/components/headers/BmvQuotaRemaining"},"deprecation":{"$ref":"#/components/headers/Deprecation"},"sunset":{"$ref":"#/components/headers/Sunset"},"link":{"$ref":"#/components/headers/Link"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingCreated"}}}},"201":{"description":"Booking created.","headers":{"bmv-api-version":{"$ref":"#/components/headers/BmvApiVersion"},"bmv-quota-remaining":{"$ref":"#/components/headers/BmvQuotaRemaining"},"deprecation":{"$ref":"#/components/headers/Deprecation"},"sunset":{"$ref":"#/components/headers/Sunset"},"link":{"$ref":"#/components/headers/Link"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingCreated"}}}},"400":{"description":"Bad request. `error` is one of: `key_in_query`, `bad_service`, `bad_practitioner`, `bad_starts_at`, `name_required`, `bad_idempotency_key`, `bad_source_ref`, `invalid_json`, `bad_value`, `guest_name_required`, `bad_deposit_amount`. `key_in_query`: a key was sent in the query string (see the API description); treat that key as compromised.","x-bmv-error-codes":["key_in_query","bad_service","bad_practitioner","bad_starts_at","name_required","bad_idempotency_key","bad_source_ref","invalid_json","bad_value","guest_name_required","bad_deposit_amount"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/InsufficientScope"},"404":{"description":"Not found. `error` is one of: `venue_not_found`, `no_such_service`, `practitioner_not_found`.","x-bmv-error-codes":["venue_not_found","no_such_service","practitioner_not_found"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict. `error` is one of: `slot_taken`, `no_practitioner_available`, `practitioner_not_qualified`, `guest_contact_required`. Booking rules are enforced by the database, not the transport. A rule violation it raises that is not listed here still arrives as 409 with the rule's name as `error`; `detail`, when present, is the database's hint.","x-bmv-error-codes":["slot_taken","no_practitioner_available","practitioner_not_qualified","guest_contact_required"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"$ref":"#/components/responses/VersionSunset"},"413":{"description":"Payload too large. `error` is one of: `payload_too_large`.","x-bmv-error-codes":["payload_too_large"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Unprocessable. `error` is one of: `payment_setup_failed`, `payment_provider_error`. The payment provider refused the checkout request; `detail` carries the provider's message. The booking may already be recorded — retry with the SAME idempotency key to replay it.","x-bmv-error-codes":["payment_setup_failed","payment_provider_error"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"Service unavailable. `error` is one of: `payments_not_configured`. `payments_not_configured`: the service needs a deposit and the venue has no payment rail.","x-bmv-error-codes":["payments_not_configured"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/public/v1/bookings/{booking_id}":{"get":{"tags":["Bookings"],"operationId":"getBooking","summary":"One booking, with guest contact details","description":"Returns the guest's name, phone and email. Notes, tags, intake answers and clinical content are never returned, under any scope.","security":[{"bearerAuth":["read:bookings"]}],"x-bmv-scope":"read:bookings","x-bmv-served-by":"both","parameters":[{"$ref":"#/components/parameters/BookingId"}],"responses":{"200":{"description":"The booking.","headers":{"bmv-api-version":{"$ref":"#/components/headers/BmvApiVersion"},"bmv-quota-remaining":{"$ref":"#/components/headers/BmvQuotaRemaining"},"deprecation":{"$ref":"#/components/headers/Deprecation"},"sunset":{"$ref":"#/components/headers/Sunset"},"link":{"$ref":"#/components/headers/Link"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingResponse"}}}},"400":{"description":"Bad request. `error` is one of: `key_in_query`, `bad_value`. `key_in_query`: a key was sent in the query string (see the API description); treat that key as compromised. `bad_value`: the path id matched the route but is not a valid uuid.","x-bmv-error-codes":["key_in_query","bad_value"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/InsufficientScope"},"404":{"description":"Not found. `error` is one of: `no_such_booking`.","x-bmv-error-codes":["no_such_booking"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"$ref":"#/components/responses/VersionSunset"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/public/v1/bookings/{booking_id}/reschedule":{"post":{"tags":["Bookings"],"operationId":"rescheduleBooking","summary":"Move a booking to a new start time","description":"Calls `api_reschedule_booking` (a venue key) or `api_reschedule_appointment_agent` (an agent key, only when the venue's `agent_may_reschedule` option is on — off answers 409 `not_reschedulable`). Notice period, move cap and slot availability are decided by the database. An `Idempotency-Key` is REQUIRED (header or body). A completed request replays its stored status and body with `bmv-idempotent-replay: true`.","security":[{"bearerAuth":["write:bookings"]}],"x-bmv-scope":"write:bookings","x-bmv-served-by":"both","parameters":[{"$ref":"#/components/parameters/BookingId"},{"$ref":"#/components/parameters/IdempotencyKeyWrite"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RescheduleRequest"}}},"description":"JSON, at most 16 KiB."},"responses":{"200":{"description":"Rescheduled.","headers":{"bmv-api-version":{"$ref":"#/components/headers/BmvApiVersion"},"bmv-quota-remaining":{"$ref":"#/components/headers/BmvQuotaRemaining"},"deprecation":{"$ref":"#/components/headers/Deprecation"},"sunset":{"$ref":"#/components/headers/Sunset"},"link":{"$ref":"#/components/headers/Link"},"bmv-idempotent-replay":{"$ref":"#/components/headers/BmvIdempotentReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RescheduleResult"}}}},"400":{"description":"Bad request. `error` is one of: `key_in_query`, `idempotency_key_required`, `bad_starts_at`, `bad_practitioner`, `invalid_json`, `bad_value`. `key_in_query`: a key was sent in the query string (see the API description); treat that key as compromised. `idempotency_key_required`: no key, or one shorter than 8 characters.","x-bmv-error-codes":["key_in_query","idempotency_key_required","bad_starts_at","bad_practitioner","invalid_json","bad_value"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Forbidden. `error` is one of: `insufficient_scope`, `forbidden`. `insufficient_scope`: the key lacks the scope this operation needs (`detail` names it).","x-bmv-error-codes":["insufficient_scope","forbidden"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found. `error` is one of: `no_such_appointment`, `practitioner_not_found`.","x-bmv-error-codes":["no_such_appointment","practitioner_not_found"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict. `error` is one of: `idempotency_key_in_flight`, `practitioner_not_qualified`, `slot_not_available`, `slot_taken`. `idempotency_key_in_flight`: a request with this key is still running; retry shortly. Booking rules are enforced by the database, not the transport. A rule violation it raises that is not listed here still arrives as 409 with the rule's name as `error`; `detail`, when present, is the database's hint. A key longer than 255 characters is also refused with 409, under an `error` string that is NOT a stable code.","x-bmv-error-codes":["idempotency_key_in_flight","practitioner_not_qualified","slot_not_available","slot_taken"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"$ref":"#/components/responses/VersionSunset"},"413":{"description":"Payload too large. `error` is one of: `payload_too_large`.","x-bmv-error-codes":["payload_too_large"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Unprocessable. `error` is one of: `idempotency_key_reuse`. `idempotency_key_reuse`: this key was already used for this operation with a different body.","x-bmv-error-codes":["idempotency_key_reuse"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/public/v1/bookings/{booking_id}/cancel":{"post":{"tags":["Bookings"],"operationId":"cancelBooking","summary":"Cancel a booking","description":"Calls `api_cancel_booking`. Cancelling an already-cancelled booking is a no-op that answers 200. An `Idempotency-Key` is REQUIRED (header or body). A completed request replays its stored status and body with `bmv-idempotent-replay: true`.","security":[{"bearerAuth":["write:bookings"]}],"x-bmv-scope":"write:bookings","x-bmv-served-by":"both","parameters":[{"$ref":"#/components/parameters/BookingId"},{"$ref":"#/components/parameters/IdempotencyKeyWrite"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelRequest"}}},"description":"JSON, at most 16 KiB. May be empty only if the key is sent as a header."},"responses":{"200":{"description":"Cancelled.","headers":{"bmv-api-version":{"$ref":"#/components/headers/BmvApiVersion"},"bmv-quota-remaining":{"$ref":"#/components/headers/BmvQuotaRemaining"},"deprecation":{"$ref":"#/components/headers/Deprecation"},"sunset":{"$ref":"#/components/headers/Sunset"},"link":{"$ref":"#/components/headers/Link"},"bmv-idempotent-replay":{"$ref":"#/components/headers/BmvIdempotentReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelResult"}}}},"400":{"description":"Bad request. `error` is one of: `key_in_query`, `idempotency_key_required`, `invalid_json`, `bad_value`. `key_in_query`: a key was sent in the query string (see the API description); treat that key as compromised. `idempotency_key_required`: no key, or one shorter than 8 characters.","x-bmv-error-codes":["key_in_query","idempotency_key_required","invalid_json","bad_value"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/InsufficientScope"},"404":{"description":"Not found. `error` is one of: `no_such_appointment`.","x-bmv-error-codes":["no_such_appointment"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict. `error` is one of: `idempotency_key_in_flight`, `bad_transition`. `idempotency_key_in_flight`: a request with this key is still running; retry shortly. Booking rules are enforced by the database, not the transport. A rule violation it raises that is not listed here still arrives as 409 with the rule's name as `error`; `detail`, when present, is the database's hint. A key longer than 255 characters is also refused with 409, under an `error` string that is NOT a stable code. `bad_transition`: the booking's current status cannot move to cancelled.","x-bmv-error-codes":["idempotency_key_in_flight","bad_transition"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"$ref":"#/components/responses/VersionSunset"},"413":{"description":"Payload too large. `error` is one of: `payload_too_large`.","x-bmv-error-codes":["payload_too_large"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Unprocessable. `error` is one of: `idempotency_key_reuse`. `idempotency_key_reuse`: this key was already used for this operation with a different body.","x-bmv-error-codes":["idempotency_key_reuse"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/public/v1/bookings/{booking_id}/confirm":{"post":{"tags":["Stays"],"operationId":"confirmBookingAsGuest","summary":"The GUEST confirms a request-mode agent booking (token from their message)","description":"NOT KEYED — the only unkeyed write in this API. When an agent key books a venue that holds agent bookings as REQUESTS, the guest is sent a message with a one-time confirmation token; posting it here flips the booking `requested` → `confirmed` ONCE through the same status path every other channel uses (db/281 `agent_confirm_by_token`), and the venue is notified by the database itself. The token is NOT the manage token — the manage token is handed to the agent, so an agent can never confirm on the guest’s behalf. A second post with the same token answers 200 with `already: true`. POST only: a GET is 405, so link scanners that prefetch cannot confirm. A token for another booking, or a malformed token/id, is the same 404 as none (no oracle).","security":[],"x-bmv-auth":"guest_token","x-bmv-served-by":"next","parameters":[{"$ref":"#/components/parameters/BookingId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GuestConfirmRequest"}}},"description":"JSON: the confirmation token from the guest’s message."},"responses":{"200":{"description":"Confirmed (or already confirmed by this token: `already: true`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GuestConfirmResult"}}}},"400":{"description":"Bad request. `error` is one of: `invalid_json`.","x-bmv-error-codes":["invalid_json"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found. `error` is one of: `not_found` — no such token for this booking (also the answer for a malformed token or id). `message` is a sentence for the guest.","x-bmv-error-codes":["not_found"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GuestConfirmError"}}}},"405":{"description":"GET. Only POST confirms; the `allow` header says so."},"409":{"description":"Cannot confirm. `error` is one of: `not_confirmable` (the booking is no longer `requested`; `status` says what it is now), `venue_confirms` (the venue confirms this one itself), `unit_taken` (the room went to someone else while the request waited), `confirm_failed` (the status change was refused by a booking rule; nothing changed). `message` is a sentence for the guest.","x-bmv-error-codes":["not_confirmable","venue_confirms","unit_taken","confirm_failed"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GuestConfirmError"}}}}}}},"/api/public/v1/webhook-endpoints":{"get":{"tags":["Webhooks"],"operationId":"listWebhookEndpoints","summary":"List this venue's webhook endpoints","description":"Oldest first. Signing secrets are never listed.","security":[{"bearerAuth":["webhooks:manage"]}],"x-bmv-scope":"webhooks:manage","x-bmv-served-by":"both","responses":{"200":{"description":"The endpoints.","headers":{"bmv-api-version":{"$ref":"#/components/headers/BmvApiVersion"},"bmv-quota-remaining":{"$ref":"#/components/headers/BmvQuotaRemaining"},"deprecation":{"$ref":"#/components/headers/Deprecation"},"sunset":{"$ref":"#/components/headers/Sunset"},"link":{"$ref":"#/components/headers/Link"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointList"}}}},"400":{"$ref":"#/components/responses/KeyInQuery"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/InsufficientScope"},"410":{"$ref":"#/components/responses/VersionSunset"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"post":{"tags":["Webhooks"],"operationId":"createWebhookEndpoint","summary":"Register a webhook endpoint","description":"The response carries `signing_secret` ONCE. It is needed to verify `bmv-signature` on every delivery (see `webhooks`) and this API cannot return it again.","security":[{"bearerAuth":["webhooks:manage"]}],"x-bmv-scope":"webhooks:manage","x-bmv-served-by":"both","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointCreateRequest"}}},"description":"JSON, at most 16 KiB."},"responses":{"201":{"description":"Endpoint registered.","headers":{"bmv-api-version":{"$ref":"#/components/headers/BmvApiVersion"},"bmv-quota-remaining":{"$ref":"#/components/headers/BmvQuotaRemaining"},"deprecation":{"$ref":"#/components/headers/Deprecation"},"sunset":{"$ref":"#/components/headers/Sunset"},"link":{"$ref":"#/components/headers/Link"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointCreated"}}}},"400":{"description":"Bad request. `error` is one of: `key_in_query`, `url_required`, `invalid_json`. `key_in_query`: a key was sent in the query string (see the API description); treat that key as compromised.","x-bmv-error-codes":["key_in_query","url_required","invalid_json"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/InsufficientScope"},"410":{"$ref":"#/components/responses/VersionSunset"},"413":{"description":"Payload too large. `error` is one of: `payload_too_large`.","x-bmv-error-codes":["payload_too_large"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Unprocessable. `error` is one of: `url_rejected`. `url_rejected`: must be https, a public host, no credentials in the URL, and not a private, loopback or link-local address.","x-bmv-error-codes":["url_rejected"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/public/v1/webhook-endpoints/{endpoint_id}":{"delete":{"tags":["Webhooks"],"operationId":"deleteWebhookEndpoint","summary":"Delete a webhook endpoint","description":"Deletes the endpoint and, with it, its delivery records.","security":[{"bearerAuth":["webhooks:manage"]}],"x-bmv-scope":"webhooks:manage","x-bmv-served-by":"both","parameters":[{"$ref":"#/components/parameters/EndpointId"}],"responses":{"200":{"description":"Deleted.","headers":{"bmv-api-version":{"$ref":"#/components/headers/BmvApiVersion"},"bmv-quota-remaining":{"$ref":"#/components/headers/BmvQuotaRemaining"},"deprecation":{"$ref":"#/components/headers/Deprecation"},"sunset":{"$ref":"#/components/headers/Sunset"},"link":{"$ref":"#/components/headers/Link"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointDeleted"}}}},"400":{"description":"Bad request. `error` is one of: `key_in_query`, `bad_value`. `key_in_query`: a key was sent in the query string (see the API description); treat that key as compromised.","x-bmv-error-codes":["key_in_query","bad_value"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/InsufficientScope"},"404":{"description":"Not found. `error` is one of: `no_such_endpoint`.","x-bmv-error-codes":["no_such_endpoint"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"$ref":"#/components/responses/VersionSunset"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/public/v1/webhook-endpoints/{endpoint_id}/ping":{"post":{"tags":["Webhooks"],"operationId":"pingWebhookEndpoint","summary":"Send a test `ping` event","description":"Records a `ping` event (`data.object` = `{\"ping\": true}`) for delivery by the webhook worker. The endpoint must be active. The event is recorded for the VENUE, not only for this endpoint: every active endpoint of the venue that subscribes to `ping` or `*` receives it. No request body is read.","security":[{"bearerAuth":["webhooks:manage"]}],"x-bmv-scope":"webhooks:manage","x-bmv-served-by":"both","parameters":[{"$ref":"#/components/parameters/EndpointId"}],"responses":{"202":{"description":"Queued.","headers":{"bmv-api-version":{"$ref":"#/components/headers/BmvApiVersion"},"bmv-quota-remaining":{"$ref":"#/components/headers/BmvQuotaRemaining"},"deprecation":{"$ref":"#/components/headers/Deprecation"},"sunset":{"$ref":"#/components/headers/Sunset"},"link":{"$ref":"#/components/headers/Link"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookPingQueued"}}}},"400":{"description":"Bad request. `error` is one of: `key_in_query`, `bad_value`. `key_in_query`: a key was sent in the query string (see the API description); treat that key as compromised.","x-bmv-error-codes":["key_in_query","bad_value"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/InsufficientScope"},"404":{"description":"Not found. `error` is one of: `no_such_endpoint`. `no_such_endpoint`: unknown, belongs to another venue, or not `active`.","x-bmv-error-codes":["no_such_endpoint"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"$ref":"#/components/responses/VersionSunset"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/public/v1/webhook-endpoints/{endpoint_id}/deliveries":{"get":{"tags":["Webhooks"],"operationId":"listWebhookDeliveries","summary":"Recent deliveries to one endpoint","description":"The 100 most recent deliveries, newest first. An unknown endpoint id answers 200 with an empty list, not 404.","security":[{"bearerAuth":["webhooks:manage"]}],"x-bmv-scope":"webhooks:manage","x-bmv-served-by":"both","parameters":[{"$ref":"#/components/parameters/EndpointId"}],"responses":{"200":{"description":"The deliveries.","headers":{"bmv-api-version":{"$ref":"#/components/headers/BmvApiVersion"},"bmv-quota-remaining":{"$ref":"#/components/headers/BmvQuotaRemaining"},"deprecation":{"$ref":"#/components/headers/Deprecation"},"sunset":{"$ref":"#/components/headers/Sunset"},"link":{"$ref":"#/components/headers/Link"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDeliveryList"}}}},"400":{"description":"Bad request. `error` is one of: `key_in_query`, `bad_value`. `key_in_query`: a key was sent in the query string (see the API description); treat that key as compromised.","x-bmv-error-codes":["key_in_query","bad_value"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/InsufficientScope"},"410":{"$ref":"#/components/responses/VersionSunset"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/public/v1/tables/availability":{"get":{"tags":["Tables"],"operationId":"searchTableAvailability","summary":"Free table times for a party on a day","description":"The widget’s own availability for a hospitality venue: each start with its service, how long the table is held, any deposit, the venue wall clock (`slot_local`), `bookable` and the hosted `booking_url`. A slot with `bookable: false` owes a deposit: the guest books it on `booking_url` (money never moves through an agent). Never cached.","security":[{"bearerAuth":["read:tables"]}],"x-bmv-scope":"read:tables","x-bmv-served-by":"next","parameters":[{"name":"venue","in":"query","required":true,"schema":{"type":"string"},"description":"Venue id or booking-page slug. Required for an agent key; a venue key may omit it."},{"name":"date","in":"query","required":true,"schema":{"type":"string","format":"date"},"description":"The day, `YYYY-MM-DD` (a real date)."},{"name":"pax","in":"query","required":true,"schema":{"type":"integer","minimum":1,"maximum":100},"description":"Party size, 1–100."}],"responses":{"200":{"description":"The slots.","headers":{"bmv-quota-remaining":{"$ref":"#/components/headers/BmvQuotaRemaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TableAvailability"},"example":{"venue":{"id":"a3c1e2d4-5b6f-4a70-8b91-c2d3e4f5a6b7","slug":"demo-bistro","name":"Harbour Bistro (Demo)","timezone":"Australia/Sydney"},"date":"2027-03-12","pax":4,"currency":"AUD","slots":[{"start":"2027-03-12T08:00:00.000Z","slot_local":"2027-03-12T19:00","service_id":"0b9a8c7d-6e5f-4a3b-8c2d-1e0f9a8b7c6d","service_name":"Dinner","duration_minutes":90,"deposit_cents":0,"bookable":true,"booking_url":"https://app.bookmyvenue.com.au/book/demo-bistro?date=2027-03-12&pax=4"}]}}}},"400":{"description":"Bad input. `error` is one of: `key_in_query`, `venue_required`, `bad_date`, `bad_pax`.","x-bmv-error-codes":["key_in_query","venue_required","bad_date","bad_pax"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"401":{"description":"No key, or not a valid key. `error` is one of: `auth_required`, `invalid_key`.","x-bmv-error-codes":["auth_required","invalid_key"],"headers":{"www-authenticate":{"$ref":"#/components/headers/WwwAuthenticate"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"403":{"description":"Key suspended, missing the scope, or a venue key naming another venue. `error` is one of: `key_suspended`, `insufficient_scope`, `venue_mismatch`.","x-bmv-error-codes":["key_suspended","insufficient_scope","venue_mismatch"],"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/StayError"},{"type":"object","properties":{"scope":{"type":"string"},"switch":{"type":"string","enum":["platform","venue"]}}}]},"example":{"error":"insufficient_scope","message":"This key does not have the scope this call needs.","detail":"This key does not have the scope this call needs.","scope":"read:tables"}}}},"404":{"description":"No such venue, or the venue does not take table bookings. `error` is one of: `venue_not_found`, `not_tables`.","x-bmv-error-codes":["venue_not_found","not_tables"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"429":{"description":"Over the per-minute rate or the daily quota. `error` is one of: `rate_limited`, `quota_exceeded`.","x-bmv-error-codes":["rate_limited","quota_exceeded"],"headers":{"retry-after":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/StayError"},{"type":"object","properties":{"retry_after":{"type":"integer"},"limit":{"type":"integer"},"window_minutes":{"type":"integer"},"limit_per_min":{"type":"integer"},"quota_per_day":{"type":"integer"}}}]},"example":{"error":"rate_limited","message":"Too many requests for this key. Slow down and retry after the retry-after header.","detail":"Too many requests for this key. Slow down and retry after the retry-after header.","retry_after":12,"limit_per_min":60}}}}}}},"/api/public/v1/tables/bookings":{"post":{"tags":["Tables"],"operationId":"createTableBooking","summary":"Book a table on a guest’s behalf","description":"Read `x-bmv-agent` first. 201 = new booking (always `confirmed`: a table has no requested state); 200 + `replayed: true` = the original booking for this Idempotency-Key. For an AGENT key the database applies the agent controls: the kill switches (403 `agent_bookings_disabled` with `switch`), the instant contact rule (422 `contact_required_instant`: the guest’s Australian mobile AND email), the caps (429 `booking_cap` / `burst_cap` / `guest_cap`) and attribution (`source` = `api`, `source_ref` = `agent:<key label>`). Hand-offs, where NOTHING is written and the guest should be sent `booking_url`: a venue that confirms agent bookings by hand answers 409 `confirmation_required`; a service that takes a deposit answers 409 `deposit_required`.","security":[{"bearerAuth":["write:tables"]}],"x-bmv-scope":"write:tables","x-bmv-served-by":"next","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","format":"uuid"},"description":"Required (or `idempotency_key` in the body). A uuid; a retry with the same key replays the ORIGINAL booking (200, `replayed: true`, header `bmv-idempotent-replay: true`). The same key with a different body is 409 `idempotency_conflict`."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TableBookingCreateRequest"}}}},"responses":{"200":{"description":"Replay of the original booking (`replayed: true`).","headers":{"bmv-quota-remaining":{"$ref":"#/components/headers/BmvQuotaRemaining"},"bmv-idempotent-replay":{"$ref":"#/components/headers/BmvIdempotentReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TableBooking"}}}},"201":{"description":"The table booking (new).","headers":{"bmv-quota-remaining":{"$ref":"#/components/headers/BmvQuotaRemaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TableBooking"}}}},"400":{"description":"Bad input. `error` is one of: `key_in_query`, `bad_request`, `venue_required`, `idempotency_key_required`, `bad_idempotency_key`, `bad_starts_at`, `bad_local_start`, `time_does_not_exist`, `starts_at_mismatch`, `bad_pax`, `name_required`, `contact_required`.","x-bmv-error-codes":["key_in_query","bad_request","venue_required","idempotency_key_required","bad_idempotency_key","bad_starts_at","bad_local_start","time_does_not_exist","starts_at_mismatch","bad_pax","name_required","contact_required"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"401":{"description":"No key, or not a valid key. `error` is one of: `auth_required`, `invalid_key`.","x-bmv-error-codes":["auth_required","invalid_key"],"headers":{"www-authenticate":{"$ref":"#/components/headers/WwwAuthenticate"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"403":{"description":"Key suspended, missing the scope, a venue key naming another venue, or agent bookings switched off (`agent_bookings_disabled`, with `switch` = `platform` or `venue`: send the guest the venue’s booking page instead). `error` is one of: `key_suspended`, `insufficient_scope`, `venue_mismatch`, `agent_bookings_disabled`.","x-bmv-error-codes":["key_suspended","insufficient_scope","venue_mismatch","agent_bookings_disabled"],"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/StayError"},{"type":"object","properties":{"scope":{"type":"string"},"switch":{"type":"string","enum":["platform","venue"]}}}]},"example":{"error":"agent_bookings_disabled","message":"AI-agent bookings are switched off here. Send the guest the booking page instead.","detail":"AI-agent bookings are switched off here. Send the guest the booking page instead.","switch":"venue"}}}},"404":{"description":"No such venue, or the venue does not take table bookings. `error` is one of: `venue_not_found`, `not_tables`.","x-bmv-error-codes":["venue_not_found","not_tables"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"409":{"description":"Conflict or hand-off. `confirmation_required` and `deposit_required` carry `booking_url` (send it to the guest; nothing was written). `no_tables`, `slot_taken`, `venue_closed`: pick another time from availability. `error` is one of: `idempotency_conflict`, `idempotency_key_in_flight`, `idempotency_race`, `confirmation_required`, `deposit_required`, `no_tables`, `slot_taken`, `venue_closed`.","x-bmv-error-codes":["idempotency_conflict","idempotency_key_in_flight","idempotency_race","confirmation_required","deposit_required","no_tables","slot_taken","venue_closed"],"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/StayError"},{"type":"object","properties":{"booking_url":{"type":"string","format":"uri"}}}]},"example":{"error":"deposit_required","message":"This option takes a deposit, which an agent cannot pay. Send the guest the booking_url to book it themselves.","detail":"This option takes a deposit, which an agent cannot pay. Send the guest the booking_url to book it themselves.","booking_url":"https://app.bookmyvenue.com.au/book/demo-bistro?date=2027-03-12&pax=4"}}}},"422":{"description":"An instant agent booking needs the guest’s Australian mobile AND email. `error` is one of: `contact_required_instant`.","x-bmv-error-codes":["contact_required_instant"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"},"example":{"error":"contact_required_instant","message":"An instant agent booking needs the guest’s Australian mobile AND email, so the guest can be told and can cancel.","detail":"An instant agent booking needs the guest’s Australian mobile AND email, so the guest can be told and can cancel."}}}},"429":{"description":"Over the rate or quota, or an agent cap: `booking_cap` (bookings + ticket orders per key per venue per venue-day; `retry-after` 3600), `burst_cap` (3 per key per venue per 10 minutes; `retry-after` = seconds until the oldest leaves the window), `guest_cap` (2 open future agent bookings/orders per guest phone or email per venue, any key; no retry-after). `error` is one of: `rate_limited`, `quota_exceeded`, `booking_cap`, `burst_cap`, `guest_cap`.","x-bmv-error-codes":["rate_limited","quota_exceeded","booking_cap","burst_cap","guest_cap"],"headers":{"retry-after":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/StayError"},{"type":"object","properties":{"retry_after":{"type":"integer"},"limit":{"type":"integer"},"window_minutes":{"type":"integer"},"limit_per_min":{"type":"integer"},"quota_per_day":{"type":"integer"}}}]},"example":{"error":"burst_cap","message":"Too many bookings from this key at this venue in 10 minutes. Retry after the retry-after header.","detail":"Too many bookings from this key at this venue in 10 minutes. Retry after the retry-after header.","limit":3,"window_minutes":10,"retry_after":412}}}}}},"get":{"tags":["Tables"],"operationId":"listTableBookings","summary":"Table bookings this key may see","description":"An agent key lists ONLY the table bookings it created; a venue key lists the venue’s.","security":[{"bearerAuth":["read:tables"]}],"x-bmv-scope":"read:tables","x-bmv-served-by":"next","parameters":[{"name":"venue","in":"query","required":true,"schema":{"type":"string"},"description":"Venue id or booking-page slug. Required for an agent key; a venue key may omit it."},{"name":"from","in":"query","schema":{"type":"string","format":"date"},"description":"`YYYY-MM-DD`; anything else is ignored."},{"name":"to","in":"query","schema":{"type":"string","format":"date"},"description":"`YYYY-MM-DD`; anything else is ignored."},{"name":"limit","in":"query","schema":{"type":"integer","default":100,"minimum":1,"maximum":200}}],"responses":{"200":{"description":"The table bookings.","headers":{"bmv-quota-remaining":{"$ref":"#/components/headers/BmvQuotaRemaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TableBookingList"}}}},"400":{"description":"Bad input. `error` is one of: `key_in_query`, `venue_required`.","x-bmv-error-codes":["key_in_query","venue_required"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"401":{"description":"No key, or not a valid key. `error` is one of: `auth_required`, `invalid_key`.","x-bmv-error-codes":["auth_required","invalid_key"],"headers":{"www-authenticate":{"$ref":"#/components/headers/WwwAuthenticate"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"403":{"description":"Key suspended, missing the scope, or a venue key naming another venue. `error` is one of: `key_suspended`, `insufficient_scope`, `venue_mismatch`.","x-bmv-error-codes":["key_suspended","insufficient_scope","venue_mismatch"],"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/StayError"},{"type":"object","properties":{"scope":{"type":"string"},"switch":{"type":"string","enum":["platform","venue"]}}}]},"example":{"error":"insufficient_scope","message":"This key does not have the scope this call needs.","detail":"This key does not have the scope this call needs.","scope":"read:tables"}}}},"404":{"description":"No such venue. `error` is one of: `venue_not_found`.","x-bmv-error-codes":["venue_not_found"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"429":{"description":"Over the per-minute rate or the daily quota. `error` is one of: `rate_limited`, `quota_exceeded`.","x-bmv-error-codes":["rate_limited","quota_exceeded"],"headers":{"retry-after":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/StayError"},{"type":"object","properties":{"retry_after":{"type":"integer"},"limit":{"type":"integer"},"window_minutes":{"type":"integer"},"limit_per_min":{"type":"integer"},"quota_per_day":{"type":"integer"}}}]},"example":{"error":"rate_limited","message":"Too many requests for this key. Slow down and retry after the retry-after header.","detail":"Too many requests for this key. Slow down and retry after the retry-after header.","retry_after":12,"limit_per_min":60}}}}}}},"/api/public/v1/events":{"get":{"tags":["Events"],"operationId":"listEvents","summary":"Upcoming events with seats left","description":"The venue’s published, not-yet-started events (up to 200, soonest first) with the ticket price, seats left and the hosted `booking_url`. Never cached.","security":[{"bearerAuth":["read:events"]}],"x-bmv-scope":"read:events","x-bmv-served-by":"next","parameters":[{"name":"venue","in":"query","required":true,"schema":{"type":"string"},"description":"Venue id or booking-page slug. Required for an agent key; a venue key may omit it."}],"responses":{"200":{"description":"The events.","headers":{"bmv-quota-remaining":{"$ref":"#/components/headers/BmvQuotaRemaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventList"},"example":{"venue":{"id":"a3c1e2d4-5b6f-4a70-8b91-c2d3e4f5a6b7","slug":"demo-bistro","name":"Harbour Bistro (Demo)","timezone":"Australia/Sydney"},"currency":"AUD","events":[{"id":"c1d2e3f4-a5b6-4c7d-8e9f-a0b1c2d3e4f5","name":"Jazz Night","description":"Live trio.","starts_at":"2027-03-20T09:00:00Z","ends_at":"2027-03-20T12:00:00Z","local_start":"2027-03-20T20:00","time_zone":"Australia/Sydney","price_cents":3500,"available":42,"booking_url":"https://app.bookmyvenue.com.au/events/demo-bistro/c1d2e3f4-a5b6-4c7d-8e9f-a0b1c2d3e4f5"}]}}}},"400":{"description":"Bad input. `error` is one of: `key_in_query`, `venue_required`.","x-bmv-error-codes":["key_in_query","venue_required"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"401":{"description":"No key, or not a valid key. `error` is one of: `auth_required`, `invalid_key`.","x-bmv-error-codes":["auth_required","invalid_key"],"headers":{"www-authenticate":{"$ref":"#/components/headers/WwwAuthenticate"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"403":{"description":"Key suspended, missing the scope, or a venue key naming another venue. `error` is one of: `key_suspended`, `insufficient_scope`, `venue_mismatch`.","x-bmv-error-codes":["key_suspended","insufficient_scope","venue_mismatch"],"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/StayError"},{"type":"object","properties":{"scope":{"type":"string"},"switch":{"type":"string","enum":["platform","venue"]}}}]},"example":{"error":"insufficient_scope","message":"This key does not have the scope this call needs.","detail":"This key does not have the scope this call needs.","scope":"read:tables"}}}},"404":{"description":"No such venue. `error` is one of: `venue_not_found`.","x-bmv-error-codes":["venue_not_found"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"429":{"description":"Over the per-minute rate or the daily quota. `error` is one of: `rate_limited`, `quota_exceeded`.","x-bmv-error-codes":["rate_limited","quota_exceeded"],"headers":{"retry-after":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/StayError"},{"type":"object","properties":{"retry_after":{"type":"integer"},"limit":{"type":"integer"},"window_minutes":{"type":"integer"},"limit_per_min":{"type":"integer"},"quota_per_day":{"type":"integer"}}}]},"example":{"error":"rate_limited","message":"Too many requests for this key. Slow down and retry after the retry-after header.","detail":"Too many requests for this key. Slow down and retry after the retry-after header.","retry_after":12,"limit_per_min":60}}}}}}},"/api/public/v1/events/{event_id}/purchase":{"post":{"tags":["Events"],"operationId":"purchaseEventTickets","summary":"Buy event tickets on a guest’s behalf","description":"Read `x-bmv-agent` first. A FREE event answers 201 with the tickets valid at once and their admission codes. A PRICED event at a venue that takes payments online answers 402 `payment_required`: the seats are HELD (15 minutes) and `payment_url` is a Stripe Checkout page the GUEST pays on — no money moves through an agent and card details never touch it; a replay while still held answers 402 again with the same Checkout session, and once paid or lapsed a replay answers 200 with the order as it is now. Hand-offs, where nothing is held and the guest should be sent `booking_url`: a priced event at a venue that cannot take that payment online answers 409 `pay_on_booking_page`; a venue that confirms agent bookings by hand answers 409 `confirmation_required`. For an AGENT key the database applies the agent controls: kill switches (403 `agent_bookings_disabled` with `switch`), the instant contact rule (422 `contact_required_instant`), the caps (429 `booking_cap` / `burst_cap` / `guest_cap`; a held order counts) and attribution on every ticket.","security":[{"bearerAuth":["write:events"]}],"x-bmv-scope":"write:events","x-bmv-served-by":"next","parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"An `id` from GET /api/public/v1/events. Not a uuid → 400 `bad_event`."},{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","format":"uuid"},"description":"Required (or `idempotency_key` in the body). A uuid; a retry with the same key replays the ORIGINAL order (the key IS the order id) (200, `replayed: true`, header `bmv-idempotent-replay: true`). The same key with a different body is 409 `idempotency_conflict`."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventPurchaseRequest"}}}},"responses":{"200":{"description":"Replay of the original order (`replayed: true`), as it is now.","headers":{"bmv-quota-remaining":{"$ref":"#/components/headers/BmvQuotaRemaining"},"bmv-idempotent-replay":{"$ref":"#/components/headers/BmvIdempotentReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventOrder"}}}},"201":{"description":"The order (new, free event): tickets valid, with codes.","headers":{"bmv-quota-remaining":{"$ref":"#/components/headers/BmvQuotaRemaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventOrder"}}}},"400":{"description":"Bad input. `error` is one of: `key_in_query`, `bad_request`, `venue_required`, `bad_event`, `idempotency_key_required`, `bad_idempotency_key`, `bad_qty`, `name_required`, `contact_required`.","x-bmv-error-codes":["key_in_query","bad_request","venue_required","bad_event","idempotency_key_required","bad_idempotency_key","bad_qty","name_required","contact_required"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"401":{"description":"No key, or not a valid key. `error` is one of: `auth_required`, `invalid_key`.","x-bmv-error-codes":["auth_required","invalid_key"],"headers":{"www-authenticate":{"$ref":"#/components/headers/WwwAuthenticate"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"402":{"description":"Payment required. The tickets are HELD (`status: pending`) until the guest pays on Stripe at `payment_url` by `payment_due_at` (UTC); unpaid, the hold lapses and the seats are released. Hand the guest `payment_url`; never take card details. If minting the Checkout session failed, `payment_url` is null and the guest completes on `booking_url`, where the seats are still held. `error` is `payment_required`.","x-bmv-error-codes":["payment_required"],"headers":{"bmv-quota-remaining":{"$ref":"#/components/headers/BmvQuotaRemaining"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/EventOrder"},{"type":"object","required":["error","message","payment_url","payment_due_at","amount_cents","tickets","booking_url"],"properties":{"error":{"type":"string","const":"payment_required"},"message":{"type":"string"},"detail":{"type":"string"},"payment_url":{"type":["string","null"],"format":"uri","description":"Stripe Checkout page the GUEST opens to pay."},"payment_due_at":{"type":"string","format":"date-time"},"amount_cents":{"type":"integer"},"booking_url":{"type":"string","format":"uri","description":"The hosted event page."}}}]},"example":{"error":"payment_required","message":"The tickets are HELD, not issued: the guest must pay on Stripe at payment_url by payment_due_at, or the hold lapses and the seats are released. Give the guest the payment_url; never take card details yourself.","detail":"The tickets are HELD, not issued: the guest must pay on Stripe at payment_url by payment_due_at, or the hold lapses and the seats are released. Give the guest the payment_url; never take card details yourself.","order_id":"9d8c7b6a-5f4e-4d3c-8b2a-1f0e9d8c7b6a","kind":"event","event":{"id":"c1d2e3f4-a5b6-4c7d-8e9f-a0b1c2d3e4f5","name":"Jazz Night","starts_at":"2027-03-20T09:00:00Z","local_start":"2027-03-20T20:00","time_zone":"Australia/Sydney"},"qty":2,"status":"pending","amount_cents":7000,"currency":"AUD","payment_due_at":"2027-03-01T02:30:00Z","tickets":[{"id":"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d","status":"reserved"},{"id":"2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e","status":"reserved"}],"source":"api","source_ref":"agent:ExampleAgent","guest":{"name":"Alex Guest","phone":"0491 570 006","email":"guest@example.com"},"created_at":"2027-03-01T02:15:00Z","replayed":false,"mode":"instant","venue":{"id":"a3c1e2d4-5b6f-4a70-8b91-c2d3e4f5a6b7","slug":"demo-bistro","name":"Harbour Bistro (Demo)"},"payment_url":"https://checkout.stripe.com/c/pay/cs_test_a1B2c3D4e5F6g7H8i9J0","booking_url":"https://app.bookmyvenue.com.au/events/demo-bistro/c1d2e3f4-a5b6-4c7d-8e9f-a0b1c2d3e4f5"}}}},"403":{"description":"Key suspended, missing the scope, a venue key naming another venue, or agent bookings switched off (`agent_bookings_disabled`, with `switch` = `platform` or `venue`: send the guest the venue’s booking page instead). `error` is one of: `key_suspended`, `insufficient_scope`, `venue_mismatch`, `agent_bookings_disabled`.","x-bmv-error-codes":["key_suspended","insufficient_scope","venue_mismatch","agent_bookings_disabled"],"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/StayError"},{"type":"object","properties":{"scope":{"type":"string"},"switch":{"type":"string","enum":["platform","venue"]}}}]},"example":{"error":"agent_bookings_disabled","message":"AI-agent bookings are switched off here. Send the guest the booking page instead.","detail":"AI-agent bookings are switched off here. Send the guest the booking page instead.","switch":"venue"}}}},"404":{"description":"No such venue or event. `error` is one of: `venue_not_found`, `event_not_found`.","x-bmv-error-codes":["venue_not_found","event_not_found"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"409":{"description":"Conflict or hand-off. `pay_on_booking_page` (with `amount_cents`) and `confirmation_required` carry `booking_url` (send it to the guest; nothing was held). `error` is one of: `idempotency_conflict`, `idempotency_key_in_flight`, `pay_on_booking_page`, `confirmation_required`, `sold_out`, `event_not_on_sale`, `event_already_started`.","x-bmv-error-codes":["idempotency_conflict","idempotency_key_in_flight","pay_on_booking_page","confirmation_required","sold_out","event_not_on_sale","event_already_started"],"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/StayError"},{"type":"object","properties":{"booking_url":{"type":"string","format":"uri"},"amount_cents":{"type":"integer"}}}]},"example":{"error":"pay_on_booking_page","message":"These tickets are paid online, and this venue cannot take that payment through the API yet. Send the guest the booking_url.","detail":"These tickets are paid online, and this venue cannot take that payment through the API yet. Send the guest the booking_url.","amount_cents":7000,"booking_url":"https://app.bookmyvenue.com.au/events/demo-bistro/c1d2e3f4-a5b6-4c7d-8e9f-a0b1c2d3e4f5"}}}},"422":{"description":"An instant agent order needs the guest’s Australian mobile AND email. `error` is one of: `contact_required_instant`.","x-bmv-error-codes":["contact_required_instant"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"429":{"description":"Over the rate or quota, or an agent cap: `booking_cap` (bookings + ticket orders per key per venue per venue-day; `retry-after` 3600), `burst_cap` (3 per key per venue per 10 minutes; `retry-after` = seconds until the oldest leaves the window), `guest_cap` (2 open future agent bookings/orders per guest phone or email per venue, any key; no retry-after). `error` is one of: `rate_limited`, `quota_exceeded`, `booking_cap`, `burst_cap`, `guest_cap`.","x-bmv-error-codes":["rate_limited","quota_exceeded","booking_cap","burst_cap","guest_cap"],"headers":{"retry-after":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/StayError"},{"type":"object","properties":{"retry_after":{"type":"integer"},"limit":{"type":"integer"},"window_minutes":{"type":"integer"},"limit_per_min":{"type":"integer"},"quota_per_day":{"type":"integer"}}}]},"example":{"error":"burst_cap","message":"Too many bookings from this key at this venue in 10 minutes. Retry after the retry-after header.","detail":"Too many bookings from this key at this venue in 10 minutes. Retry after the retry-after header.","limit":3,"window_minutes":10,"retry_after":412}}}}}}},"/api/public/v1/stays/availability":{"get":{"tags":["Stays"],"operationId":"searchStayAvailability","summary":"Unit types free for the dates, with prices","description":"The database’s own answer, never cached. `bookable` and the hosted `booking_url` (URL-addressable stay state) are per unit type.","security":[{"bearerAuth":["read:stays"]}],"x-bmv-scope":"read:stays","x-bmv-served-by":"next","parameters":[{"name":"venue","in":"query","required":true,"schema":{"type":"string"},"description":"Venue id or booking-page slug. A venue key may omit it."},{"name":"check_in","in":"query","required":true,"schema":{"type":"string","format":"date"}},{"name":"check_out","in":"query","required":true,"schema":{"type":"string","format":"date"}},{"name":"adults","in":"query","schema":{"type":"integer","default":1}},{"name":"children","in":"query","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"Unit types that sleep the party.","headers":{"bmv-quota-remaining":{"$ref":"#/components/headers/BmvQuotaRemaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayAvailability"}}}},"400":{"description":"Bad input. `error` is one of: `bad_dates`, `bad_party`, `venue_required`.","x-bmv-error-codes":["bad_dates","bad_party","venue_required"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"401":{"description":"No key, or not a valid key. `error` is one of: `auth_required`, `invalid_key`.","x-bmv-error-codes":["auth_required","invalid_key"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"403":{"description":"Key suspended, or missing the scope. `error` is one of: `key_suspended`, `insufficient_scope`, `venue_mismatch`.","x-bmv-error-codes":["key_suspended","insufficient_scope","venue_mismatch"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"404":{"description":"No such venue. `error` is one of: `venue_not_found`, `not_accommodation`.","x-bmv-error-codes":["venue_not_found","not_accommodation"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"429":{"description":"Over the per-minute rate or the daily quota. `error` is one of: `rate_limited`, `quota_exceeded`.","x-bmv-error-codes":["rate_limited","quota_exceeded"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}}}}},"/api/public/v1/stays/calendar":{"get":{"tags":["Stays"],"operationId":"getStayCalendar","summary":"Night-by-night availability for one unit type","description":"One row per night: free units, closed, minimum stay, nightly price.","security":[{"bearerAuth":["read:stays"]}],"x-bmv-scope":"read:stays","x-bmv-served-by":"next","parameters":[{"name":"venue","in":"query","required":true,"schema":{"type":"string"},"description":"Venue id or booking-page slug. A venue key may omit it."},{"name":"unit_type","in":"query","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"from","in":"query","required":true,"schema":{"type":"string","format":"date"}},{"name":"days","in":"query","schema":{"type":"integer","default":62,"maximum":400}}],"responses":{"200":{"description":"The nights.","headers":{"bmv-quota-remaining":{"$ref":"#/components/headers/BmvQuotaRemaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayCalendar"}}}},"400":{"description":"Bad input. `error` is one of: `bad_dates`, `bad_unit_type`, `venue_required`.","x-bmv-error-codes":["bad_dates","bad_unit_type","venue_required"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"401":{"description":"No key, or not a valid key. `error` is one of: `auth_required`, `invalid_key`.","x-bmv-error-codes":["auth_required","invalid_key"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"403":{"description":"Key suspended, or missing the scope. `error` is one of: `key_suspended`, `insufficient_scope`, `venue_mismatch`.","x-bmv-error-codes":["key_suspended","insufficient_scope","venue_mismatch"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"404":{"description":"No such venue. `error` is one of: `venue_not_found`, `not_accommodation`.","x-bmv-error-codes":["venue_not_found","not_accommodation"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"429":{"description":"Over the per-minute rate or the daily quota. `error` is one of: `rate_limited`, `quota_exceeded`.","x-bmv-error-codes":["rate_limited","quota_exceeded"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}}}}},"/api/public/v1/stays/bookings":{"post":{"tags":["Stays"],"operationId":"createStayBooking","summary":"Book a stay on a guest’s behalf","description":"Read `x-bmv-agent` first. 201 = new booking; 200 + `replayed: true` = the original booking for this Idempotency-Key. The response carries the guest’s `manage_url`.","security":[{"bearerAuth":["write:stays"]}],"x-bmv-scope":"write:stays","x-bmv-served-by":"next","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","format":"uuid"},"description":"Required. A uuid; it becomes the booking’s client_op, so a retry replays the ORIGINAL booking (200, replayed:true). The same key with a different body is 409 idempotency_conflict."}],"responses":{"200":{"description":"Replay of the original booking (replayed: true).","headers":{"bmv-quota-remaining":{"$ref":"#/components/headers/BmvQuotaRemaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Stay"}}}},"201":{"description":"The stay (new).","headers":{"bmv-quota-remaining":{"$ref":"#/components/headers/BmvQuotaRemaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Stay"}}}},"400":{"description":"Bad input. `error` is one of: `bad_request`, `bad_dates`, `bad_party`, `bad_unit_type`, `name_required`, `contact_required`, `idempotency_key_required`, `bad_idempotency_key`, `venue_required`, `in_past`, `nights_below_min`, `nights_above_max`, `beyond_advance_window`, `too_many_guests`.","x-bmv-error-codes":["bad_request","bad_dates","bad_party","bad_unit_type","name_required","contact_required","idempotency_key_required","bad_idempotency_key","venue_required","in_past","nights_below_min","nights_above_max","beyond_advance_window","too_many_guests"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"401":{"description":"No key, or not a valid key. `error` is one of: `auth_required`, `invalid_key`.","x-bmv-error-codes":["auth_required","invalid_key"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"402":{"description":"Payment required (L8b). The unit type takes a deposit and the venue takes online payments: the stay is booked INSTANT but HELD `pending` until the guest pays the deposit on the secure payment page at `payment_url` (the venue's own gateway or Stripe) by `payment_due_at` (UTC). The agent never handles card data — hand the guest `payment_url` (they are also sent it) and `manage_url`. If unpaid by `payment_due_at` the hold lapses and the dates are released. The hold counts toward every cap. A replay with the same Idempotency-Key answers 402 again (same Checkout session) while the stay is still pending. When the venue cannot take payments the answer is 409 `deposit_required` instead. `error` is `payment_required`.","x-bmv-error-codes":["payment_required"],"headers":{"bmv-quota-remaining":{"$ref":"#/components/headers/BmvQuotaRemaining"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Stay"},{"type":"object","required":["error","message","booking_id","status","deposit_cents","payment_due_at","payment_url"],"properties":{"error":{"type":"string","const":"payment_required"},"message":{"type":"string"},"detail":{"type":"string"},"deposit_cents":{"type":"integer","description":"The deposit the guest pays, AUD cents."},"payment_due_at":{"type":"string","format":"date-time","description":"The hold lapses at this instant (UTC) if unpaid."},"payment_url":{"type":"string","format":"uri","description":"Stripe Checkout page the GUEST opens to pay. (If minting the session failed, the guest's BookMyVenue pay link, which redirects to the same Checkout.)"}}}]},"example":{"error":"payment_required","message":"The stay is HELD, not confirmed: the guest must pay the deposit on the secure payment page at payment_url by payment_due_at, or the hold lapses and the dates are released. Give the guest the payment_url (they were also sent it); never take card details yourself.","detail":"The stay is HELD, not confirmed: the guest must pay the deposit on the secure payment page at payment_url by payment_due_at, or the hold lapses and the dates are released. Give the guest the payment_url (they were also sent it); never take card details yourself.","booking_id":"6f1d2c3b-4a5e-4f60-8a71-92b3c4d5e6f7","kind":"accommodation","reference":"7KQ2PD","status":"pending","mode":"instant","replayed":false,"check_in":"2027-03-12","check_out":"2027-03-14","nights":2,"adults":2,"children":0,"unit_type":{"id":"12999037-e575-4785-ab92-107f8fc721fe","name":"Riverside Cabin"},"total_cents":43800,"currency":"AUD","time_zone":"Australia/Sydney","source":"api","source_ref":"agent:ExampleAgent","guest":{"name":"Alex Guest","phone":"0491 570 006","email":"guest@example.com"},"deposit_cents":10000,"payment_due_at":"2027-03-01T02:30:00Z","venue":{"id":"b5c21e71-4e2c-489e-966f-3714b1c67ab7","slug":"demo-stays","name":"Riverbend Riverside Cabins (Demo)"},"manage_url":"https://app.bookmyvenue.com.au/book/demo-stays?manage=0c9e8f7a-6b5d-4c3e-9f2a-1b0c9d8e7f6a","payment_url":"https://checkout.stripe.com/c/pay/cs_test_a1B2c3D4e5F6g7H8i9J0"}}}},"403":{"description":"Key suspended, missing the scope, or agent bookings switched off. `error` is one of: `key_suspended`, `insufficient_scope`, `venue_mismatch`, `agent_bookings_disabled`.","x-bmv-error-codes":["key_suspended","insufficient_scope","venue_mismatch","agent_bookings_disabled"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"404":{"description":"No such venue or unit type. `error` is one of: `venue_not_found`, `not_accommodation`, `no_such_unit_type`.","x-bmv-error-codes":["venue_not_found","not_accommodation","no_such_unit_type"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"409":{"description":"Conflict. `error` is one of: `no_units_free`, `venue_closed`, `deposit_required`, `idempotency_conflict`, `idempotency_key_in_flight`.","x-bmv-error-codes":["no_units_free","venue_closed","deposit_required","idempotency_conflict","idempotency_key_in_flight"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"422":{"description":"Instant agent bookings need a mobile AND an email. `error` is one of: `contact_required_instant`.","x-bmv-error-codes":["contact_required_instant"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"429":{"description":"Rate, quota or booking caps. `error` is one of: `rate_limited`, `quota_exceeded`, `booking_cap`, `burst_cap`, `guest_cap`.","x-bmv-error-codes":["rate_limited","quota_exceeded","booking_cap","burst_cap","guest_cap"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayCreateRequest"}}}}},"get":{"tags":["Stays"],"operationId":"listStayBookings","summary":"Stays this key may see","description":"An agent key lists ONLY the stays it created; a venue key lists the venue’s.","security":[{"bearerAuth":["read:stays"]}],"x-bmv-scope":"read:stays","x-bmv-served-by":"next","parameters":[{"name":"venue","in":"query","required":true,"schema":{"type":"string"},"description":"Venue id or booking-page slug. A venue key may omit it."},{"name":"from","in":"query","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","schema":{"type":"string","format":"date"}},{"name":"limit","in":"query","schema":{"type":"integer","default":100,"maximum":200}}],"responses":{"200":{"description":"The stays.","headers":{"bmv-quota-remaining":{"$ref":"#/components/headers/BmvQuotaRemaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayList"}}}},"401":{"description":"No key, or not a valid key. `error` is one of: `auth_required`, `invalid_key`.","x-bmv-error-codes":["auth_required","invalid_key"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"403":{"description":"Key suspended, or missing the scope. `error` is one of: `key_suspended`, `insufficient_scope`, `venue_mismatch`.","x-bmv-error-codes":["key_suspended","insufficient_scope","venue_mismatch"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"404":{"description":"No such venue. `error` is one of: `venue_not_found`.","x-bmv-error-codes":["venue_not_found"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"429":{"description":"Over the per-minute rate or the daily quota. `error` is one of: `rate_limited`, `quota_exceeded`.","x-bmv-error-codes":["rate_limited","quota_exceeded"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}}}}},"/api/public/v1/stays/bookings/{booking_id}":{"get":{"tags":["Stays"],"operationId":"getStayBooking","summary":"One stay","description":"An agent key reads only a stay it created; any other id is 404.","security":[{"bearerAuth":["read:stays"]}],"x-bmv-scope":"read:stays","x-bmv-served-by":"next","parameters":[{"name":"booking_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"venue","in":"query","required":true,"schema":{"type":"string"},"description":"Venue id or booking-page slug. A venue key may omit it."}],"responses":{"200":{"description":"The stay.","headers":{"bmv-quota-remaining":{"$ref":"#/components/headers/BmvQuotaRemaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Stay"}}}},"401":{"description":"No key, or not a valid key. `error` is one of: `auth_required`, `invalid_key`.","x-bmv-error-codes":["auth_required","invalid_key"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"403":{"description":"Key suspended, or missing the scope. `error` is one of: `key_suspended`, `insufficient_scope`, `venue_mismatch`.","x-bmv-error-codes":["key_suspended","insufficient_scope","venue_mismatch"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"404":{"description":"Not found for this key. `error` is one of: `not_found`, `venue_not_found`.","x-bmv-error-codes":["not_found","venue_not_found"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"429":{"description":"Over the per-minute rate or the daily quota. `error` is one of: `rate_limited`, `quota_exceeded`.","x-bmv-error-codes":["rate_limited","quota_exceeded"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}}}}},"/api/public/v1/stays/bookings/{booking_id}/cancel":{"post":{"tags":["Stays"],"operationId":"cancelStayBooking","summary":"Cancel a stay on the guest’s terms","description":"The venue’s online-cancel switch and cut-off apply exactly as for the guest. Cancelling an already-cancelled stay answers 200 with `already: true`.","security":[{"bearerAuth":["write:stays"]}],"x-bmv-scope":"write:stays","x-bmv-served-by":"next","parameters":[{"name":"booking_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":255}}],"responses":{"200":{"description":"The cancelled stay.","headers":{"bmv-quota-remaining":{"$ref":"#/components/headers/BmvQuotaRemaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Stay"}}}},"401":{"description":"No key, or not a valid key. `error` is one of: `auth_required`, `invalid_key`.","x-bmv-error-codes":["auth_required","invalid_key"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"403":{"description":"Key suspended, or missing the scope. `error` is one of: `key_suspended`, `insufficient_scope`, `venue_mismatch`.","x-bmv-error-codes":["key_suspended","insufficient_scope","venue_mismatch"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"404":{"description":"Not found for this key. `error` is one of: `not_found`, `venue_not_found`.","x-bmv-error-codes":["not_found","venue_not_found"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"409":{"description":"Refused. `error` is one of: `guest_cancel_disabled`, `not_cancellable`, `cancel_cutoff_passed`, `idempotency_conflict`, `idempotency_key_in_flight`.","x-bmv-error-codes":["guest_cancel_disabled","not_cancellable","cancel_cutoff_passed","idempotency_conflict","idempotency_key_in_flight"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"429":{"description":"Over the per-minute rate or the daily quota. `error` is one of: `rate_limited`, `quota_exceeded`.","x-bmv-error-codes":["rate_limited","quota_exceeded"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["venue"],"properties":{"venue":{"type":"string"},"reason":{"type":"string","maxLength":200}}}}}}}},"/api/public/v1/stays/bookings/{booking_id}/reschedule":{"post":{"tags":["Stays"],"operationId":"rescheduleStayBooking","summary":"Not available in v1","description":"Always 409 `not_reschedulable`, with the guest’s `manage_url` and the venue phone.","security":[{"bearerAuth":["write:stays"]}],"x-bmv-scope":"write:stays","x-bmv-served-by":"next","parameters":[{"name":"booking_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Never returned."},"401":{"description":"No key, or not a valid key. `error` is one of: `auth_required`, `invalid_key`.","x-bmv-error-codes":["auth_required","invalid_key"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"403":{"description":"Key suspended, or missing the scope. `error` is one of: `key_suspended`, `insufficient_scope`, `venue_mismatch`.","x-bmv-error-codes":["key_suspended","insufficient_scope","venue_mismatch"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"404":{"description":"Not found for this key. `error` is one of: `not_found`.","x-bmv-error-codes":["not_found"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"409":{"description":"Always. `error` is one of: `not_reschedulable`.","x-bmv-error-codes":["not_reschedulable"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}},"429":{"description":"Over the per-minute rate or the daily quota. `error` is one of: `rate_limited`, `quota_exceeded`.","x-bmv-error-codes":["rate_limited","quota_exceeded"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayError"}}}}},"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"venue":{"type":"string"}}}}}}}}},"webhooks":{"event":{"post":{"summary":"Event delivery to a registered endpoint","description":"BookMyVenue POSTs one JSON event per delivery to each active endpoint that subscribes to the event type (or `*`).\n\nVerify every delivery: `bmv-signature` is `t=<unix seconds>,v1=<hex>` where `v1 = HMAC-SHA256(signing_secret, \"<t>.\" + <raw request body bytes>)`. Compute it over the RAW bytes you received, not a re-serialised parse.\n\nReply 2xx within 10 seconds. Anything else — another status, a timeout, a connection failure — is a failed attempt, retried on a backoff ladder (1 min, 5 min, 30 min, 2 h, 6 h, then 24 h) until the delivery is marked `dead`. A delivery can arrive more than once: `bmv-delivery-id` is stable across redeliveries — use it as your idempotency key.","security":[],"parameters":[{"name":"bmv-signature","in":"header","required":true,"description":"`t=<unix seconds>,v1=<hex HMAC-SHA256>`; see above.","schema":{"type":"string"}},{"name":"bmv-delivery-id","in":"header","required":true,"description":"Delivery id (uuid). Stable across redeliveries of the same event to the same endpoint.","schema":{"type":"string","format":"uuid"}},{"name":"bmv-event-type","in":"header","required":true,"description":"The event type, e.g. `booking.confirmed`.","schema":{"type":"string"}},{"name":"bmv-product","in":"header","required":true,"description":"Always `bookmyvenue`.","schema":{"type":"string","const":"bookmyvenue"}},{"name":"bmv-venue-id","in":"header","required":true,"description":"The venue the event belongs to (uuid).","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEnvelope"}}}},"responses":{"2XX":{"description":"Acknowledged. Reply within 10 seconds."}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"bmv_sk_(live|test)_ + 40 [A-Za-z0-9]","description":"API key in the Authorization header ONLY. A key in the URL query string is refused with 400 `key_in_query`."}},"parameters":{"BookingId":{"name":"booking_id","in":"path","required":true,"description":"Booking id. The route matches 36 hex digits and dashes; such a value that is not a valid uuid is answered 400 `bad_value`.","schema":{"type":"string","format":"uuid"}},"EndpointId":{"name":"endpoint_id","in":"path","required":true,"description":"Webhook endpoint id. The route matches 36 hex digits and dashes; such a value that is not a valid uuid is answered 400 `bad_value`.","schema":{"type":"string","format":"uuid"}},"AvailabilityService":{"name":"service","in":"query","required":true,"description":"Service id.","schema":{"type":"string","format":"uuid"}},"AvailabilityDate":{"name":"date","in":"query","required":true,"description":"The day, `YYYY-MM-DD`.","schema":{"type":"string","format":"date"}},"AvailabilityPractitioner":{"name":"practitioner","in":"query","required":false,"description":"Restrict to one practitioner.","schema":{"type":"string","format":"uuid"}},"ListFrom":{"name":"from","in":"query","required":false,"description":"Bookings starting on or after this date, `YYYY-MM-DD`. The database converts the date to a timestamp in its session time zone, which this API does not set; the venue's time zone is NOT applied.","schema":{"type":"string","format":"date"}},"ListTo":{"name":"to","in":"query","required":false,"description":"Bookings starting before the END of this date (inclusive), `YYYY-MM-DD`. Same time-zone caveat as `from`.","schema":{"type":"string","format":"date"}},"ListStatus":{"name":"status","in":"query","required":false,"description":"Only bookings in this status.","schema":{"$ref":"#/components/schemas/BookingStatus"}},"ListCursor":{"name":"cursor","in":"query","required":false,"description":"`next_cursor` from the previous page.","schema":{"type":"string"}},"ListLimit":{"name":"limit","in":"query","required":false,"description":"Page size. Default 100; values above 200 are capped at 200; a non-integer is treated as 100. Send 1–200: the lower bound is not validated.","schema":{"type":"integer","minimum":1,"maximum":200,"default":100}},"IdempotencyKeyCreate":{"name":"Idempotency-Key","in":"header","required":false,"description":"A uuid. Repeating a create with the same key returns the ORIGINAL booking. May instead be sent as `idempotency_key` in the body (the body value wins).","schema":{"type":"string","format":"uuid"}},"IdempotencyKeyWrite":{"name":"Idempotency-Key","in":"header","required":false,"description":"REQUIRED for this operation — here or as `idempotency_key` in the body (the body value wins). 8–255 characters. A key is scoped to your API key and to this OPERATION, not to the booking in the path: reusing a key with an identical body against a different booking replays the first booking's stored response and does not act on the second. Use a fresh key for every distinct request.","schema":{"type":"string","minLength":8,"maxLength":255}}},"headers":{"BmvApiVersion":{"description":"The API version that answered (`v1`). Sent when the version is registered on the server.","schema":{"type":"string"}},"BmvQuotaRemaining":{"description":"Calls left in this key's daily quota after this one.","schema":{"type":"integer","minimum":0}},"Deprecation":{"description":"RFC 8594. Present once this version is deprecated: the deprecation date (HTTP-date).","schema":{"type":"string"}},"Sunset":{"description":"RFC 8594. Present once a sunset date is set (HTTP-date).","schema":{"type":"string"}},"Link":{"description":"Present with `deprecation` when a successor exists: `</api/public/<successor>/>; rel=\"successor-version\"`.","schema":{"type":"string"}},"RetryAfter":{"description":"Seconds to wait before retrying.","schema":{"type":"integer","minimum":1}},"WwwAuthenticate":{"description":"`Bearer realm=\"bookmyvenue\"`, with `error=\"invalid_token\"` for `invalid_key`.","schema":{"type":"string"}},"BmvIdempotentReplay":{"description":"`true` when the response is the stored result of an earlier request with the same idempotency key.","schema":{"type":"string","enum":["true"]}}},"responses":{"KeyInQuery":{"description":"Bad request. `error` is one of: `key_in_query`. `key_in_query`: a key was sent in the query string (see the API description); treat that key as compromised.","x-bmv-error-codes":["key_in_query"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthenticated":{"description":"Not authenticated. `error` is one of: `auth_required`, `invalid_key`. `invalid_key` is the same answer for a malformed, unknown or revoked key and for a key whose venue is deactivated.","x-bmv-error-codes":["auth_required","invalid_key"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"www-authenticate":{"$ref":"#/components/headers/WwwAuthenticate"}}},"InsufficientScope":{"description":"Forbidden. `error` is one of: `insufficient_scope`. `insufficient_scope`: the key lacks the scope this operation needs (`detail` names it).","x-bmv-error-codes":["insufficient_scope"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"VersionSunset":{"description":"Gone. `error` is one of: `api_version_sunset`. `api_version_sunset`: this API version has passed its sunset date.","x-bmv-error-codes":["api_version_sunset"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests. `error` is one of: `rate_limited`, `quota_exceeded`. `rate_limited`: over the key's per-minute limit. `quota_exceeded`: the key's daily quota is spent; it resets at the venue's local midnight and `retry-after` is a fixed 3600.","x-bmv-error-codes":["rate_limited","quota_exceeded"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"retry-after":{"$ref":"#/components/headers/RetryAfter"}}},"InternalError":{"description":"Server error. `error` is one of: `internal_error`. `internal_error` is answered by the server for any fault this API does not map. No detail is given.","x-bmv-error-codes":["internal_error"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable, machine-readable code. Code against this."},"detail":{"type":"string","description":"Human-readable explanation. Wording may change between releases; do not parse it."}},"description":"Every error body. `detail` is omitted when there is nothing to add."},"Scope":{"type":"string","enum":["read:venue","read:services","read:availability","read:bookings","write:bookings","webhooks:manage","read:stays","write:stays","read:tables","write:tables","write:events","read:events"],"description":"The scope catalogue. There is no clinical, intake or payment-write scope. read:tables / write:tables are used by the /tables paths and read:events / write:events by the /events paths."},"BookingStatus":{"type":"string","enum":["pending","requested","confirmed","seated","finished","no_show","cancelled","waitlist"],"description":"`pending`: awaiting payment or confirmation. `requested`: waiting for the venue to confirm by hand. `waitlist`: no slot held."},"BookingSource":{"type":"string","examples":["widget","phone","ai_phone","walk_in","staff","google","facebook","instagram","pos","api","import","referral","direct","agent","other","fresha_marketplace","fresha_import"],"description":"Booking channel. Bookings made through this API are `api`. Values at the time of writing: widget, phone, ai_phone, walk_in, staff, google, facebook, instagram, pos, api, import, referral, direct, agent, other, fresha_marketplace, fresha_import. Treat unknown values as possible: the set is widened by database migrations."},"Venue":{"type":"object","required":["id","name","slug","timezone","phone","email","address","require_confirmation"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"slug":{"type":"string","description":"The venue's public booking-page handle."},"timezone":{"type":"string","description":"IANA time zone name, e.g. `Australia/Sydney`."},"phone":{"type":["string","null"]},"email":{"type":["string","null"]},"address":{"type":["string","null"],"description":"Free text, one field."},"require_confirmation":{"type":"boolean","description":"True when the venue confirms appointment bookings by hand; new bookings then start as `requested`."}}},"VenueResponse":{"type":"object","required":["venue"],"properties":{"venue":{"$ref":"#/components/schemas/Venue"}}},"ServicePractitioner":{"type":"object","required":["id","name","title"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","description":"Display name."},"title":{"type":["string","null"]}}},"Service":{"type":"object","required":["id","name","category","description","duration_minutes","price_cents","deposit_policy","deposit_cents","deposit_percent","cutoff_minutes","max_days_ahead","practitioners"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"category":{"type":["string","null"]},"description":{"type":["string","null"]},"duration_minutes":{"type":"integer","minimum":1,"description":"Length the guest is told; the diary may block extra buffer time around it."},"price_cents":{"type":"integer","minimum":0,"description":"Price in integer cents. The currency is not returned by this API."},"deposit_policy":{"type":"string","enum":["none","fixed","percent"]},"deposit_cents":{"type":["integer","null"],"minimum":0,"description":"Integer cents. Set when `deposit_policy` is `fixed`."},"deposit_percent":{"type":["integer","null"],"minimum":1,"maximum":100,"description":"1–100. Set when `deposit_policy` is `percent`."},"cutoff_minutes":{"type":"integer","minimum":0,"description":"Minimum notice for online booking, in minutes."},"max_days_ahead":{"type":"integer","minimum":1,"description":"How far ahead the service can be booked online, in days."},"practitioners":{"type":"array","items":{"$ref":"#/components/schemas/ServicePractitioner"}}}},"ServiceList":{"type":"object","required":["services"],"properties":{"services":{"type":"array","items":{"$ref":"#/components/schemas/Service"}}}},"Slot":{"type":"object","required":["starts_at","practitioner_id","practitioner_name"],"properties":{"starts_at":{"type":"string","format":"date-time","description":"UTC ISO 8601, serialised by the server from a Postgres timestamptz (includes milliseconds, e.g. `2026-01-02T03:04:05.000Z`)."},"practitioner_id":{"type":"string","format":"uuid"},"practitioner_name":{"type":"string"}}},"Availability":{"type":"object","required":["date","timezone","slots"],"properties":{"date":{"type":"string","format":"date","description":"Echo of the `date` query parameter."},"timezone":{"type":"string","description":"The venue's IANA time zone."},"slots":{"type":"array","items":{"$ref":"#/components/schemas/Slot"}}}},"BookingListItem":{"type":"object","required":["booking_id","reference","status","starts_at","ends_at","pax","source","source_ref","client_op","practitioner_id","guest_name"],"properties":{"booking_id":{"type":"string","format":"uuid"},"reference":{"type":"string","description":"Short human code for the booking."},"status":{"$ref":"#/components/schemas/BookingStatus"},"starts_at":{"type":"string","format":"date-time","description":"UTC, formatted by SQL as `YYYY-MM-DDTHH:MM:SSZ` (no fractional seconds)."},"ends_at":{"type":"string","format":"date-time","description":"UTC, formatted by SQL as `YYYY-MM-DDTHH:MM:SSZ` (no fractional seconds)."},"pax":{"type":"integer","minimum":1},"source":{"$ref":"#/components/schemas/BookingSource"},"source_ref":{"type":["string","null"],"description":"The partner's own correlation token, as recorded at creation."},"client_op":{"type":["string","null"],"description":"The idempotency key the booking was created with, when there was one."},"practitioner_id":{"type":["string","null"],"format":"uuid"},"guest_name":{"type":"string"},"local_start":{"$ref":"#/components/schemas/BookingLocalStart"},"local_end":{"$ref":"#/components/schemas/BookingLocalEnd"},"time_zone":{"$ref":"#/components/schemas/BookingTimeZone"}},"description":"No phone, email, notes, tags or intake — absent from the database function's result type."},"BookingList":{"type":"object","required":["bookings","next_cursor"],"properties":{"bookings":{"type":"array","items":{"$ref":"#/components/schemas/BookingListItem"}},"next_cursor":{"type":["string","null"],"description":"Opaque. Pass back as `cursor` for the next page. `null` on the last page."}}},"Booking":{"type":"object","required":["booking_id","reference","status","starts_at","ends_at","pax","source","deposit_cents","source_ref","client_op","practitioner_id","service","guest"],"properties":{"booking_id":{"type":"string","format":"uuid"},"reference":{"type":"string"},"status":{"$ref":"#/components/schemas/BookingStatus"},"starts_at":{"type":"string","format":"date-time","description":"UTC, formatted by SQL as `YYYY-MM-DDTHH:MM:SSZ` (no fractional seconds)."},"ends_at":{"type":"string","format":"date-time","description":"UTC, formatted by SQL as `YYYY-MM-DDTHH:MM:SSZ` (no fractional seconds)."},"pax":{"type":"integer","minimum":1},"source":{"$ref":"#/components/schemas/BookingSource"},"deposit_cents":{"type":["integer","null"],"minimum":0,"description":"Integer cents."},"source_ref":{"type":["string","null"]},"client_op":{"type":["string","null"]},"practitioner_id":{"type":["string","null"],"format":"uuid"},"service":{"type":["object","null"],"required":["id","name","duration_minutes"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"duration_minutes":{"type":"integer"}},"description":"Null when the booking has no appointment service."},"guest":{"type":"object","required":["name","phone","email"],"properties":{"name":{"type":"string"},"phone":{"type":["string","null"]},"email":{"type":["string","null"]}}},"kind":{"type":"string","enum":["appointment","table","function","event","accommodation"],"description":"What kind of booking this is."},"local_start":{"$ref":"#/components/schemas/BookingLocalStart"},"local_end":{"$ref":"#/components/schemas/BookingLocalEnd"},"time_zone":{"$ref":"#/components/schemas/BookingTimeZone"}}},"BookingResponse":{"type":"object","required":["booking"],"properties":{"booking":{"$ref":"#/components/schemas/Booking"}}},"BookingCreateRequest":{"type":"object","required":["service_id","starts_at"],"properties":{"service_id":{"type":"string","format":"uuid","description":"From `GET /services`."},"practitioner_id":{"type":"string","format":"uuid","description":"Optional. Omit to let the venue pick a qualified practitioner."},"starts_at":{"type":"string","format":"date-time","description":"ISO 8601 with an explicit offset or `Z`. The transport accepts anything `Date.parse` accepts and the database casts it to timestamptz."},"guest":{"type":"object","properties":{"name":{"type":"string","maxLength":120,"description":"Required (here or as top-level `name`). Trimmed; longer than 120 characters is truncated."},"phone":{"type":"string","maxLength":60,"description":"Trimmed; truncated to 60 characters."},"email":{"type":"string","maxLength":160,"description":"Trimmed; truncated to 160 characters."}}},"name":{"type":"string","maxLength":120,"description":"Fallback, used only when `guest.name` is absent."},"phone":{"type":"string","maxLength":60,"description":"Fallback, used only when `guest.phone` is absent."},"email":{"type":"string","maxLength":160,"description":"Fallback, used only when `guest.email` is absent."},"notes":{"type":"string","maxLength":500,"description":"Trimmed; truncated to 500 characters."},"idempotency_key":{"type":"string","format":"uuid","description":"Alternative to the `Idempotency-Key` header; wins when both are sent."},"source_ref":{"type":"string","minLength":1,"maxLength":200,"description":"Your own correlation token (session id, click id, order ref). 1–200 characters after trimming. Stored exactly and NEVER truncated — a longer value is refused (`bad_source_ref`). Write-once: a replay echoes the ORIGINAL booking's value."}}},"BookingCreated":{"type":"object","required":["booking_id","reference","status","replayed"],"properties":{"booking_id":{"type":"string","format":"uuid"},"reference":{"type":"string"},"status":{"$ref":"#/components/schemas/BookingStatus"},"practitioner_id":{"type":"string","format":"uuid","description":"The practitioner booked. Omitted when the database returns none."},"price_cents":{"type":"integer","description":"Integer cents. Omitted when the database returns none."},"replayed":{"type":"boolean","description":"True when this is a replay of an earlier request with the same idempotency key."},"manage_token":{"type":"string","format":"uuid","description":"Booking management token from the database (`appt_manage_token`). How it is to be used is not specified by this API."},"source_ref":{"type":"string","description":"Present only when the booking has one. The value RECORDED, which on a replay is the original request's."},"intake":{"type":"object","required":["policy","url","expires_at"],"properties":{"policy":{"type":"string","description":"The venue's intake policy for this booking. Values are not enumerated by this API."},"url":{"type":"string","format":"uri","description":"Link to the guest's intake (health) form. The token is in the URL fragment."},"expires_at":{"type":"string","format":"date-time","description":"When the intake link expires (30 days after issue)."}},"description":"Present only when the guest still has to complete an intake form."},"payment":{"type":"object","required":["amount_cents","kind","checkout_url"],"properties":{"amount_cents":{"type":"integer","description":"Integer cents due now."},"kind":{"type":"string","enum":["deposit","full"]},"checkout_url":{"type":"string","format":"uri","description":"Stripe Checkout page for the guest."}},"description":"Present only when a payment is due."},"local_start":{"$ref":"#/components/schemas/BookingLocalStart"},"local_end":{"$ref":"#/components/schemas/BookingLocalEnd"},"time_zone":{"$ref":"#/components/schemas/BookingTimeZone"}}},"RescheduleRequest":{"type":"object","required":["starts_at"],"properties":{"starts_at":{"type":"string","format":"date-time","description":"New start. ISO 8601 with an explicit offset or `Z`."},"practitioner_id":{"type":"string","format":"uuid","description":"Optional. Move to this practitioner; must belong to the venue and be qualified for the service."},"reason":{"type":"string","maxLength":300,"description":"Trimmed; truncated to 300 characters."},"idempotency_key":{"type":"string","minLength":8,"maxLength":255,"description":"Alternative to the `Idempotency-Key` header; wins when both are sent. 8–255 characters."}}},"BookingLocalStart":{"type":"string","description":"The booking's start on the venue's wall clock, `YYYY-MM-DDTHH:MM:SS`, exactly as the database stored it — the time the guest agreed to. Display it as written in `time_zone`; never convert it. Absent when the database did not supply it.","examples":["2027-03-20T20:00:00"]},"BookingLocalEnd":{"type":"string","description":"The booking's end on the venue's wall clock, `YYYY-MM-DDTHH:MM:SS` (see `local_start`).","examples":["2027-03-20T21:00:00"]},"BookingTimeZone":{"type":"string","description":"The IANA zone `local_start` / `local_end` are in (the venue's zone when the booking was made).","examples":["Australia/Sydney"]},"RescheduleResult":{"type":"object","required":["booking_id","reference","status","starts_at","ends_at"],"properties":{"booking_id":{"type":"string","format":"uuid"},"reference":{"type":"string"},"status":{"$ref":"#/components/schemas/BookingStatus"},"starts_at":{"type":"string","format":"date-time","description":"UTC, formatted by SQL as `YYYY-MM-DDTHH:MM:SSZ` (no fractional seconds)."},"ends_at":{"type":"string","format":"date-time","description":"UTC, formatted by SQL as `YYYY-MM-DDTHH:MM:SSZ` (no fractional seconds)."},"local_start":{"$ref":"#/components/schemas/BookingLocalStart"},"local_end":{"$ref":"#/components/schemas/BookingLocalEnd"},"time_zone":{"$ref":"#/components/schemas/BookingTimeZone"}}},"CancelRequest":{"type":"object","properties":{"reason":{"type":"string","maxLength":300,"description":"Trimmed; truncated to 300 characters."},"idempotency_key":{"type":"string","minLength":8,"maxLength":255,"description":"Alternative to the `Idempotency-Key` header; wins when both are sent. 8–255 characters."}}},"CancelResult":{"type":"object","required":["booking_id","reference","status"],"properties":{"booking_id":{"type":"string","format":"uuid"},"reference":{"type":"string"},"status":{"$ref":"#/components/schemas/BookingStatus"}}},"WebhookEventType":{"type":"string","enum":["booking.created","booking.confirmed","booking.rescheduled","booking.cancelled","booking.no_show","booking.seated","booking.finished","ping","payment.succeeded","payment.failed","payment.refunded","tip.captured","order.paid","order.refunded","order.refund_failed","gift_card.purchased","gift_card.refunded","ticket.purchased","ticket.refunded","charge.disputed"],"description":"The event catalogue (`webhook_event_catalog()`)."},"WebhookEndpoint":{"type":"object","required":["id","url","description","event_types","status","disabled_reason","consecutive_failures","last_success_at","created_at"],"properties":{"id":{"type":"string","format":"uuid"},"url":{"type":"string","format":"uri"},"description":{"type":["string","null"]},"event_types":{"type":"array","items":{"type":"string"},"description":"Subscribed event types, or `[\"*\"]` for all."},"status":{"type":"string","enum":["active","disabled"]},"disabled_reason":{"type":["string","null"],"description":"Set exactly when `status` is `disabled`."},"consecutive_failures":{"type":"integer","minimum":0},"last_success_at":{"type":["string","null"],"format":"date-time","description":"UTC ISO 8601, serialised by the server from a Postgres timestamptz (includes milliseconds, e.g. `2026-01-02T03:04:05.000Z`)."},"created_at":{"type":"string","format":"date-time","description":"UTC ISO 8601, serialised by the server from a Postgres timestamptz (includes milliseconds, e.g. `2026-01-02T03:04:05.000Z`)."}}},"WebhookEndpointList":{"type":"object","required":["webhook_endpoints"],"properties":{"webhook_endpoints":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"WebhookEndpointCreateRequest":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","maxLength":2000,"description":"https only; public host; no user:password; not a private, loopback, link-local or CGNAT literal address. Trimmed; a value longer than 2000 characters is truncated to 2000 BEFORE it is checked."},"description":{"type":"string","maxLength":300,"description":"Trimmed; truncated to 300 characters."},"event_types":{"type":"array","items":{"type":"string","maxLength":60},"description":"Event types to receive (see `WebhookEventType`) or `[\"*\"]`. Omitted or empty means `[\"*\"]`. Each entry is truncated to 60 characters. Entries are NOT checked against the catalogue: an unknown type is stored and simply never matches."}}},"WebhookEndpointCreated":{"type":"object","required":["webhook_endpoint","signing_secret","signing_secret_note"],"properties":{"webhook_endpoint":{"type":"object","required":["id","url","event_types","status","created_at"],"properties":{"id":{"type":"string","format":"uuid"},"url":{"type":"string","format":"uri"},"event_types":{"type":"array","items":{"type":"string"}},"status":{"type":"string","enum":["active","disabled"]},"created_at":{"type":"string","format":"date-time","description":"UTC ISO 8601, serialised by the server from a Postgres timestamptz (includes milliseconds, e.g. `2026-01-02T03:04:05.000Z`)."}}},"signing_secret":{"type":"string","pattern":"^bmv_whsec_[A-Za-z0-9]{40}$","description":"HMAC key for `bmv-signature`. Shown ONCE."},"signing_secret_note":{"type":"string"}}},"WebhookEndpointDeleted":{"type":"object","required":["ok","deleted"],"properties":{"ok":{"type":"boolean","const":true},"deleted":{"type":"string","format":"uuid"}}},"WebhookPingQueued":{"type":"object","required":["ok","event_id"],"properties":{"ok":{"type":"boolean","const":true},"event_id":{"type":"string","format":"uuid","description":"The recorded `ping` event."}}},"WebhookDelivery":{"type":"object","required":["id","event_id","event_type","status","attempts","next_attempt_at","last_response_code","last_error","delivered_at"],"properties":{"id":{"type":"string","format":"uuid","description":"Also sent as `bmv-delivery-id`."},"event_id":{"type":"string","format":"uuid"},"event_type":{"type":"string"},"status":{"type":"string","enum":["queued","delivered","failed","dead"]},"attempts":{"type":"integer","minimum":0},"next_attempt_at":{"type":"string","format":"date-time","description":"UTC ISO 8601, serialised by the server from a Postgres timestamptz (includes milliseconds, e.g. `2026-01-02T03:04:05.000Z`)."},"last_response_code":{"type":["integer","null"],"description":"HTTP status your endpoint last answered."},"last_error":{"type":["string","null"],"description":"Why the last attempt failed, as recorded by the delivery worker (at most 500 characters)."},"delivered_at":{"type":["string","null"],"format":"date-time","description":"UTC ISO 8601, serialised by the server from a Postgres timestamptz (includes milliseconds, e.g. `2026-01-02T03:04:05.000Z`)."}}},"WebhookDeliveryList":{"type":"object","required":["deliveries"],"properties":{"deliveries":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDelivery"},"maxItems":100}}},"WebhookBookingObject":{"type":"object","required":["id","reference","status","starts_at","ends_at","pax","source","marketing_ref","source_ref","client_op","guest_name","deposit_cents","created_at"],"properties":{"id":{"type":"string","format":"uuid","description":"The booking id (named `id` here, `booking_id` in the REST responses)."},"reference":{"type":"string"},"status":{"$ref":"#/components/schemas/BookingStatus"},"starts_at":{"type":"string","format":"date-time","description":"UTC, formatted by SQL as `YYYY-MM-DDTHH:MM:SSZ` (no fractional seconds)."},"ends_at":{"type":"string","format":"date-time","description":"UTC, formatted by SQL as `YYYY-MM-DDTHH:MM:SSZ` (no fractional seconds)."},"pax":{"type":"integer"},"source":{"$ref":"#/components/schemas/BookingSource"},"marketing_ref":{"type":["string","null"],"description":"Campaign or landing string for the channel."},"source_ref":{"type":["string","null"]},"client_op":{"type":["string","null"]},"guest_name":{"type":"string"},"deposit_cents":{"type":["integer","null"],"description":"Integer cents."},"created_at":{"type":"string","format":"date-time","description":"UTC, formatted by SQL as `YYYY-MM-DDTHH:MM:SSZ` (no fractional seconds)."},"kind":{"type":"string","enum":["appointment","table","function","event","accommodation"],"description":"What kind of booking this is."},"check_in":{"type":"string","format":"date","description":"Accommodation only."},"check_out":{"type":"string","format":"date","description":"Accommodation only."},"nights":{"type":"integer","description":"Accommodation only."},"unit_type":{"type":["string","null"],"description":"Null on booking.created (the stay row is written after the booking); present on every later event."},"adults":{"type":["integer","null"],"description":"Accommodation only."},"children":{"type":["integer","null"],"description":"Accommodation only."}},"description":"`data.object` for `booking.*` events. The guest's name only — fetch the booking with `read:bookings` for contact details."},"WebhookEnvelope":{"type":"object","required":["product","api_version","type","venue","data"],"properties":{"product":{"type":"string","const":"bookmyvenue"},"api_version":{"type":"string","const":"v1"},"type":{"$ref":"#/components/schemas/WebhookEventType"},"venue":{"type":"object","required":["id","slug","name"],"properties":{"id":{"type":"string","format":"uuid"},"slug":{"type":"string"},"name":{"type":"string"}}},"data":{"type":"object","required":["object"],"properties":{"object":{"type":"object","description":"`booking.*`: a `WebhookBookingObject`. `ping`: `{\"ping\": true}`. The payment, order, gift card, ticket, tip and dispute event payloads are not specified in this version of the document."}}}}},"TableSlot":{"type":"object","required":["start","service_id","service_name","duration_minutes","deposit_cents","bookable","booking_url"],"properties":{"start":{"type":"string","format":"date-time","description":"The start (UTC, with milliseconds). Send it back as `starts_at`."},"slot_local":{"type":"string","description":"The same start on the venue’s wall clock, `YYYY-MM-DDTHH:MM` (send it back as `local_start`). Absent when the database did not supply it."},"service_id":{"type":"string","format":"uuid"},"service_name":{"type":"string"},"duration_minutes":{"type":"integer","description":"How long the table is held."},"deposit_cents":{"type":"integer","description":"Deposit per booking, AUD cents. 0 = none."},"bookable":{"type":"boolean","description":"false when a deposit is owed: the guest books that slot on `booking_url` (money never moves through the API)."},"booking_url":{"type":"string","format":"uri","description":"The venue’s hosted booking page for this date and party size."}}},"TableAvailability":{"type":"object","required":["venue","date","pax","currency","slots"],"properties":{"venue":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"slug":{"type":"string"},"name":{"type":"string"},"timezone":{"type":"string","description":"IANA time zone (on the read paths only)."}}},"date":{"type":"string","format":"date"},"pax":{"type":"integer"},"currency":{"type":"string","const":"AUD"},"slots":{"type":"array","items":{"$ref":"#/components/schemas/TableSlot"}}}},"TableBookingCreateRequest":{"type":"object","required":["venue","pax","guest"],"description":"Send `starts_at` (an instant, e.g. a slot’s `start`) or `local_start` (the venue’s wall clock, e.g. a slot’s `slot_local`); when both are sent they must name the same instant.","properties":{"venue":{"type":"string","description":"Venue id or booking-page slug. A venue key may omit it."},"starts_at":{"type":"string","format":"date-time"},"local_start":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}$"},"pax":{"type":"integer","minimum":1,"maximum":100},"guest":{"type":"object","required":["name"],"properties":{"name":{"type":"string","maxLength":120},"phone":{"type":"string","maxLength":40,"description":"An Australian mobile. Required (with email) for an instant agent booking."},"email":{"type":"string","format":"email","maxLength":160}}},"notes":{"type":"string","maxLength":1000},"source_ref":{"type":"string","maxLength":200,"description":"Venue keys only; an agent key’s bookings are always `agent:<key label>`."},"idempotency_key":{"type":"string","format":"uuid","description":"Alternative to the Idempotency-Key header (the header wins)."}},"example":{"venue":"demo-bistro","local_start":"2027-03-12T19:00","pax":4,"guest":{"name":"Alex Guest","phone":"0491 570 006","email":"guest@example.com"},"notes":"Window table if possible"}},"TableBooking":{"type":"object","required":["booking_id","kind","status","pax","starts_at","ends_at"],"description":"A table booking (db/280 api_table_json). On create/replay it also carries `replayed`, `mode` and `venue`.","properties":{"booking_id":{"type":"string","format":"uuid"},"kind":{"type":"string","const":"table"},"reference":{"type":"string"},"status":{"type":"string","description":"A new API table booking is always `confirmed` (a table has no requested state); a listed one may since have been cancelled etc."},"pax":{"type":"integer"},"starts_at":{"type":"string","format":"date-time"},"ends_at":{"type":"string","format":"date-time"},"local_start":{"type":"string"},"time_zone":{"type":"string"},"service":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"source":{"type":"string"},"source_ref":{"type":"string"},"guest":{"type":"object","properties":{"name":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string","format":"email"}}},"created_at":{"type":"string","format":"date-time"},"replayed":{"type":"boolean"},"mode":{"type":"string","const":"instant"},"venue":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"slug":{"type":"string"},"name":{"type":"string"},"timezone":{"type":"string","description":"IANA time zone (on the read paths only)."}}}},"example":{"booking_id":"5e0c1b2a-3d4f-4a5b-9c6d-7e8f9a0b1c2d","kind":"table","reference":"T4M9QZ","status":"confirmed","pax":4,"starts_at":"2027-03-12T08:00:00Z","ends_at":"2027-03-12T09:30:00Z","local_start":"2027-03-12T19:00","time_zone":"Australia/Sydney","service":{"id":"0b9a8c7d-6e5f-4a3b-8c2d-1e0f9a8b7c6d","name":"Dinner"},"source":"api","source_ref":"agent:ExampleAgent","guest":{"name":"Alex Guest","phone":"0491 570 006","email":"guest@example.com"},"created_at":"2027-03-01T02:15:00Z","replayed":false,"mode":"instant","venue":{"id":"a3c1e2d4-5b6f-4a70-8b91-c2d3e4f5a6b7","slug":"demo-bistro","name":"Harbour Bistro (Demo)"}}},"TableBookingList":{"type":"object","required":["bookings"],"properties":{"bookings":{"type":"array","items":{"$ref":"#/components/schemas/TableBooking"}}}},"EventSummary":{"type":"object","required":["id","name","starts_at","price_cents","available","booking_url"],"properties":{"id":{"type":"string","format":"uuid","description":"The `event_id` for purchase."},"name":{"type":"string"},"description":{"type":["string","null"]},"starts_at":{"type":"string","format":"date-time"},"ends_at":{"type":["string","null"],"format":"date-time"},"local_start":{"type":["string","null"]},"time_zone":{"type":"string"},"price_cents":{"type":["integer","null"],"description":"Per ticket, AUD cents. 0 or null = free."},"available":{"type":["integer","null"],"description":"Seats left (null = no capacity limit)."},"booking_url":{"type":"string","format":"uri","description":"The hosted event page."}}},"EventList":{"type":"object","required":["venue","currency","events"],"properties":{"venue":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"slug":{"type":"string"},"name":{"type":"string"},"timezone":{"type":"string","description":"IANA time zone (on the read paths only)."}}},"currency":{"type":"string","const":"AUD"},"events":{"type":"array","items":{"$ref":"#/components/schemas/EventSummary"}}}},"EventPurchaseRequest":{"type":"object","required":["venue","guest"],"properties":{"venue":{"type":"string","description":"Venue id or booking-page slug. A venue key may omit it."},"qty":{"type":"integer","minimum":1,"maximum":8,"default":1},"guest":{"type":"object","required":["name","email"],"properties":{"name":{"type":"string","maxLength":120},"phone":{"type":"string","maxLength":40,"description":"An Australian mobile. Required (with email) for an instant agent booking."},"email":{"type":"string","format":"email","maxLength":160}}},"source_ref":{"type":"string","maxLength":200,"description":"Venue keys only; an agent key’s tickets are always `agent:<key label>`."},"idempotency_key":{"type":"string","format":"uuid","description":"Alternative to the Idempotency-Key header (the header wins)."}},"example":{"venue":"demo-bistro","qty":2,"guest":{"name":"Alex Guest","phone":"0491 570 006","email":"guest@example.com"}}},"EventOrder":{"type":"object","required":["order_id","kind","event","qty","status","tickets"],"description":"One API ticket order: every ticket bought under one Idempotency-Key (db/280 api_event_order_json).","properties":{"order_id":{"type":"string","format":"uuid","description":"The Idempotency-Key you sent."},"kind":{"type":"string","const":"event"},"event":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"starts_at":{"type":"string","format":"date-time"},"local_start":{"type":"string"},"time_zone":{"type":"string"}}},"qty":{"type":"integer"},"status":{"type":"string","enum":["confirmed","pending","cancelled","mixed"],"description":"`confirmed` = tickets valid; `pending` = HELD awaiting the guest’s payment; `cancelled` = refunded, cancelled or a lapsed hold."},"amount_cents":{"type":"integer","description":"Order total, AUD cents."},"currency":{"type":"string","const":"AUD"},"payment_due_at":{"type":"string","format":"date-time","description":"Only while held: the hold lapses at this instant (UTC) if unpaid."},"tickets":{"type":"array","items":{"type":"object","required":["id","status"],"properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["reserved","valid","used","refunded","cancelled"]},"code":{"type":"string","description":"The admission code, only once the ticket is valid."}}}},"source":{"type":"string"},"source_ref":{"type":"string"},"guest":{"type":"object","properties":{"name":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string","format":"email"}}},"created_at":{"type":"string","format":"date-time"},"replayed":{"type":"boolean"},"mode":{"type":"string","const":"instant"},"venue":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"slug":{"type":"string"},"name":{"type":"string"},"timezone":{"type":"string","description":"IANA time zone (on the read paths only)."}}}},"example":{"order_id":"9d8c7b6a-5f4e-4d3c-8b2a-1f0e9d8c7b6a","kind":"event","event":{"id":"c1d2e3f4-a5b6-4c7d-8e9f-a0b1c2d3e4f5","name":"Jazz Night","starts_at":"2027-03-20T09:00:00Z","local_start":"2027-03-20T20:00","time_zone":"Australia/Sydney"},"qty":2,"status":"confirmed","amount_cents":0,"currency":"AUD","tickets":[{"id":"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d","status":"valid","code":"K7P2QX"},{"id":"2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e","status":"valid","code":"M3R8TW"}],"source":"api","source_ref":"agent:ExampleAgent","guest":{"name":"Alex Guest","phone":"0491 570 006","email":"guest@example.com"},"created_at":"2027-03-01T02:15:00Z","replayed":false,"mode":"instant","venue":{"id":"a3c1e2d4-5b6f-4a70-8b91-c2d3e4f5a6b7","slug":"demo-bistro","name":"Harbour Bistro (Demo)"}}},"StayError":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","description":"Stable code. Code against this."},"message":{"type":"string"},"detail":{"type":"string","description":"Same sentence as message (legacy key)."},"retry_after":{"type":"integer"},"booking_url":{"type":"string","format":"uri"},"manage_url":{"type":"string","format":"uri"},"scope":{"type":"string"},"limit":{"type":"integer"}}},"StayQuote":{"type":"object","properties":{"nights":{"type":"integer"},"total_cents":{"type":"integer"},"nightly":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string","format":"date"},"cents":{"type":"integer"}}}},"reasons":{"type":"array","items":{"type":"string"},"description":"Empty = bookable on those dates. Otherwise the rule that refuses them (in_past, nights_below_min, nights_above_max, beyond_advance_window, venue_closed, bad_dates)."},"min_nights":{"type":"integer"},"max_nights":{"type":"integer"}}},"StayUnitOption":{"type":"object","properties":{"unit_type_id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":["string","null"]},"max_adults":{"type":"integer"},"max_guests":{"type":"integer"},"free_count":{"type":"integer"},"quote":{"$ref":"#/components/schemas/StayQuote"},"bookable":{"type":"boolean"},"booking_url":{"type":"string","format":"uri"}}},"StayAvailability":{"type":"object","properties":{"venue":{"type":"object"},"check_in":{"type":"string","format":"date"},"check_out":{"type":"string","format":"date"},"nights":{"type":"integer"},"adults":{"type":"integer"},"children":{"type":"integer"},"currency":{"type":"string","const":"AUD"},"units":{"type":"array","items":{"$ref":"#/components/schemas/StayUnitOption"}}}},"StayCalendar":{"type":"object","properties":{"unit_type_id":{"type":"string","format":"uuid"},"from":{"type":"string","format":"date"},"currency":{"type":"string"},"days":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string","format":"date"},"free_count":{"type":"integer"},"closed":{"type":"boolean"},"min_nights":{"type":"integer"},"nightly_cents":{"type":"integer"}}}}}},"StayCreateRequest":{"type":"object","required":["venue","unit_type_id","check_in","check_out","adults","guest"],"properties":{"venue":{"type":"string","description":"Venue id or booking-page slug (agent keys must send it; a venue key may omit it)."},"unit_type_id":{"type":"string","format":"uuid"},"check_in":{"type":"string","format":"date"},"check_out":{"type":"string","format":"date"},"adults":{"type":"integer","minimum":1,"maximum":20},"children":{"type":"integer","minimum":0,"maximum":20,"default":0},"guest":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"phone":{"type":"string","description":"Australian mobile. Required (with email) for an instant agent booking."},"email":{"type":"string","format":"email"}}},"notes":{"type":"string","maxLength":2000},"source_ref":{"type":"string","maxLength":200,"description":"Venue keys only: your own correlation token. Agent keys are attributed as agent:<label>."},"user_confirmed":{"type":"boolean","description":"Recorded; required by the MCP create_booking tool."}},"example":{"venue":"demo-stays","unit_type_id":"0b8c6a52-0000-4000-8000-000000000001","check_in":"2026-12-01","check_out":"2026-12-03","adults":2,"children":0,"guest":{"name":"Angus Test","phone":"0491 570 006","email":"angus@alienit.com.au"},"user_confirmed":true}},"Stay":{"type":"object","properties":{"booking_id":{"type":"string","format":"uuid"},"kind":{"type":"string","const":"accommodation"},"reference":{"type":"string"},"status":{"type":"string","enum":["requested","pending","confirmed","seated","finished","no_show","cancelled"]},"mode":{"type":"string","enum":["instant","request"],"description":"Create only."},"replayed":{"type":"boolean"},"check_in":{"type":"string","format":"date"},"check_out":{"type":"string","format":"date"},"nights":{"type":"integer"},"adults":{"type":"integer"},"children":{"type":"integer"},"unit_type":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}}},"total_cents":{"type":"integer"},"currency":{"type":"string"},"time_zone":{"type":"string"},"source":{"type":"string"},"source_ref":{"type":"string"},"guest":{"type":"object","properties":{"name":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string"}}},"manage_url":{"type":"string","format":"uri","description":"The guest’s own view/cancel link. Hand it to the human."},"venue":{"type":"object"},"created_at":{"type":"string"}},"example":{"booking_id":"5d2c1f00-0000-4000-8000-000000000001","kind":"accommodation","reference":"A1B2C3","status":"confirmed","mode":"instant","replayed":false,"check_in":"2026-12-01","check_out":"2026-12-03","nights":2,"adults":2,"children":0,"unit_type":{"id":"0b8c6a52-0000-4000-8000-000000000001","name":"Riverside Cabin"},"total_cents":43800,"currency":"AUD","source":"api","source_ref":"agent:ExampleAgent","manage_url":"https://app.bookmyvenue.com.au/book/demo-stays?manage=00000000-0000-4000-8000-000000000000"}},"StayList":{"type":"object","properties":{"stays":{"type":"array","items":{"$ref":"#/components/schemas/Stay"}}}},"GuestConfirmRequest":{"type":"object","required":["token"],"properties":{"token":{"type":"string","format":"uuid","description":"The confirmation token from the guest’s message. Case-insensitive; surrounding whitespace is ignored."}},"description":"The guest’s confirmation."},"GuestConfirmResult":{"type":"object","required":["ok","booking_id","status","already"],"properties":{"ok":{"type":"boolean","enum":[true]},"booking_id":{"type":"string","format":"uuid"},"status":{"$ref":"#/components/schemas/BookingStatus"},"already":{"type":"boolean","description":"true when this token had already confirmed the booking; nothing changed this time."}}},"GuestConfirmError":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","description":"Stable, machine-readable code (see the response’s `x-bmv-error-codes`)."},"message":{"type":"string","description":"A sentence to show the guest. Wording may change; do not parse it."},"status":{"$ref":"#/components/schemas/BookingStatus","description":"Present on `not_confirmable`: the booking’s current status."}},"description":"The guest-confirm error body: `message` (for a person) rather than the keyed API’s `detail`."}}},"x-bmv-agent":{"consent":"Only call create (POST /stays/bookings, MCP create_booking) after the user has confirmed the guest name, contact details and the exact dates and unit type. The booking is final and the venue is notified immediately.","modes":{"instant":"Default. Confirmed immediately when the venue is not in request mode and the unit type takes no deposit.","request":"Held as `requested`; the venue confirms or declines and the guest is told.","off":"403 agent_bookings_disabled. Send the guest the booking page instead."},"contact":"An instant agent booking needs the guest’s Australian mobile AND email (422 contact_required_instant); a request needs one of them.","money":"Money never moves through an agent. A unit type that takes a deposit answers 409 deposit_required with `booking_url` for the guest to book it themselves.","caps":{"rate_per_min":60,"quota_per_day":2000,"bookings_per_venue_per_day":10,"burst":"3 bookings per key per venue per 10 minutes (429 burst_cap)","guest":"2 open agent bookings per guest phone or email per venue (429 guest_cap)"},"attribution":"source = api, source_ref = agent:<your key label>; the guest’s confirmation names you and carries a cancel link.","reschedule":"An agent may move a booking it created only when the venue has turned on agent rescheduling (a per-venue option, default off). Off → 409 not_reschedulable with the guest’s manage_url; on → the same rules as a guest moving it themselves.","privacy":"An agent key reads only the bookings it created. Webhook payloads carry the guest’s name only. Collection notice: the venue’s booking page links BookMyVenue’s privacy collection notice (APP 5).","mcp":"https://app.bookmyvenue.com.au/api/mcp (Streamable HTTP, stateless, POST JSON-RPC; same bearer key).","llms_txt":"https://app.bookmyvenue.com.au/book/<slug>/llms.txt"}}