Submit a booking
API reference
Submit a booking
The single write. Requires an idempotency key and a Turnstile token.
POST
Submit a booking
The only write in this API. It creates the booking, matches or creates the contact, and
fires the operator notification and customer email off the response path.
The playground is read-only for this endpoint. It can’t mint the single-use Turnstile
token the route requires, and firing it would write a real booking to a real tenant and
burn that contact’s daily quota. Copy the request and run it against your own page.
Idempotency
Idempotency-Key is required: any caller-generated value, 1–128 printable characters.
A retry with the same key returns the original booking instead of creating a second one,
and the check runs before Turnstile — deliberately, because Turnstile tokens are
single-use and a network retry would otherwise fail on a spent token.
Generate a fresh key whenever the customer picks a different slot. That’s a different
logical booking, and reusing the key would replay the old one.
The honeypot will swallow you silently
website is a honeypot. A non-empty value returns 200 with a believable
acknowledgment and writes nothing at all. Bind it to a hidden, autocomplete="off"
input and never populate it — including from a password manager or an over-eager
autofill.
Branch on status === 201, not on the presence of acknowledgment. The honeypot reply
has no booking key precisely so the two are distinguishable.
Turnstile
You cannot use your own Turnstile key. Every booking verifies against one server-side secret, so a token minted by a different site key is rejected asturnstile_failed.
Render the widget with the site key DPC issues you, and ask us to add your domain to that
widget’s allowlist — otherwise the widget won’t render on your domain at all.
Reset the widget after any rejected submit. The token is spent either way.
Answers
form_data is validated against the questions attached to the submitted service_id.
A key this service doesn’t ask is 400 invalid_form_data — that’s what stops a stale
payload, from a customer who backed up and switched service, recording answers to
questions the appointment never asked.
What gets stored is the normalized answer set, not your raw body: numeric strings become
numbers, strings are trimmed, and an unchecked box is a real false. The
client-side normalizer in the guide mirrors those rules exactly.Headers
Caller-generated, 1–128 printable characters. Reuse it when retrying the same booking; generate a new one when the customer picks a different slot.
Required string length:
1 - 128Path Parameters
The tenant's public booking slug. Issued by DPC; there is no discovery endpoint.
Body
application/json
Pattern:
^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$Pattern:
^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$Required string length:
1 - 200Required string length:
1 - 2048Maximum string length:
50Maximum string length:
2000Response
Honeypot triggered — website was non-empty. Deliberately indistinguishable prose, but NO booking was created and there is no booking key. Branch on the 201 status, not on the presence of acknowledgment.