Skip to content

The Bookings API

The dashboard, app, booking page and MCP server all use this API, so the same rules apply everywhere. This page lists the calls in order; the Bookings reference has every field.

Your appTuppenceGET /v1/availabilityslots[].starts_atPOST /v1/bookings/holdsstatus: held, held_untilPOST /v1/bookings/{id}/confirmstatus: confirmedbooking.confirmed
ObjectPrefix
Locationloc_Where: timezone, address, opening hours. Every account has one.
Servicesvc_What: duration, price, options (svar_) and add-ons (sadd_).
Staff memberstf_Who: their services and hours (/v1/staff/{id}/schedules).
Booking policybpol_The client’s terms. One per account, versioned on each change.
Bookingbk_A visit: items (one per service), client (cus_) and money.

Test and live keys see only their own mode’s objects.

A secret key (sk_…) can do everything; a restricted key (rk_…) only what its scopes allow. resource:write includes resource:read.

ScopeFor
services:*, staff:*, schedules:*, locations:*, resources:*What you offer, who, where and when
availability:readAvailability
bookings:read, bookings:writeBookings, holds and the calendar
waitlist:*The waitlist
account:read, account:writeThe booking policy
booking_page:*The booking page
payments:writeBooking payments and fees
test_helpers:writeThe test-mode helpers

GET /v1/availability returns every bookable start between two days:

Parameter
service (+ variant, addons) or itemsOne service, or several back to back
locationWhere
staff_memberA named person; omit for anyone
from, toYYYY-MM-DD, at most 62 days

GET /v1/availability/next returns the first. Each starts_at is ISO-8601 with the location’s offset, ready to book.

Availability applies the policy’s notice, advance window and slot interval. Bookings made with your key count as the business’s own: they’re checked against who is free and what is held, not notice or advance window.

You’re buildingCall
Your own booking pagePOST /v1/bookings/holds, then POST /v1/bookings/{id}/confirmA hold takes the slot for hold_minutes without a client (status: held, held_until); confirm adds one. …/holds/{id}/extend and …/holds/{id}/release extend or free it; unconfirmed holds lapse.
A desk, phone line or agentPOST /v1/bookingsOne call. Needs an Idempotency-Key; retries return the first booking.

Pass an existing customer or client details (matched by email or phone, created if new). The result is confirmed or pending_approval. A taken slot returns 409 slot_unavailable with the nearest free starts in error.alternatives.

On an account with a staffed service (Set up bookings):

Terminal window
api() { # GET a path, or POST it with a body, using your key and a fresh Idempotency-Key
if [ $# -eq 1 ]; then
curl -sS "https://api.tuppence.ai$1" -H "Authorization: Bearer $TUPPENCE_SECRET_KEY"
else
curl -sS "https://api.tuppence.ai$1" -H "Authorization: Bearer $TUPPENCE_SECRET_KEY" \
-H "Idempotency-Key: $(uuidgen)" -H "Content-Type: application/json" -d "$2"
fi
}
location=$(api /v1/locations | jq -r '.data[0].id')
service=$(api "/v1/services?status=active&limit=1" | jq -r '.data[0].id')
# The first time a client could come.
starts_at=$(api "/v1/availability/next?service=$service&location=$location" | jq -r .starts_at)
# Hold it while they give their details: nobody else can take it meanwhile.
hold=$(api /v1/bookings/holds \
"{\"location\": \"$location\", \"items\": [{\"service\": \"$service\"}], \"starts_at\": \"$starts_at\"}")
id=$(echo "$hold" | jq -r .id)
echo "$hold" | jq -r '"\(.status) until \(.held_until)"' # held until …
# Their details make it theirs.
api "/v1/bookings/$id/confirm" '{"client": {"name": "Sam Patel", "email": "sam@example.test"}}' \
| jq -r .status # confirmed
# What would cancelling cost them now? Nothing changes yet.
api "/v1/bookings/$id/cancellation?by=client" | jq -c '{refund_amount, deposit_kept, fee_due}'
# Cancel it, as the client: the policy decides what is kept.
api "/v1/bookings/$id/cancel" '{"by": "client"}' | jq -r .status # cancelled
heldpending_approvalconfirmedexpireddeclinedarrivedcancelledin_progresscompletedno_showconfirmapprovedeclinecheck_instartcompleteundo
CallDoes
POST /v1/bookings/{id}/check_in, …/start, …/completeArrived, in the chair, done.
POST /v1/bookings/{id}/no_showAfter the start. The fee follows. Undo with …/complete within a day, unless the fee is charged.
POST /v1/bookings/{id}/rescheduleNew starts_at (and staff_member, or any); prices and accepted policy stay. by: "client" inside the window returns 409 policy_window_closed unless consented. notify: false skips notifications.
GET /v1/bookings/{id}/cancellation, POST /v1/bookings/{id}/cancelPreview, then cancel. by: "client" applies the policy; by: "business" (default) refunds everything, no fee.
POST /v1/bookings/{id}/approve, …/declineAnswer a request to book.
POST /v1/bookings/{id}Notes and metadata. Use reschedule for time or person.
GET /v1/bookingsFilters: day, status, person, service, client, source, channel, payment, fee.
GET /v1/bookings/{id}/timelineA booking’s history, oldest first.

Invalid transitions return 409 invalid_status, naming the current status and what’s required.

Booking money uses ordinary payments: POST /v1/bookings/{id}/payments with kind deposit, balance, full, tip_only, late_cancel_fee or no_show_fee. amount_due and payment_status update automatically; never set them. Pay links, desk payments, tips and fees: Deposits and no-shows and the reference.

Subscribe a webhook endpoint. Events identify the client as {id, first_name}, never email or phone.

GroupEvents
Lifecyclebooking.created, booking.confirmed, booking.pending_approval, booking.declined, booking.expired, booking.rescheduled, booking.updated, booking.arrived, booking.completed, booking.cancelled, booking.no_show
Moneybooking.deposit_paid, booking.paid, booking.fee_charged, booking.fee_failed, booking.fee_waived
HelperDoes
POST /v1/test_helpers/bookings/seed_weekFills a week with example bookings from your services, staff and hours
POST /v1/test_helpers/bookings/{id}/force_no_showMarks a no-show before the start
POST /v1/test_helpers/bookings/expire_holdsReleases all open holds
Moving the test clockHolds lapse, requests expire, reminders send, automatic fees charge
ResourceNode (@tuppence/node)Python (tuppence)
Locations, services, stafflocations, services, stafflocations, services, staff
Availabilityavailability.query, .nextavailability.query, .next
Bookingsbookings.create, .hold, .confirm, .reschedule, .cancel, .checkIn, .noShow…bookings.create, .hold, .confirm, .reschedule, .cancel, .check_in, .no_show…
Policy, pagebookingPolicy, bookingPagebooking_policy, booking_page
Anything elsetuppence.request(method, path, { body, query })tuppence.request(method, path, body=, params=)

Both send Idempotency-Key automatically.

Docs assistant

Open as a page
Loading the assistant…