Public API for table bookings: check availability, create a booking, and let the guest view, change or cancel it with the token returned at creation. Venue-side events are delivered as signed webhooks (see the webhooks section).
Base URL
https://api.thaodien.app
Embed the booking form
Paste this where the form should appear, with your venue slug. It resizes to fit.
Your page can listen for a window message { source: 'venuos-booking', type: 'booking.created', confirmation_code }. No guest details are sent.
Endpoints
post/bookings
Create a booking
Request body
Field
Type
Required
Description
customer_email
string (email)
No
customer_lang
"en" | "vi" | "ko"
No
customer_name
string
Yes
customer_phone
string
Yes
date
string (date)
Yes
marketing_consent
object
No
Only when the guest ticked the box shown from consent-wording. Stored on the booking; it becomes a recorded opt-in only when the guest, signed in with an OTP session, confirms it explicitly.
marketing_consent.accepted
"true"
No
marketing_consent.channels
"sms" | "zalo" | "email"[]
No
marketing_consent.wording_version
string
No
occasion
string
No
party_size
integer
Yes
special_requests
string
No
time
string
Yes
Example: 19:00
venue_id
string (uuid)
Yes
Responses
201Created (CreatedBooking)
400Error (Error)
403Error (Error)
409No longer available. `code` is `table_taken` (no free table) or `pacing_full` (the interval cover cap is reached); a duplicate booking has `duplicate: true`. (Error)
429Error (Error)
201 body
Field
Type
Required
Description
confirmation_code
string
No
manage_url
string (uri)
No
modify_token
string
No
status
"pending" | "confirmed"
No
success
boolean
No
get/bookings/manage/{modify_token}
View a booking
Parameters
Name
In
Type
Required
Description
modify_token
path
string
Yes
The modify_token returned when the booking was created. It is the only credential.
Responses
200The booking
404Error (Error)
410Error (Error)
put/bookings/manage/{modify_token}
Change date, time, party size or requests
Parameters
Name
In
Type
Required
Description
modify_token
path
string
Yes
The modify_token returned when the booking was created. It is the only credential.
Request body
Field
Type
Required
Description
date
string (date)
No
party_size
integer
No
special_requests
string
No
time
string
No
updated_at
string (date-time)
No
The updated_at the page loaded; a mismatch answers 409 conflict_retry.
Responses
200Updated
400Error (Error)
404Error (Error)
409Error (Error)
410Error (Error)
delete/bookings/manage/{modify_token}
Cancel a booking
Parameters
Name
In
Type
Required
Description
modify_token
path
string
Yes
The modify_token returned when the booking was created. It is the only credential.
Confirm the marketing consent ticked on the booking form
Needs the manage link AND a verified customer session (Bearer token from an OTP login) whose customer is the booking's customer: 401 without a session, 403 for another customer. Body {"action": "confirm"}.
Parameters
Name
In
Type
Required
Description
modify_token
path
string
Yes
The modify_token returned when the booking was created. It is the only credential.
Responses
200Recorded
400Error (Error)
401Error (Error)
403Error (Error)
404Error (Error)
409Nothing pending, or the wording changed
410Error (Error)
get/bookings/venues/{venue_id}/availability
Available slots for a date and party size
Times are venue-local wall-clock. A slot is unavailable when no table fits, the venue is closed, or the per-interval cover cap is reached.
Parameters
Name
In
Type
Required
Description
venue_id
path
string (uuid)
Yes
date
query
string (date)
Yes
party_size
query
integer
Yes
Responses
200Slots (Availability)
400Error (Error)
403Error (Error)
404Error (Error)
200 body
Field
Type
Required
Description
date
string (date)
No
party_size
integer
No
settings
object
No
settings.cancellation_hours
integer
No
settings.cancellation_policy
string
No
settings.deposit_amount
number
No
settings.requires_deposit
boolean
No
slots
object[]
No
slots[].available
boolean
No
slots[].remaining_capacity
integer
No
slots[].time
string
No
Example: 19:00
venue_id
string (uuid)
No
venue_name
string
No
get/bookings/venues/{venue_id}/consent-wording
Marketing-consent wording for the booking form
When enabled is true, show the wording next to an unticked checkbox. A ticked box is sent as `marketing_consent` on create. When enabled is false, show nothing.
Parameters
Name
In
Type
Required
Description
venue_id
path
string (uuid)
Yes
Responses
200Wording or {enabled: false}
400Error (Error)
post/bookings/venues/{venue_id}/waitlist
Join the waitlist for a full slot
Parameters
Name
In
Type
Required
Description
venue_id
path
string (uuid)
Yes
Request body
Field
Type
Required
Description
customer_email
string (email)
No
customer_lang
"en" | "vi" | "ko"
No
customer_name
string
Yes
customer_phone
string
Yes
date
string (date)
Yes
marketing_consent
object
No
Only when the guest ticked the box shown from consent-wording. Stored on the booking; it becomes a recorded opt-in only when the guest, signed in with an OTP session, confirms it explicitly.
marketing_consent.accepted
"true"
No
marketing_consent.channels
"sms" | "zalo" | "email"[]
No
marketing_consent.wording_version
string
No
occasion
string
No
party_size
integer
Yes
special_requests
string
No
time
string
Yes
Example: 19:00
venue_id
string (uuid)
Yes
Responses
201Waitlisted
400Error (Error)
403Error (Error)
Webhooks
Events
booking.cancelledA booking was cancelled
booking.createdA booking was created
booking.updatedA booking changed (time, party size, status, table)
HMAC-SHA256 of "<t>.<raw request body>" with the endpoint secret
Tolerance
300 seconds
Retries
Non-2xx or timeout is retried after 1m, 5m, 30m, 2h, 6h, 24h, then marked dead (7 attempts).
Setup
Venue owners add endpoints in the portal under Settings, Data and webhooks.
Verify (Python)
import hmac, hashlib, time
def verify(secret, header, raw_body, tolerance=300):
parts = [p.split('=', 1) for p in header.split(',') if '=' in p]
t = int(dict(parts)['t'])
if abs(time.time() - t) > tolerance:
return False
expected = hmac.new(secret.encode(), str(t).encode() + b'.' + raw_body, hashlib.sha256).hexdigest()
return any(hmac.compare_digest(expected, v) for k, v in parts if k == 'v1')
Errors
Failed requests return a JSON body with these fields.
Field
Type
Required
Description
code
string
No
error
string
No
waitlist_available
boolean
No
400Error
401Error
403Error
404Error
409No longer available. `code` is `table_taken` (no free table) or `pacing_full` (the interval cover cap is reached); a duplicate booking has `duplicate: true`.