bookingId you use to update, cancel, or look the booking up later.
Create a booking
Requires thebookings:write scope.
areaId(required) - the area id from List areasstartTime,endTime(required) - ISO date-times,endTimeafterstartTimecontactId- the customer, resolved through Contactscustomer- an inline customer matched to a contact by email, instead ofcontactIdinstructorId(optional) - the coach leading the session, from Instructorspayment.status(required) -paid(you collected the money) orunpaid(outstanding)payment.amount(optional) - the price the customer paid, before any fee you kept; defaults to the computed price for the slot (max 1,000,000)payment.reference(optional) - your own id for the payment you collected, up to 128 characters. Requirespayment.status: "paid".payment.fee(optional) - what you kept out ofamount, your commission. It does not reduce the sale; the gym reconciles it against your payout statement. Requirespayment.status: "paid"and an explicitpayment.amountit cannot exceed.channel(optional) - a label stored for attribution, up to 120 charactersnotes(optional) - up to 2,000 charactersnotifyCustomer(optional) - tell the customer their booking is confirmed: email plus the in-app, WhatsApp, and SMS notification, and the payment receipt for apaidbooking. Defaults tofalsefor an API key, since your system normally sends its own confirmation. The gym’s own staff alerts are sent either way.
contactId or customer. Sending both, or neither, is a 400. payment.reference and payment.fee cannot accompany an amount of 0, which records no payment.
Response (201 Created):
bookingUuid- a stable, unguessable identifier. Prefer it overbookingIdwhen you store a reference.maxPlayers- how many players the booking takes, the organizer included, when it differs from the area type’s default (for example2on a court booked for singles).nullmeans the area type’s own limit.payment.paidAmount- what has been collected, before any refund.payment.refundedAmount- what has been returned.paidAmount - refundedAmountis what the gym kept.payment.referenceandpayment.fee- what you reported on create, ornull.
Idempotency-Key, so a timeout cannot become two bookings. See Idempotency.
Identifying the customer
contactId is the recommended path: resolve the customer with Search contacts, create them if new, then book against the id. It is explicit about which person the booking is for, and it works for customers you only know by name.
The inline customer object remains supported for integrations that never hold 1Club ids. It is matched to a contact by email, and a contact is created when there is no match - see How contacts sync.
Adding a coach
PassinstructorId when the session is coached rather than a bare court hire. The coach must be active in your organization. Their bookability setting does not block an API booking, because your key acts for the gym, but their own calendar is respected, so a booking that would double-book them is rejected. Attaching a coach also feeds the gym’s instructor pay records automatically.
How the amount is calculated
payment.amount is optional. When you omit it, 1Club prices the slot itself: the area price per hour plus the instructor hourly rate, times the duration. A one hour court at 24 with a coach at 30 is priced at 54. Without an instructor it is just the area price. When the instructor has priceIncludesArea: true, their hourly rate is an all-in price and the area price is not added on top. Send payment.amount explicitly whenever your side is the authority on what the customer paid.
payment.amount is stated the way the gym states its prices: where the gym’s tax rate is exclusive, tax is added on top, exactly as for a price entered in the admin.
How payment is recorded
paid- 1Club records the booking’s charge as paid (a Charge in the 1Club admin, atransactionin the Platform API), settled against a payment method named after your API key (manual channel, no card charged). Each key gets its own method, so the gym can tell your revenue apart from every other partner’s. Counts as revenue and paid.unpaid- 1Club records the booking’s charge as outstanding. Counts as accrued revenue with nothing paid.
payment.status you get back can also be no_charge, which means there is nothing to collect - a free area, an explicit amount of 0, or a membership or external program the customer holds that covers the booking. There is no charge to settle in that case, so it is reported as no_charge rather than as whichever of paid or unpaid you sent.
You keep ownership of collection and refunds. 1Club never charges a card for these bookings.
Booking policy is not applied to this API
1Club has member-facing booking rules a gym can configure: a minimum notice period, a maximum number of days in advance, an opening-hours envelope, a block on slots that have already ended, and the coach’s working hours. None of them are enforced for this API, because the system calling it runs its own booking rules. That is what lets you record a schedule as it stands, including sessions in the past when you are migrating an existing calendar into 1Club.How contacts sync
Every booking belongs to a contact in the gym’s 1Club CRM. When you send an inlinecustomer object instead of a contactId, that customer is resolved to a
contact automatically, as a by-product of booking sync. Sync is one-way:
partner to 1Club. 1Club never pushes contact changes back to you.
This section describes the inline
customer path. To resolve a customer yourself
before booking - by name, email, or phone - use
Contacts and pass the resulting
contactId instead.Matching and creation
1Club resolves the contact fromcustomer.email:
- Email is the identity key. It’s normalized (trimmed and lower-cased) and matched within the club’s organization. Matching is case-insensitive -
Alex@Example.comandalex@example.comare the same contact. - Scoped per organization. Contacts belong to one organization. The same email booking at two different clubs (organizations) is two separate contacts - there is no global customer identity across clubs.
- Match wins over create. If a contact with that email already exists, the booking links to it. If not, a new contact is created from the
customerfields (firstName,lastName,phone;nameis derived from first + last, falling back to the email when no name is given). - The booking stores the resolved
contactId. From then on the booking, its revenue, and its history all roll up to that one contact in the CRM.
Guarantees
- No duplicates. A unique constraint on
(organization, lower(email))plus a create-race retry means two bookings racing with the same new email still produce exactly one contact. - Consistent identity. Because matching is by normalized email, repeat customers always land on the same contact - their bookings accumulate rather than fragmenting.
- Searchable immediately. A newly created contact is available in the CRM (searchable by name and email) as soon as the booking succeeds.
The full create flow
1
Validate the area
The
areaId must be bookable through this API (active, public, bookable type).
If not → 400.2
Resolve the contact
Normalize
customer.email (trim + lower-case), then match an existing
contact in the organization or create one from the customer fields. An
existing contact is reused as-is (name/phone not overwritten). When you send a
contactId instead, it is checked against your organization and must not be
archived.3
Validate the instructor
When
instructorId is present, the coach must be active in your organization.4
Create the booking
Link the resolved
contactId and write the booking, re-checking availability
atomically. If the slot was taken in the meantime → 400 with a populated
details.conflicts - see Conflicts.5
Record revenue & respond
Record the booking’s charge (
paid → settled, unpaid → outstanding) and return
201 with the booking.Contact resolution runs before the booking write. So if the slot turns out to
be taken (
400) or the request is otherwise rejected, a contact just created for a
first-time customer is kept - it isn’t rolled back. That’s harmless: it carries
no booking, stays matched by email, and is reused on retry. Only the booking write
re-checks availability atomically and rejects conflicts.Conflicts
A slot that cannot be sold is refused with400, not 409. The body carries a
machine-readable code and a details.conflicts array naming what is in the way:
subject says what holds the slot, in the same vocabulary the
availability response
uses for busy[].subject. A court conflict is member_booking, class, event,
or area_block; a coach conflict adds time_off and shift_break. The counterpart’s own startTime/endTime may be wider
than the slot you asked for.
id and label carry the counterpart’s id and name (the booking and its customer’s
name, or the class or event and its name). They are null unless your key can read
that counterpart through this API anyway: bookings:read for a booking, classes:read
for a class or an event, and areas:read for a block. A bookings:write-only key is
told that the hour is taken, and not by whom.
The codes you can see today are AREA_AT_CAPACITY, AREA_BLOCKED,
AREA_RUNNING_EVENT, INSTRUCTOR_AT_CAPACITY, INSTRUCTOR_TEACHING_CLASS, and
INSTRUCTOR_RUNNING_EVENT.
Retrying a conflict does not help, and there is no parameter that forces one
through. Re-read availability for the window and offer another slot.
Common errors
400- the slot cannot be sold (see Conflicts), the area is not bookable through this API, the contact was not found in your organization, the instructor was not found or is inactive, the contact is archived, you sent bothcontactIdandcustomer(or neither), orpayment.referenceorpayment.feewas sent without a collected payment.409- anIdempotency-Keywas reused with a different request body. Scheduling conflicts are400.403- the API key is missing thebookings:writescope.429- rate limit exceeded (booking writes are throttled in addition to the standard per-org limit).
Update a booking
Requires thebookings:write scope. Reschedules a booking, moves it to another area, attaches or removes a coach, reassigns the customer, or changes its notes or price. Send only the fields that change.
startTime,endTime- ISO date-times with offset. The end must be after the start, whether stored or sent.areaId- move the booking to this area. It must be an area this API can sell, as on create.instructorId- attach this coach, ornullto take the coach off. The coach must be free at the time.contactId- reassign the booking to this contact. An outstanding charge moves with it.notes- up to 2,000 charactersamount- the booking’s new price, before tax, in the gym’s currency (max 1,000,000). Omit it to keep the price you recorded.notifyCustomer- tell the customer their booking changed. Sent only when the edit names a time, area, or instructor. Defaults tofalsefor an API key. A body with nothing butnotifyCustomeris refused.
400 and details.conflicts - see Conflicts.
- A cancelled booking cannot be edited (
BOOKING_CANCELLED), and a booking on a class takes its time and price from the class (BOOKING_ON_CLASS_NOT_RESCHEDULABLE). - Payment status cannot be changed here. A new
amounton a paid booking leaves any increase outstanding. A price below what was already paid is refused with409(BOOKING_CHARGE_OVERPAID), because it needs a refund rather than an edit. - A booking an API key created cannot be made to recur (
RECURRENCE_NOT_AVAILABLE_TO_MANAGING_KEY), so each session you sell stays its own booking.
Cancel a booking
Requires thebookings:write scope. Cancels the booking by its 1Club id. 1Club moves no money: you collected the payment, so you return it.
reason(optional) - why the booking is cancelled, up to 500 characters. Shown to the gym’s staff on the cancellation. Defaults to a note naming the API.refund.amount(optional) - what you returned to the customer.0records no refund, for example when you kept the whole payment as a cancellation fee. It cannot exceed what was collected (400withcode: "AMOUNT_EXCEEDS_REFUNDABLE").refund.reference(optional) - your own id for the refund, up to 128 charactersnotifyCustomer(optional) - tell the customer the booking was cancelled. Defaults tofalsefor a booking this API created.
refund.amount says otherwise, so the gym’s revenue matches yours. Send the amount you actually returned to keep a cancellation fee, or 0 if you kept it all. refundedAmount is what this call recorded, and 0 when nothing was. An unpaid booking records nothing.
Cancelling is idempotent: cancelling an already-cancelled booking succeeds with status: "cancelled". A repeat records a refund only when it carries an explicit refund, so a retry can record a refund that failed the first time without ever recording one twice.
A booking that has already been checked in cannot be cancelled (409, BOOKING_ALREADY_CHECKED_IN). One occurrence of a recurring booking is cancelled, not the series.
Look up a booking
Requires thebookings:read scope. Returns the current state of a booking, including its payment status.
404 means no
booking with that id exists in your organization. Pass include to embed more, as on
List bookings.
Reads reach any booking in your organization, whichever channel created it.
Writes do not: update, cancel, and adding or removing players reach only the
bookings an API key created (
source: "api"), and any other booking is
reported as not found. That is deliberate, so a key cannot change or cancel a
booking a member made in the gym’s own app. A booking another API key manages is
refused with 409 and code: "BOOKING_EXTERNALLY_MANAGED", naming that
integration in details.managedBy.List bookings
Requires thebookings:read scope. Returns bookings whose start time falls in a window, so you can verify what you created and reconcile against what else is on the calendar.
from,to(optional) - ISO date-times, or a bareYYYY-MM-DDread as midnight UTC, bounding the booking start time.fromis inclusive andtoexclusive. The window may span at most 93 days. Omit both for the next 7 days; pass one and the other is derived from it.clubId,areaId,instructorId,contactId(optional) - narrow to one gym, court, coach, or customerclassId,eventId(optional) - only the attendees of one class occurrence, or the bookings for one eventstatus(optional) - for exampleconfirmed,pending,cancelledinclude(optional) - comma-separated expansions:customer,money,cancellation. See Embedding the customer.limit(optional) - 1 to 100, defaults to 50offset(optional) - defaults to 0
source (api for yours, plus admin, portal, app, kiosk, network, website, and integration). That is what makes reconciliation possible: a caller that cannot see member and staff bookings cannot tell that a slot it thinks is free was taken in the gym’s own tools.
Embedding the customer
A booking carries the customer as a barecontactId. Pass include=customer on
either read to get the whole contact inline, so importing a window of bookings
costs one request instead of a contact look-up
per row - which matters against the 100 requests per minute budget.
data then carries a customer alongside its contactId:
GET /contacts/{id}
returns, so nothing is lost by skipping that call. customer is null when the
booking has no contact.
Requires the
contacts:read scope in addition to bookings:read. A key
without it gets a 403 rather than a page with the field quietly missing -
which would read as “these bookings have no customer”. phone can be null:
1Club requires an email or a phone on a contact, not both.Money and cancellation
Two more expansions sit alongsidecustomer. Combine them with commas, for example include=customer,money,cancellation.
money- what the booking billed, what was paid, and what is still outstanding, with the charges, cancellation credits, payments, and refunds behind those figures. Requirestransactions:read.cancellation- the cancellation terms stamped on the booking when it was accepted, with the deadlines they give and until when it cancels for free, and, once cancelled, who cancelled it, when, why, with how much notice, and at what refund. Needs no scope beyondbookings:read.
403 rather than left out.
If your key deliberately holds no contacts scope, attendee names are still
available for class bookings: GET /v1/platform/classes/{id}/bookings returns
contactName under bookings:read alone, and gates only the email on
contacts:read. A name identifies who is on a roster; the whole contact record is
CRM data.
Players on a booking
A doubles court is booked by one person but played by several. The customer the booking is for stays on the booking itself ascontactId; the others are players on it.
List players
Requires thebookings:read scope. Works on any booking in your organization. Adding and removing players reaches only the bookings an API key created, as with cancel.
Add a player
Requires thebookings:write scope. The contact must already exist - create it through Contacts first.
201 with { "bookingId": 48213, "contactId": 301 }.
Remove a player
Requires thebookings:write scope. Returns 204 and does not affect the person the booking is for.
Limits
The total, counting the person the booking is for, is capped by the area type’s maximum players. Adding beyond it returns400. On an area that allows only one player, no additional players can be added at all.
Adding someone who is already the booking’s customer, or already a player, returns 409. Archived contacts cannot be added.