> ## Documentation Index
> Fetch the complete documentation index at: https://docs.1club.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Bookings

> Create, cancel, and look up bookings through the platform API.

Create a booking when a customer books a court in your app, and cancel it when they cancel on your side. 1Club returns a `bookingId` you use to cancel or look the booking up later.

## Create a booking

Requires the `bookings:write` scope.

```bash theme={null}
curl -X POST "https://api.1club.ai/v1/platform/bookings" \
  -H "Authorization: Bearer 1club_sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "areaId": 51,
    "startTime": "2026-07-20T17:00:00Z",
    "endTime": "2026-07-20T18:00:00Z",
    "customer": {
      "email": "alex@example.com",
      "firstName": "Alex",
      "lastName": "Rivera",
      "phone": "+34600111222"
    },
    "payment": { "status": "paid", "amount": 24 },
    "channel": "Padel Community",
    "notes": "Booked via Padel Community"
  }'
```

Body fields:

* `areaId` (required) - the area id from [List areas](/api-reference/channel-integration/areas-and-availability#list-areas)
* `startTime`, `endTime` (required) - ISO date-times, `endTime` after `startTime`
* `customer.email` (required) - matches an existing contact or creates a new one
* `customer.firstName`, `customer.lastName`, `customer.phone` (optional) - used when creating a new contact
* `payment.status` (required) - `paid` (you collected the money) or `unpaid` (outstanding)
* `payment.amount` (optional) - amount collected; defaults to the area's price for the slot (max 1,000,000)
* `channel` (optional) - a label stored for attribution
* `notes` (optional)

Response (`201 Created`):

```json theme={null}
{
  "bookingId": 48213,
  "status": "confirmed",
  "areaId": 51,
  "startTime": "2026-07-20T17:00:00.000Z",
  "endTime": "2026-07-20T18:00:00.000Z",
  "payment": { "status": "paid", "amount": 24 }
}
```

### How payment is recorded

* `paid` → 1Club records a paid booking transaction, settled against a dedicated **External / API** payment method (manual channel, no card charged). Counts as revenue and paid.
* `unpaid` → 1Club records the transaction as outstanding. Counts as accrued revenue with nothing paid.

You keep ownership of collection and refunds. 1Club never charges a card for these bookings.

## How contacts sync

Every booking belongs to a **contact** in the club's 1Club CRM. You never create or
manage contacts directly — there is no contacts endpoint. Instead, the `customer`
object you send with each booking is resolved to a contact automatically, as a
by-product of booking sync. This is deliberately one-way: **partner → 1Club**. 1Club
never pushes contact changes back to you.

### Matching and creation

1Club resolves the contact from `customer.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.com` and `alex@example.com` are 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 `customer` fields (`firstName`, `lastName`, `phone`; `name` is 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.

<Warning>
  For an **existing** contact, 1Club matches by email and does **not** overwrite the
  stored name or phone with what you send. Treat contact profile fields as
  create-time only — sending a new phone on a later booking for the same email will
  not change the contact. (To correct contact details, edit the contact in 1Club.)
</Warning>

### 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

<Steps>
  <Step title="Validate the area">
    The `areaId` must be bookable through this API (active, public, bookable type).
    If not → `400`.
  </Step>

  <Step title="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).
  </Step>

  <Step title="Create the booking">
    Link the resolved `contactId` and write the booking, re-checking availability
    **atomically**. If the slot was taken in the meantime → `409`.
  </Step>

  <Step title="Record revenue & respond">
    Record the transaction (`paid` → settled, `unpaid` → outstanding) and return
    `201` with `{ bookingId, status, payment }`.
  </Step>
</Steps>

<Note>
  Contact resolution runs **before** the booking write. So if the slot turns out to
  be taken (`409`) 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.
</Note>

### Common errors

* `409` - the area is not available for the selected time.
* `400` - the time is outside operating hours, the booking breaks a club booking policy, or the area is not bookable through this API.
* `403` - the API key is missing the `bookings:write` scope.
* `429` - rate limit exceeded (booking writes are throttled in addition to the standard per-org limit).

## Cancel a booking

Requires the `bookings:write` scope. Cancels the booking by its 1Club id. No refund is issued by 1Club.

```bash theme={null}
curl -X POST "https://api.1club.ai/v1/platform/bookings/48213/cancel" \
  -H "Authorization: Bearer 1club_sk_live_..."
```

Response:

```json theme={null}
{ "bookingId": 48213, "status": "cancelled" }
```

Cancelling is idempotent: cancelling an already-cancelled booking succeeds and returns the same response.

## Look up a booking

Requires the `bookings:read` scope. Returns the current state of a booking, including its payment status.

```bash theme={null}
curl "https://api.1club.ai/v1/platform/bookings/48213" \
  -H "Authorization: Bearer 1club_sk_live_..."
```

```json theme={null}
{
  "bookingId": 48213,
  "status": "confirmed",
  "areaId": 51,
  "startTime": "2026-07-20T17:00:00.000Z",
  "endTime": "2026-07-20T18:00:00.000Z",
  "payment": { "status": "paid", "amount": 24 }
}
```

A `404` means no booking with that id exists in your organization.
