Three surfaces:
- **Public** (`/api/v1/{venue}/*`): no authentication, CORS `*`, per-IP rate limits. Powers the booking widget and the venue's own website. Never returns customer data.
- **REST** (`/api/v1/rest/*`): read-only, `Authorization: Bearer vb_live_…` (test keys `vb_test_…`). Create keys under Admin > Integrations; available on the Venue and Pro plans. 60 requests per minute per key (`X-RateLimit-*` headers, `Retry-After` on 429). Lists are newest first with cursor pagination (`{data, next_cursor}`).
- **Webhooks**: signed HTTPS POSTs to your URL (Pro plan), see the Webhooks tag.
Public error bodies are `{error}`, `{error, problems}` or `{problems}`; REST errors are `{error, message}`.
Public
Unauthenticated endpoints for booking forms and websites.
get/api/v1/{venue}/catalog
Venue catalogue (getCatalog)
Everything the booking form needs: venue settings, active rooms, booking types, event types, extras and packages, plus which AI helpers are on. Prices are omitted when the venue hides them. Cached for 60 s. Rate limit: 120 requests per minute per IP.
Parameters
| Name | In | Type | Req. | Description |
|---|
| venue | path | string | yes | Venue slug, e.g. `uks-maja`. |
| lang | query | "et" | "en" | "ru" | "lv" | "lt" | | Return venue-entered names in this language where the venue has translated them. |
Responses
- 200 The catalogue.
| Property | Type | Req. | Description |
|---|
| venue | object | yes | |
| venue.name | string | yes | |
| venue.slug | string | yes | |
| venue.locale | string | yes | |
| venue.currency | string | yes | |
| venue.vatRate | number | yes | |
| venue.pricesIncludeVat | boolean | yes | |
| venue.showPrices | boolean | yes | |
| venue.startStepMinutes | integer | yes | |
| venue.minBookingMinutes | integer | yes | |
| venue.maxBookingMinutes | integer | yes | |
| venue.timezone | string | yes | |
| venue.brand | object | yes | Colours and logo for embedding. |
| rooms | Room[] | yes | |
| rooms[].id | string (uuid) | yes | |
| rooms[].name | string | yes | |
| rooms[].description | string | null | yes | |
| rooms[].capacity | integer | yes | |
| rooms[].areaM2 | number | null | yes | |
| rooms[].equipment | any | yes | Equipment list as entered by the venue. |
| rooms[].restrictions | string | null | yes | |
| rooms[].openHours | object | yes | Opening hours per weekday. |
| rooms[].layouts | object[] | yes | |
| rooms[].layouts[].style | string | yes | |
| rooms[].layouts[].capacity | integer | yes | |
| bookingTypes | object[] | yes | |
| bookingTypes[].id | string (uuid) | yes | |
| bookingTypes[].code | string | yes | |
| bookingTypes[].name | string | yes | |
| bookingTypes[].description | string | null | yes | |
| bookingTypes[].terms | string | null | yes | |
| bookingTypes[].collectsCompany | boolean | yes | |
| bookingTypes[].allowsPublicEvent | boolean | yes | |
| eventTypes | object[] | yes | |
| eventTypes[].id | string (uuid) | yes | |
| eventTypes[].name | string | yes | |
| eventTypes[].bookingTypeIds | array | null | yes | |
| extras | object[] | yes | |
| extras[].id | string (uuid) | yes | |
| extras[].name | string | yes | |
| extras[].description | null | yes | |
| extras[].unit | string | yes | |
| extras[].amountCents | integer | null | yes | |
| extras[].bookingTypeIds | array | null | yes | |
| extras[].minQuantity | integer | null | yes | |
| packages | object[] | yes | |
| packages[].id | string (uuid) | yes | |
| packages[].name | string | yes | |
| packages[].description | string | null | yes | |
| packages[].bookingTypeIds | array | null | yes | |
| packages[].roomIds | array | null | yes | |
| packages[].wholeHouse | boolean | yes | |
| packages[].perPersonCents | integer | null | yes | |
| packages[].minGuests | integer | null | yes | |
| packages[].maxMinutes | integer | null | yes | |
| packages[].includedExtraIds | array | null | yes | |
| ai | object | yes | |
| ai.describe | boolean | yes | |
| ai.faq | boolean | yes | |
- 404 Unknown venue, or the feature is switched off (`feature_off`).
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
- 410 The venue is being closed (`venue_closed`).
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
- 429 Per-IP rate limit exceeded.
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
Example
curl "https://saalo.ee/api/v1/uks-maja/catalog"
get/api/v1/{venue}/rooms
Active rooms (listRooms)
The `rooms` part of the catalogue. Rate limit: 120 requests per minute per IP.
Parameters
| Name | In | Type | Req. | Description |
|---|
| venue | path | string | yes | Venue slug, e.g. `uks-maja`. |
| lang | query | "et" | "en" | "ru" | "lv" | "lt" | | Return venue-entered names in this language where the venue has translated them. |
Responses
- 200 Rooms.
| Property | Type | Req. | Description |
|---|
| rooms | Room[] | yes | |
| rooms[].id | string (uuid) | yes | |
| rooms[].name | string | yes | |
| rooms[].description | string | null | yes | |
| rooms[].capacity | integer | yes | |
| rooms[].areaM2 | number | null | yes | |
| rooms[].equipment | any | yes | Equipment list as entered by the venue. |
| rooms[].restrictions | string | null | yes | |
| rooms[].openHours | object | yes | Opening hours per weekday. |
| rooms[].layouts | object[] | yes | |
| rooms[].layouts[].style | string | yes | |
| rooms[].layouts[].capacity | integer | yes | |
- 404 Unknown venue, or the feature is switched off (`feature_off`).
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
- 410 The venue is being closed (`venue_closed`).
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
- 429 Per-IP rate limit exceeded.
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
Example
curl "https://saalo.ee/api/v1/uks-maja/rooms"
get/api/v1/{venue}/availability
Free start times for one day (getAvailability)
Free start times per room for one date and duration. `commonStarts` are the starts where every chosen room is free; `takenStarts` are valid starts that are taken in at least one room (the form offers the waitlist for them). Provide `rooms` or `wholeHouse=1`. Rate limit: 120 requests per minute per IP.
Parameters
| Name | In | Type | Req. | Description |
|---|
| venue | path | string | yes | Venue slug, e.g. `uks-maja`. |
| date | query | string (/^\d{4}-\d{2}-\d{2}$/) | yes | Local date, `YYYY-MM-DD`. |
| duration | query | integer (min 15, max 20160) | yes | Length in minutes, 15 to 20160. |
| rooms | query | string | | Comma-separated room ids. Required unless `wholeHouse=1`. |
| wholeHouse | query | "0" | "1" | | `1` = all active rooms. |
| eventTypeId | query | string | | Event type id (can affect conflicts). |
Responses
- 200 Availability.
| Property | Type | Req. | Description |
|---|
| date | string (date) | yes | |
| rooms | object[] | yes | |
| rooms[].roomId | string (uuid) | yes | |
| rooms[].freeStarts | string (/^\d{2}:\d{2}$/)[] | yes | |
| rooms[].takenStarts | string (/^\d{2}:\d{2}$/)[] | yes | |
| commonStarts | string (/^\d{2}:\d{2}$/)[] | yes | |
| takenStarts | string (/^\d{2}:\d{2}$/)[] | yes | |
- 400 Validation failed.
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
- 404 Unknown venue, or the feature is switched off (`feature_off`).
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
- 410 The venue is being closed (`venue_closed`).
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
- 429 Per-IP rate limit exceeded.
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
Example
curl "https://saalo.ee/api/v1/uks-maja/availability?date=2026-11-20&duration=…"
get/api/v1/{venue}/availability/month
Day statuses for a month (getMonthAvailability)
One status per day of the month for the chosen rooms: `free`, `partial`, `full` or `closed`. The month must be within the booking horizon (about 18 months ahead). Rate limit: 120 requests per minute per IP.
Parameters
| Name | In | Type | Req. | Description |
|---|
| venue | path | string | yes | Venue slug, e.g. `uks-maja`. |
| month | query | string (/^\d{4}-(0[1-9]|1[0-2])$/) | yes | `YYYY-MM`. |
| roomIds | query | string | | Comma-separated room ids. Required unless `wholeHouse=1`. |
| wholeHouse | query | "0" | "1" | | `1` = all active rooms. |
| durationMinutes | query | integer (min 15, max 20160) | yes | Length in minutes, 15 to 20160. |
| bookingTypeId | query | string | | Booking type id (accepted, currently informational). |
| eventTypeId | query | string | | Event type id (can affect conflicts). |
Responses
- 200 Month calendar.
| Property | Type | Req. | Description |
|---|
| month | string (/^\d{4}-\d{2}$/) | yes | |
| days | object[] | yes | |
| days[].date | string (date) | yes | |
| days[].status | "free" | "partial" | "full" | "closed" | yes | |
- 400 Validation failed.
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
- 404 Unknown venue, or the feature is switched off (`feature_off`).
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
- 410 The venue is being closed (`venue_closed`).
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
- 429 Per-IP rate limit exceeded.
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
Example
curl "https://saalo.ee/api/v1/uks-maja/availability/month?month=2026-11&durationMinutes=…"
post/api/v1/{venue}/estimate
Price estimate and availability check (estimatePrice)
Same body as `createRequest` without `customer`. Nothing is stored. Prices come from the pricing engine only; when the venue hides prices the amounts are `null`/empty. `needsManualPrice: true` means the final price comes with the quote. Validation problems answer 400 with `available: false`. Rate limit: 60 requests per minute per IP.
Parameters
| Name | In | Type | Req. | Description |
|---|
| venue | path | string | yes | Venue slug, e.g. `uks-maja`. |
| lang | query | "et" | "en" | "ru" | "lv" | "lt" | | Return venue-entered names in this language where the venue has translated them. |
Request body (application/json)
| Property | Type | Req. | Description |
|---|
| bookingTypeId | string (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/) | yes | |
| eventTypeId | any | | |
| date | string (/^\d{4}-\d{2}-\d{2}$/) | yes | |
| endDate | any | | |
| startTime | string (/^([01]\d|2[0-3]):[0-5]\d$/) | yes | |
| endTime | string (/^([01]\d|2[0-3]):[0-5]\d$|^24:00$/) | yes | |
| roomIds | string (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/)[] | | |
| wholeHouse | boolean | | |
| guests | integer (min 1, max 10000) | yes | |
| extras | object[] | | |
| extras[].extraId | string (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/) | yes | |
| extras[].quantity | integer (min 1, max 1000) | | |
| packageId | any | | |
| accommodation | any | | |
| layout | any | | |
| locale | any | | |
| title | any | | |
| description | any | | |
| isPublicEvent | boolean | | |
| message | any | | |
| lead | any | | |
Responses
- 200 Estimate.
| Property | Type | Req. | Description |
|---|
| available | boolean | yes | |
| problems | Problem[] | yes | |
| problems[].code | string | yes | Stable machine-readable code, e.g. `invalid`, `not_available`, `not_taken`, `venue_closed`. |
| problems[].message | string | yes | Human-readable, in the customer's language. |
| problems[].field | string | | Offending field, when there is one. |
| lines | object[] | yes | |
| lines[].label | string | yes | |
| lines[].quantity | number | yes | |
| lines[].unit | string | yes | |
| lines[].unitPriceCents | integer | yes | |
| lines[].totalCents | integer | yes | |
| netCents | integer | null | yes | |
| vatCents | integer | null | yes | |
| grossCents | integer | null | yes | |
| needsManualPrice | boolean | yes | |
| flags | object[] | yes | |
| flags[].code | string | yes | |
| flags[].message | string | yes | |
- 400 Validation failed (`available` is false).
| Property | Type | Req. | Description |
|---|
| available | boolean | yes | |
| problems | Problem[] | yes | |
| problems[].code | string | yes | Stable machine-readable code, e.g. `invalid`, `not_available`, `not_taken`, `venue_closed`. |
| problems[].message | string | yes | Human-readable, in the customer's language. |
| problems[].field | string | | Offending field, when there is one. |
| lines | object[] | yes | |
| lines[].label | string | yes | |
| lines[].quantity | number | yes | |
| lines[].unit | string | yes | |
| lines[].unitPriceCents | integer | yes | |
| lines[].totalCents | integer | yes | |
| netCents | integer | null | yes | |
| vatCents | integer | null | yes | |
| grossCents | integer | null | yes | |
| needsManualPrice | boolean | yes | |
| flags | object[] | yes | |
| flags[].code | string | yes | |
| flags[].message | string | yes | |
- 404 Unknown venue, or the feature is switched off (`feature_off`).
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
- 410 The venue is being closed (`venue_closed`).
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
- 429 Per-IP rate limit exceeded.
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
Example
curl -X POST "https://saalo.ee/api/v1/uks-maja/estimate" \
-H "Content-Type: application/json" \
-d '{
"bookingTypeId": "…",
"date": "2026-11-20",
"startTime": "18:00",
"endTime": "22:00",
"guests": 1
}'
post/api/v1/{venue}/requests
Submit a booking request (createRequest)
Creates a booking request; staff review it and send a quote, unless the booking type is set to confirm instantly (or the signed-in customer is pre-approved) and the request is fully priced and free: then the response has `status`. `?source=widget` marks the source as the widget (default `api`). Problems are worded in `locale` (body), else `?lang=`, else the venue's language. A request that trips the spam trap answers 201 with `bookingNumber: null`. Rate limit: 10 requests per minute per IP.
Parameters
| Name | In | Type | Req. | Description |
|---|
| venue | path | string | yes | Venue slug, e.g. `uks-maja`. |
| lang | query | "et" | "en" | "ru" | "lv" | "lt" | | Return venue-entered names in this language where the venue has translated them. |
| source | query | "widget" | "api" | | `widget` for the embeddable widget; anything else is recorded as `api`. |
Request body (application/json)
| Property | Type | Req. | Description |
|---|
| bookingTypeId | string (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/) | yes | |
| eventTypeId | any | | |
| date | string (/^\d{4}-\d{2}-\d{2}$/) | yes | |
| endDate | any | | |
| startTime | string (/^([01]\d|2[0-3]):[0-5]\d$/) | yes | |
| endTime | string (/^([01]\d|2[0-3]):[0-5]\d$|^24:00$/) | yes | |
| roomIds | string (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/)[] | | |
| wholeHouse | boolean | | |
| guests | integer (min 1, max 10000) | yes | |
| extras | object[] | | |
| extras[].extraId | string (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/) | yes | |
| extras[].quantity | integer (min 1, max 1000) | | |
| packageId | any | | |
| accommodation | any | | |
| layout | any | | |
| locale | any | | |
| title | any | | |
| description | any | | |
| isPublicEvent | boolean | | |
| message | any | | |
| lead | any | | |
| customer | object | yes | |
| customer.name | string (min length 2, max length 200) | yes | |
| customer.email | string (email, /^(?:[A-Za-z0-9_'+\-]+\.)*[A-Za-z0-9_'+\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$/, max length 200) | yes | |
| customer.phone | any | | |
| customer.company | any | | |
| customer.regCode | any | | |
| customer.newsletterConsent | boolean | | |
| website_url | string | | Honeypot: leave empty. A filled value is silently dropped. |
| form_stamp | string | | Signed render time of the venue's form. API clients omit it. |
Responses
- 201 Request created.
| Property | Type | Req. | Description |
|---|
| bookingNumber | string | null | yes | Booking number, e.g. `UM-2026-0042`. |
| estimate | Estimate | null | yes | Same shape as the `estimatePrice` response; `null` when dropped as spam. |
| quoteUrl | string | | Only present when the venue sends instant quotes with a visible link. |
| status | "confirmed" | "awaiting_payment" | "awaiting_staff" | | Only present for an instant confirmation: `confirmed`, `awaiting_payment` (deposit link in `paymentUrl`) or `awaiting_staff`. |
| paymentUrl | string | | Deposit payment link, with `status: awaiting_payment`. |
- 400 Validation failed.
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
- 404 Unknown venue, or the feature is switched off (`feature_off`).
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
- 409 The chosen time is not available.
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
- 410 The venue is being closed (`venue_closed`).
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
- 429 Per-IP rate limit exceeded.
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
Example
curl -X POST "https://saalo.ee/api/v1/uks-maja/requests" \
-H "Content-Type: application/json" \
-d '{
"bookingTypeId": "…",
"date": "2026-11-20",
"startTime": "18:00",
"endTime": "22:00",
"guests": 1,
"customer": {
"name": "Mari Maasikas",
"email": "mari@example.ee"
}
}'
post/api/v1/{venue}/requests/series
Repeat a booking weekly or every second week (signed-in customer) (createRequestSeries)
Body is the booking request (without `customer`: the signed-in portal customer from the session cookie is used) plus `repeat: { intervalWeeks: 1|2, endsOn }` (2 to 26 dates, the weekday of `date`). With `preview: true` nothing is saved and `dates` lists every date with its clash. Otherwise one booking is created per free date: confirmed at once for a pre-approved customer when the instant-confirm rules hold for that date, else a request for staff. 401 `login_required` without a portal session. Rate limit: 10 requests per minute per IP.
Parameters
| Name | In | Type | Req. | Description |
|---|
| venue | path | string | yes | Venue slug, e.g. `uks-maja`. |
| lang | query | "et" | "en" | "ru" | "lv" | "lt" | | Return venue-entered names in this language where the venue has translated them. |
Request body (application/json)
| Property | Type | Req. | Description |
|---|
| bookingTypeId | string (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/) | yes | |
| eventTypeId | any | | |
| date | string (/^\d{4}-\d{2}-\d{2}$/) | yes | |
| endDate | any | | |
| startTime | string (/^([01]\d|2[0-3]):[0-5]\d$/) | yes | |
| endTime | string (/^([01]\d|2[0-3]):[0-5]\d$|^24:00$/) | yes | |
| roomIds | string (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/)[] | | |
| wholeHouse | boolean | | |
| guests | integer (min 1, max 10000) | yes | |
| extras | object[] | | |
| extras[].extraId | string (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/) | yes | |
| extras[].quantity | integer (min 1, max 1000) | | |
| packageId | any | | |
| accommodation | any | | |
| layout | any | | |
| locale | any | | |
| title | any | | |
| description | any | | |
| isPublicEvent | boolean | | |
| message | any | | |
| lead | any | | |
| customer | object | yes | |
| customer.name | string (min length 2, max length 200) | yes | |
| customer.email | string (email, /^(?:[A-Za-z0-9_'+\-]+\.)*[A-Za-z0-9_'+\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$/, max length 200) | yes | |
| customer.phone | any | | |
| customer.company | any | | |
| customer.regCode | any | | |
| customer.newsletterConsent | boolean | | |
| repeat | object | | |
| repeat.intervalWeeks | 1 | 2 | yes | |
| repeat.endsOn | string (date) | yes | |
| preview | boolean | | |
Responses
- 200 Preview of the dates.
| Property | Type | Req. | Description |
|---|
| dates | object[] | yes | |
| dates[].date | string (date) | yes | |
| dates[].ok | boolean | yes | |
| dates[].problems | string[] | yes | |
- 201 Series created.
| Property | Type | Req. | Description |
|---|
| recurrenceId | string (uuid) | yes | |
| bookingNumber | string | yes | Number of the first booking. |
| created | object[] | yes | |
| created[].date | string (date) | yes | |
| created[].number | string | yes | |
| created[].status | "confirmed" | "request" | yes | |
| skipped | string (date)[] | yes | |
- 400 Validation failed.
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
- 401 Not signed in to the customer portal.
| Property | Type | Req. | Description |
|---|
| error | string | yes | |
- 404 Unknown venue, or the feature is switched off (`feature_off`).
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
- 409 The chosen time is not available.
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
- 410 The venue is being closed (`venue_closed`).
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
- 429 Per-IP rate limit exceeded.
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
Example
curl -X POST "https://saalo.ee/api/v1/uks-maja/requests/series" \
-H "Content-Type: application/json" \
-d '{
"bookingTypeId": "…",
"date": "2026-11-20",
"startTime": "18:00",
"endTime": "22:00",
"guests": 1,
"customer": {
"name": "Mari Maasikas",
"email": "mari@example.ee"
}
}'
post/api/v1/{venue}/waitlist
Join the waitlist for a taken slot (joinWaitlist)
Body is the booking request plus `waitlistConsent: true`. Only accepted when the chosen rooms and time really clash (otherwise 409 `not_taken`: send a normal request). Books nothing; the oldest waiter gets an email with an offer link when the slot frees up. Rate limit: 10 requests per minute per IP.
Parameters
| Name | In | Type | Req. | Description |
|---|
| venue | path | string | yes | Venue slug, e.g. `uks-maja`. |
Request body (application/json)
| Property | Type | Req. | Description |
|---|
| bookingTypeId | string (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/) | yes | |
| eventTypeId | any | | |
| date | string (/^\d{4}-\d{2}-\d{2}$/) | yes | |
| endDate | any | | |
| startTime | string (/^([01]\d|2[0-3]):[0-5]\d$/) | yes | |
| endTime | string (/^([01]\d|2[0-3]):[0-5]\d$|^24:00$/) | yes | |
| roomIds | string (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/)[] | | |
| wholeHouse | boolean | | |
| guests | integer (min 1, max 10000) | yes | |
| extras | object[] | | |
| extras[].extraId | string (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/) | yes | |
| extras[].quantity | integer (min 1, max 1000) | | |
| packageId | any | | |
| accommodation | any | | |
| layout | any | | |
| locale | any | | |
| title | any | | |
| description | any | | |
| isPublicEvent | boolean | | |
| message | any | | |
| lead | any | | |
| customer | object | yes | |
| customer.name | string (min length 2, max length 200) | yes | |
| customer.email | string (email, /^(?:[A-Za-z0-9_'+\-]+\.)*[A-Za-z0-9_'+\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$/, max length 200) | yes | |
| customer.phone | any | | |
| customer.company | any | | |
| customer.regCode | any | | |
| customer.newsletterConsent | boolean | | |
| waitlistConsent | true | yes | Required, must be `true`: the customer agrees to be emailed when the slot frees up. |
| website_url | string | | Honeypot: leave empty. A filled value is silently dropped. |
| form_stamp | string | | Signed render time of the venue's form. API clients omit it. |
Responses
- 201 Joined.
| Property | Type | Req. | Description |
|---|
| ok | true | yes | |
- 400 Validation failed.
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
- 404 Unknown venue, or the feature is switched off (`feature_off`).
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
- 409 The chosen time is not available.
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
- 410 The venue is being closed (`venue_closed`).
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
- 429 Per-IP rate limit exceeded.
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
Example
curl -X POST "https://saalo.ee/api/v1/uks-maja/waitlist" \
-H "Content-Type: application/json" \
-d '{
"bookingTypeId": "…",
"date": "2026-11-20",
"startTime": "18:00",
"endTime": "22:00",
"guests": 1,
"customer": {
"name": "Mari Maasikas",
"email": "mari@example.ee"
},
"waitlistConsent": true
}'
post/api/v1/{venue}/describe
Turn a free-text event description into form values (describeEvent)
The "Describe your event" helper. Never returns prices or availability and stores nothing. 404 `feature_off` when the venue has switched it off or its plan does not include it. Text is limited to 1500 characters. Rate limit: 6 requests per minute per IP.
Parameters
| Name | In | Type | Req. | Description |
|---|
| venue | path | string | yes | Venue slug, e.g. `uks-maja`. |
| lang | query | "et" | "en" | "ru" | "lv" | "lt" | | Return venue-entered names in this language where the venue has translated them. |
Request body (application/json)
| Property | Type | Req. | Description |
|---|
| text | string (min length 1, max length 1500) | yes | What the customer wrote. |
| lang | "et" | "en" | "ru" | "lv" | "lt" | | Customer language. |
| website_url | string | | Honeypot: leave empty. A filled value is silently dropped. |
| form_stamp | string | | Signed render time of the venue's form. API clients omit it. |
Responses
- 200 Suggested form values.
| Property | Type | Req. | Description |
|---|
| fields | object | yes | |
| fields.bookingTypeId | string | null (uuid) | yes | |
| fields.eventTypeId | string | null (uuid) | yes | |
| fields.date | string | null (date) | yes | |
| fields.startTime | string | null | yes | |
| fields.endTime | string | null | yes | |
| fields.durationMinutes | integer | null | yes | |
| fields.guests | integer | null | yes | |
| fields.layout | "theatre" | "classroom" | "banquet" | "cocktail" | "boardroom" | "u_shape" | "cabaret" | yes | |
| fields.roomIds | string (uuid)[] | yes | |
| fields.extras | object[] | yes | |
| fields.extras[].extraId | string (uuid) | yes | |
| fields.extras[].quantity | integer | yes | |
| filled | "bookingType" | "eventType" | "date" | "startTime" | "duration" | "guests" | "layout" | "rooms" | "extras"[] | yes | |
| unclear | string[] | yes | |
| source | "ai" | "parser" | yes | |
- 400 `invalid`, `text_required` or `text_too_long` (with `max`).
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
- 404 Unknown venue, or the feature is switched off (`feature_off`).
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
- 410 The venue is being closed (`venue_closed`).
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
- 429 Per-IP rate limit exceeded.
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
Example
curl -X POST "https://saalo.ee/api/v1/uks-maja/describe" \
-H "Content-Type: application/json" \
-d '{
"text": "…"
}'
post/api/v1/{venue}/ask
Ask a question about the venue (askQuestion)
The "Ask a question" helper: answers from the venue's published data only; never prices or availability; nothing is stored. 404 `feature_off` when switched off or not in the plan. Rate limit: 6 requests per minute per IP.
Parameters
| Name | In | Type | Req. | Description |
|---|
| venue | path | string | yes | Venue slug, e.g. `uks-maja`. |
| lang | query | "et" | "en" | "ru" | "lv" | "lt" | | Return venue-entered names in this language where the venue has translated them. |
Request body (application/json)
| Property | Type | Req. | Description |
|---|
| question | string (min length 1) | yes | Length limit applies (`question_too_long` with `max`). |
| lang | "et" | "en" | "ru" | "lv" | "lt" | | Visitor language. |
| website_url | string | | Honeypot: leave empty. A filled value is silently dropped. |
| form_stamp | string | | Signed render time of the venue's form. API clients omit it. |
Responses
- 200 Answer.
| Property | Type | Req. | Description |
|---|
| answer | string | yes | |
| sourceIds | string[] | yes | |
| confidence | "answered" | "partial" | "unknown" | yes | |
| handoff | boolean | yes | True when the visitor should ask the team (open the request form). |
| source | "ai" | "keyword" | yes | |
- 400 `invalid`, `question_required` or `question_too_long` (with `max`).
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
- 404 Unknown venue, or the feature is switched off (`feature_off`).
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
- 410 The venue is being closed (`venue_closed`).
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
- 429 Per-IP rate limit exceeded.
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
Example
curl -X POST "https://saalo.ee/api/v1/uks-maja/ask" \
-H "Content-Type: application/json" \
-d '{
"question": "…"
}'
get/api/v1/{venue}/testimonials
Published customer comments (listTestimonials)
Up to 50 newest comments customers agreed to publish: first name plus initial, score, month. No other customer data. Cached for 60 s. Rate limit: 120 requests per minute per IP.
Parameters
| Name | In | Type | Req. | Description |
|---|
| venue | path | string | yes | Venue slug, e.g. `uks-maja`. |
Responses
- 200 Testimonials.
| Property | Type | Req. | Description |
|---|
| testimonials | object[] | yes | |
| testimonials[].name | string | null | yes | First name and initial, e.g. `Anna T.`. |
| testimonials[].score | integer (min 0, max 10) | yes | |
| testimonials[].month | string (/^\d{4}-\d{2}$/) | yes | |
| testimonials[].comment | string | yes | |
- 404 Unknown venue, or the feature is switched off (`feature_off`).
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
- 410 The venue is being closed (`venue_closed`).
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
- 429 Per-IP rate limit exceeded.
| Property | Type | Req. | Description |
|---|
| error | string | | Machine code, e.g. `not_found`, `rate_limited`, `invalid`, `venue_closed`, `feature_off`, `text_required`. |
| problems | Problem | string[] | | |
| max | integer | | Length limit, with `text_too_long` / `question_too_long`. |
Example
curl "https://saalo.ee/api/v1/uks-maja/testimonials"
get/api/v1/{venue}/ics/{room}
iCalendar feed of a room (getIcsFeed)
Read-only iCalendar feed of a room's blocking bookings and closures (30 days back, 365 days ahead), for subscribing from Google/Apple/Outlook calendars. Events carry no customer data. Access is by the secret `key` shown in Admin; a wrong key, room or venue is a plain 404. Rate limit: 30 requests per minute per IP.
Parameters
| Name | In | Type | Req. | Description |
|---|
| venue | path | string | yes | Venue slug, e.g. `uks-maja`. |
| room | path | string | yes | Room id, or `all` for every room. |
| key | query | string | yes | Feed key from Admin (HMAC; not an API key). |
Responses
- 200 iCalendar document.
text/calendar - 404 Not found or wrong key (plain text).
text/plain - 429 Rate limit exceeded (plain text).
text/plain
Example
curl "https://saalo.ee/api/v1/uks-maja/ics/all?key=KEY"
Webhooks
Events the platform sends to your URL.
webhook booking.confirmed
A booking was confirmed (the customer accepted the quote, or staff confirmed it).
Sent as an HTTPS POST to each URL configured under Admin > Integrations that subscribes to this event (Pro plan). The body carries ids, number, times and status only, no customer contact data; fetch the customer through the REST API (`customer_id`). Verify `x-venue-signature` (HMAC-SHA256 of the raw body, hex, prefixed `sha256=`) before trusting the payload. Reply 2xx within 5 seconds; redirects are not followed. Failed deliveries are retried after 1 min, 5 min, 30 min, 2 h and 12 h with the same `x-venue-delivery` id and body, then marked dead.
Headers
x-venue-signature: `sha256=` + hex HMAC-SHA256 of the raw request body, keyed with the webhook's signing secret.x-venue-event: The event name.x-venue-delivery: Unique id of this delivery; retries reuse it, so use it to de-duplicate.
Body
| Property | Type | Req. | Description |
|---|
| event | "booking.confirmed" | yes | Event name. |
| occurred_at | string (date-time) | yes | When the event was emitted (ISO 8601, UTC). |
| data | WebhookBookingData | yes | |
| data.bookingId | string (uuid) | yes | |
| data.number | string | yes | Booking number. |
| data.status | "new_request" | "quote_drafting" | "awaiting_staff" | "quote_sent" | "awaiting_customer" | "confirmed" | "cancelled" | "completed" | yes | |
| data.startsAt | string (date-time) | yes | |
| data.endsAt | string (date-time) | yes | |
| data.guests | integer | yes | |
{
"event": "booking.confirmed",
"occurred_at": "2026-11-05T12:00:03.120Z",
"data": {
"bookingId": "6f1c…",
"number": "B-2026-00042",
"status": "confirmed",
"startsAt": "2026-11-20T16:00:00.000Z",
"endsAt": "2026-11-20T20:00:00.000Z",
"guests": 40
}
}
webhook booking.cancelled
A booking was cancelled.
Sent as an HTTPS POST to each URL configured under Admin > Integrations that subscribes to this event (Pro plan). The body carries ids, number, times and status only, no customer contact data; fetch the customer through the REST API (`customer_id`). Verify `x-venue-signature` (HMAC-SHA256 of the raw body, hex, prefixed `sha256=`) before trusting the payload. Reply 2xx within 5 seconds; redirects are not followed. Failed deliveries are retried after 1 min, 5 min, 30 min, 2 h and 12 h with the same `x-venue-delivery` id and body, then marked dead.
Headers
x-venue-signature: `sha256=` + hex HMAC-SHA256 of the raw request body, keyed with the webhook's signing secret.x-venue-event: The event name.x-venue-delivery: Unique id of this delivery; retries reuse it, so use it to de-duplicate.
Body
| Property | Type | Req. | Description |
|---|
| event | "booking.cancelled" | yes | Event name. |
| occurred_at | string (date-time) | yes | When the event was emitted (ISO 8601, UTC). |
| data | WebhookBookingData | yes | |
| data.bookingId | string (uuid) | yes | |
| data.number | string | yes | Booking number. |
| data.status | "new_request" | "quote_drafting" | "awaiting_staff" | "quote_sent" | "awaiting_customer" | "confirmed" | "cancelled" | "completed" | yes | |
| data.startsAt | string (date-time) | yes | |
| data.endsAt | string (date-time) | yes | |
| data.guests | integer | yes | |
{
"event": "booking.cancelled",
"occurred_at": "2026-11-05T12:00:03.120Z",
"data": {
"bookingId": "6f1c…",
"number": "B-2026-00042",
"status": "confirmed",
"startsAt": "2026-11-20T16:00:00.000Z",
"endsAt": "2026-11-20T20:00:00.000Z",
"guests": 40
}
}
webhook booking.changed
A booking's time, rooms or details were changed.
Sent as an HTTPS POST to each URL configured under Admin > Integrations that subscribes to this event (Pro plan). The body carries ids, number, times and status only, no customer contact data; fetch the customer through the REST API (`customer_id`). Verify `x-venue-signature` (HMAC-SHA256 of the raw body, hex, prefixed `sha256=`) before trusting the payload. Reply 2xx within 5 seconds; redirects are not followed. Failed deliveries are retried after 1 min, 5 min, 30 min, 2 h and 12 h with the same `x-venue-delivery` id and body, then marked dead.
Headers
x-venue-signature: `sha256=` + hex HMAC-SHA256 of the raw request body, keyed with the webhook's signing secret.x-venue-event: The event name.x-venue-delivery: Unique id of this delivery; retries reuse it, so use it to de-duplicate.
Body
| Property | Type | Req. | Description |
|---|
| event | "booking.changed" | yes | Event name. |
| occurred_at | string (date-time) | yes | When the event was emitted (ISO 8601, UTC). |
| data | WebhookBookingData | yes | |
| data.bookingId | string (uuid) | yes | |
| data.number | string | yes | Booking number. |
| data.status | "new_request" | "quote_drafting" | "awaiting_staff" | "quote_sent" | "awaiting_customer" | "confirmed" | "cancelled" | "completed" | yes | |
| data.startsAt | string (date-time) | yes | |
| data.endsAt | string (date-time) | yes | |
| data.guests | integer | yes | |
{
"event": "booking.changed",
"occurred_at": "2026-11-05T12:00:03.120Z",
"data": {
"bookingId": "6f1c…",
"number": "B-2026-00042",
"status": "confirmed",
"startsAt": "2026-11-20T16:00:00.000Z",
"endsAt": "2026-11-20T20:00:00.000Z",
"guests": 40
}
}
webhook request.created
A new booking request arrived through the public API, the widget or the waitlist.
Sent as an HTTPS POST to each URL configured under Admin > Integrations that subscribes to this event (Pro plan). The body carries ids, number, times and status only, no customer contact data; fetch the customer through the REST API (`customer_id`). Verify `x-venue-signature` (HMAC-SHA256 of the raw body, hex, prefixed `sha256=`) before trusting the payload. Reply 2xx within 5 seconds; redirects are not followed. Failed deliveries are retried after 1 min, 5 min, 30 min, 2 h and 12 h with the same `x-venue-delivery` id and body, then marked dead.
Headers
x-venue-signature: `sha256=` + hex HMAC-SHA256 of the raw request body, keyed with the webhook's signing secret.x-venue-event: The event name.x-venue-delivery: Unique id of this delivery; retries reuse it, so use it to de-duplicate.
Body
| Property | Type | Req. | Description |
|---|
| event | "request.created" | yes | Event name. |
| occurred_at | string (date-time) | yes | When the event was emitted (ISO 8601, UTC). |
| data | WebhookBookingData | yes | |
| data.bookingId | string (uuid) | yes | |
| data.number | string | yes | Booking number. |
| data.status | "new_request" | "quote_drafting" | "awaiting_staff" | "quote_sent" | "awaiting_customer" | "confirmed" | "cancelled" | "completed" | yes | |
| data.startsAt | string (date-time) | yes | |
| data.endsAt | string (date-time) | yes | |
| data.guests | integer | yes | |
{
"event": "request.created",
"occurred_at": "2026-11-05T12:00:03.120Z",
"data": {
"bookingId": "6f1c…",
"number": "B-2026-00042",
"status": "confirmed",
"startsAt": "2026-11-20T16:00:00.000Z",
"endsAt": "2026-11-20T20:00:00.000Z",
"guests": 40
}
}