Bookings
A booking is one appointment: a customer, a treatment, a start and end time. Every booking has two identifiers:
id: a UUID, stable for the life of the booking.reference: the short code the customer sees in their confirmation mail. Webhooks identify bookings by this code.
Statuses
| Status | Meaning |
|---|---|
requested |
The customer asked for the slot, but something still has to be arranged (for example a second therapist). The business confirms or declines within its response time. |
awaiting_payment |
The customer chose to pay online on the booking page and has not paid yet. The slot is held for 30 minutes. Unpaid, the booking becomes cancelled. |
confirmed |
The appointment stands. |
in_session |
The treatment has started. |
completed |
The treatment is done. A completed booking always has an invoice. |
cancelled |
Cancelled by the customer or the business. |
no_show |
The customer did not turn up. |
draft |
Internal state while a booking is being made. You will rarely see it. |
List bookings
GET /api/v1/bookings
Scope: bookings:read. Newest start time first.
| Query parameter | Description |
|---|---|
status |
Only bookings with this status, for example confirmed. |
from |
Only bookings that start at or after this time (ISO 8601). |
to |
Only bookings that start at or before this time (ISO 8601). |
limit, offset |
See pagination. |
curl "https://app.aphrabooking.com/api/v1/bookings?status=confirmed&from=2026-11-01T00:00:00Z&to=2026-11-30T23:59:59Z" \
-H "Authorization: Bearer $APHRA_KEY"
{
"bookings": [
{
"id": "6f1c2a5e-0b7d-4c8e-9a51-2f0e7d3b9c11",
"reference": "K7Q2MX",
"status": "confirmed",
"startsAt": "2026-11-20T14:00:00+00:00",
"endsAt": "2026-11-20T15:00:00+00:00",
"total": 65,
"currency": "USD",
"notes": "Sensitive skin",
"createdAt": "2026-11-14T09:12:44.120+00:00"
}
],
"limit": 50,
"offset": 0
}
total is the price at the time of booking, including chosen extras. The final amount is on the invoice.
The list does not include the customer or the treatment yet.
Create a booking
POST /api/v1/bookings
Scope: bookings:write.
Use this when a booking is made somewhere else, for example in your own app or a form on your site that you built yourself. Aphra checks availability exactly as the booking page does. If the slot is free, the booking is made, the customer gets the usual confirmation by mail (and WhatsApp, if the business uses it), and your booking.created webhooks fire.
There is no payment step: a booking made through the API is confirmed (or requested) straight away, and the customer pays at the business.
The three steps, all with ids you get from the API itself:
GET /api/public/servicesgives each treatment’sid, itsvariantsand itsaddons.GET /api/availabilitywith a service and variant id gives the free start times for a day.POST /api/v1/bookingswith one of those times books it.
Body
| Field | Type | Required | Description |
|---|---|---|---|
serviceId |
UUID | yes | The treatment. Get it from /api/public/services. |
variantId |
UUID | yes | The variant of the treatment, for example the 60 or 90 minute version. One of the service’s variants. |
slotStart |
string | yes | Start time in UTC, ending in Z. Use a value from /api/availability. |
firstName |
string | yes | |
lastName |
string | yes | |
email |
string | yes | Used to find an existing customer or create a new one. |
phone |
string | no | International format, for example +5997001234. |
notes |
string | no | Shown to the business with the booking. |
serviceAddress |
string | no | Required when the variant has address_required: true, for treatments given at the customer’s place. |
addonIds |
UUID[] | no | Extras for the treatment, from the service’s addons. |
locale |
string | no | Language for the customer’s messages: nl, en, de, es, fr or pt. Defaults to the business’s language. |
whatsappOptIn |
boolean | no | Send true only if the customer ticked a box agreeing to WhatsApp messages; Meta requires you to be able to show that. Leave it out if you did not ask. |
Promo codes, gift cards and prepaid passes cannot be used through the API. They only work on the booking page.
Example
curl https://app.aphrabooking.com/api/v1/bookings \
-H "Authorization: Bearer $APHRA_KEY" \
-H "Content-Type: application/json" \
-d '{
"serviceId": "0d4f3b1e-6a2c-4f1a-9e7d-2b8c5a1f3e90",
"variantId": "9a7e2c41-3b5d-4e6f-8a1b-c2d3e4f5a6b7",
"slotStart": "2026-11-20T14:00:00Z",
"firstName": "Maria",
"lastName": "Janga",
"email": "maria@example.com",
"phone": "+5997001234",
"locale": "en"
}'
Response
201 Created:
{
"booking": {
"id": "6f1c2a5e-0b7d-4c8e-9a51-2f0e7d3b9c11",
"reference": "K7Q2MX",
"startsAt": "2026-11-20T14:00:00.000Z",
"status": "confirmed",
"pending": [],
"total": 65,
"currency": "USD"
}
}
If status is requested, the slot is held but not yet promised. pending then says what the business still has to arrange, for example [{ "type": "staff", "count": 1 }]. The customer is told the business will respond, and you receive booking.created (with status requested) now and booking.cancelled later if the business declines.
Errors
A 422 means the booking was not made. The error field holds one of these codes:
| Code | What happened |
|---|---|
unknown_service |
The treatment does not exist or is switched off. |
unknown_variant |
The variant does not belong to this treatment. |
slot_in_past |
slotStart is in the past. |
slot_unavailable |
Nobody is free at that time. Fetch fresh times and let the customer choose again. |
slot_just_taken |
Someone else booked the slot a moment ago. Same remedy. |
customer_double_booked |
This customer already has an appointment at that time. |
customer_blocked |
The business does not accept online bookings from this customer. |
address_required |
The treatment is given at the customer’s place and serviceAddress is missing. |
booking_failed, booking_line_failed |
Saving failed on our side. Safe to retry. |
A 400 means a field is missing or has the wrong format. The error field then names the problem.
Retries
There is no idempotency key yet. If your request times out, list bookings for that time before you send it again, or you may create the booking twice.