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

# Book a customer into a class

> Enrols one customer in a class occurrence - the booking the club's own staff make when they add someone to a class. The seat takes the class's time, club and price; capacity, the class's booking policy, a waitlist entry the customer already holds (converted into this booking) and the "already on this class" check all apply exactly as they do in the admin portal. Identify the customer with `contactId` (resolve it via GET /v1/platform/contacts) or with an inline `customer` matched to a contact by email and created if new - exactly one of the two.

Payment works as on POST /v1/platform/bookings: `paid` records the seat as settled against a payment method named after your API key (no gateway call), `unpaid` leaves it outstanding, and `amount` is the price. With `unpaid` and no `amount`, the seat is paid for the way the club's own staff would default it: a membership of the customer's that covers the class is used (one session is spent and nothing is owed), and otherwise the class price is recorded as owed. With an API key that needs the customer named by `contactId`: an inline `customer` never spends a membership, and the class price is recorded as owed.

A full class, a cancelled one and one that has already run are refused; nothing here overrides them. Returns the same booking as POST /v1/platform/bookings, with `classId` set.




## OpenAPI

````yaml /openapi-platform.json post /v1/platform/classes/{id}/bookings
openapi: 3.0.0
info:
  title: 1club Platform API
  version: 1.0.0
  description: >-
    The 1club Platform API lets you programmatically access and manage your
    organization's data.


    ## Official API Contract


    This documentation is the official source of truth for the 1club Platform
    API.

    If an integration relies on undocumented endpoints, fields, response shapes,
    or internal behavior outside this spec, we can't guarantee backward
    compatibility.

    Build against what's documented here to stay stable as the platform evolves.


    ## Authentication


    All requests require a customer API key passed as a Bearer token:


    ```

    Authorization: Bearer 1club_sk_live_...

    ```


    Generate API keys from the admin portal under **Settings > API Tokens**.

    The key is tied to your organization - all responses are scoped to your
    org's data.


    Agents and MCP connectors can instead send an OAuth access token (the
    `platformOAuth` scheme), requesting only the scopes the task needs.


    ## Rate Limiting


    - **100 requests per minute** per API key

    - When exceeded, the API returns `429 Too Many Requests` with a
    `Retry-After` header

    - Rate limit headers are included in every response:
      - `X-RateLimit-Limit` - max requests per window
      - `X-RateLimit-Remaining` - requests remaining
      - `X-RateLimit-Reset` - seconds until the window resets

    ## Errors


    | Status | Meaning |

    |--------|---------|

    | `400` | Invalid request parameters |

    | `401` | Missing or invalid API key |

    | `404` | Resource not found (or doesn't belong to your organization) |

    | `429` | Rate limit exceeded |

    | `500` | Internal server error |


    ## Versioning and deprecation


    The API is versioned in the path (`/v1`). Changes within a version are
    additive: new endpoints, new optional parameters and new response fields can
    appear at any time, so clients should ignore fields they do not recognise.

    A breaking change ships as a new version.

    An operation due to be removed is first marked `deprecated: true` in this
    spec and announced in the changelog at https://docs.1club.ai/changelog, with
    advance notice before it is withdrawn.
  contact:
    name: 1club API Support
    email: support@1club.ai
servers:
  - url: https://api.1club.ai
    description: Production API
security:
  - customerApiAuth: []
  - platformOAuth: []
tags: []
paths:
  /v1/platform/classes/{id}/bookings:
    post:
      tags:
        - Classes
      summary: Book a customer into a class
      description: >
        Enrols one customer in a class occurrence - the booking the club's own
        staff make when they add someone to a class. The seat takes the class's
        time, club and price; capacity, the class's booking policy, a waitlist
        entry the customer already holds (converted into this booking) and the
        "already on this class" check all apply exactly as they do in the admin
        portal. Identify the customer with `contactId` (resolve it via GET
        /v1/platform/contacts) or with an inline `customer` matched to a contact
        by email and created if new - exactly one of the two.


        Payment works as on POST /v1/platform/bookings: `paid` records the seat
        as settled against a payment method named after your API key (no gateway
        call), `unpaid` leaves it outstanding, and `amount` is the price. With
        `unpaid` and no `amount`, the seat is paid for the way the club's own
        staff would default it: a membership of the customer's that covers the
        class is used (one session is spent and nothing is owed), and otherwise
        the class price is recorded as owed. With an API key that needs the
        customer named by `contactId`: an inline `customer` never spends a
        membership, and the class price is recorded as owed.


        A full class, a cancelled one and one that has already run are refused;
        nothing here overrides them. Returns the same booking as POST
        /v1/platform/bookings, with `classId` set.
      operationId: createClassBooking
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: integer
          description: Class occurrence id (from GET /v1/platform/classes)
        - in: header
          name: Idempotency-Key
          schema:
            type: string
            maxLength: 255
          description: >-
            Retry-safe key; a repeat of the same request replays the first
            response
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - payment
              properties:
                contactId:
                  type: integer
                  description: >-
                    Existing contact to book for (from GET
                    /v1/platform/contacts). Mutually exclusive with `customer`.
                customer:
                  type: object
                  description: >-
                    Inline find-or-create by email. Mutually exclusive with
                    `contactId`.
                  required:
                    - email
                  properties:
                    email:
                      type: string
                      format: email
                    firstName:
                      type: string
                    lastName:
                      type: string
                    phone:
                      type: string
                payment:
                  type: object
                  required:
                    - status
                  properties:
                    status:
                      type: string
                      enum:
                        - paid
                        - unpaid
                    amount:
                      type: number
                      maximum: 1000000
                      description: >
                        The price of the seat, in the club's currency, before
                        any fee you kept. Defaults to the class price (or, for
                        an `unpaid` seat, nothing when a membership covers it).
                        Stated the way the club states its prices: where the
                        club's tax rate is exclusive, tax is added on top.
                    reference:
                      type: string
                      maxLength: 128
                      description: >-
                        Your own id for the payment you collected. Requires
                        `status` paid.
                    fee:
                      type: number
                      maximum: 1000000
                      description: >
                        What you kept out of `amount` - your commission.
                        Requires `status` paid and an explicit `amount` it
                        cannot exceed.
                notes:
                  type: string
                  maxLength: 2000
                notifyCustomer:
                  type: boolean
                  description: >
                    Tell the customer they are booked - email plus the in-app,
                    WhatsApp and SMS notification. Off by default for an API
                    key, on by default over an OAuth connection, as on POST
                    /v1/platform/bookings.
      responses:
        '201':
          description: Booking created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlatformBooking'
        '400':
          description: >
            Invalid request; the class is cancelled (`CLASS_CANCELLED`) or has
            already run (`BOOKING_ALREADY_ENDED`); or the contact cannot be
            booked.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlatformError'
        '401':
          $ref: '#/components/responses/PlatformUnauthorized'
        '403':
          description: API key is missing the required `bookings:write` scope
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlatformError'
        '404':
          $ref: '#/components/responses/PlatformNotFound'
        '409':
          description: >-
            The class is full, or the customer is already booked on it, teaching
            it or checked in to it
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlatformError'
        '429':
          $ref: '#/components/responses/PlatformRateLimited'
      security:
        - customerApiAuth: []
components:
  schemas:
    PlatformBooking:
      type: object
      description: >-
        A booking created through the platform API. Recorded with a booking
        transaction so it counts for revenue, marked paid (settled against a
        payment method named after the caller's API key, so the club can
        reconcile what each partner collected) or unpaid (outstanding) per the
        caller. Collection stays with the caller; what it collected and what it
        gave back on cancellation are reported in `payment`.
      properties:
        bookingId:
          type: integer
          description: 1club booking id
        bookingUuid:
          type: string
          format: uuid
          description: >-
            Stable unguessable identifier. Prefer this over `bookingId` when
            storing a reference.
        status:
          type: string
          description: Booking status (e.g. confirmed, cancelled)
        areaId:
          type: integer
          nullable: true
          description: Area id
        classId:
          type: integer
          nullable: true
          description: >-
            The class occurrence this booking attends, when it is a class
            booking. Every attendee holds their own booking row against the same
            `classId`, and `classes` rows are per-occurrence - so this is what
            tells two classes running in the same hour apart. Join it against
            GET /v1/platform/classes, or read the roster with GET
            /v1/platform/classes/{id}/bookings.
        eventId:
          type: integer
          nullable: true
          description: The event this booking attends, when it is an event booking
        maxPlayers:
          type: integer
          nullable: true
          description: >-
            How many players the booking takes, the organizer included, when it
            differs from the area type's `defaultMaxPlayers`: `2` on a court
            booked for singles (area types with `allowsSingles`). `null` means
            the area type's own limit. Adding a player past it is refused.
        instructorId:
          type: integer
          nullable: true
          description: Instructor leading the session, when one is attached
        contactId:
          type: integer
          nullable: true
          description: Contact the booking is for
        startTime:
          type: string
          format: date-time
        endTime:
          type: string
          format: date-time
        customer:
          allOf:
            - $ref: '#/components/schemas/PlatformContact'
          nullable: true
          description: >-
            The booking's customer, identical to what GET
            /v1/platform/contacts/{id} returns. Present only when the request
            passes `include=customer`, which additionally requires the
            `contacts:read` scope; `null` when the booking has no contact. Use
            it to import a window of bookings without a contact lookup per row.
        money:
          type: object
          description: >-
            What the booking costs and how it was paid. Present only with
            `include=money`, which additionally requires the `transactions:read`
            scope. `billed` is always `paid + outstanding`, on the same basis as
            the admin's Booking audit: tax-inclusive, net of refunds, so a
            cancelled booking bills only the Cancellation Charge it kept.
          properties:
            billed:
              type: number
            billedExTax:
              type: number
            paid:
              type: number
              description: 'Money the club kept: collected, net of refunds.'
            outstanding:
              type: number
              description: Still owed on the booking. A closed charge owes nothing.
            paymentState:
              type: string
              description: paid, partial, unpaid, refunded, covered or free.
            paymentStatus:
              type: string
              nullable: true
              description: The charges' own payment statuses, `/`-joined.
            charges:
              type: array
              items:
                type: object
                properties:
                  transactionId:
                    type: integer
                  transactionType:
                    type: string
                    nullable: true
                  payerContactId:
                    type: integer
                    nullable: true
                    description: >-
                      Who the charge is on: the organizer, or a player paying
                      their own share.
                  date:
                    type: string
                    format: date-time
                  paymentStatus:
                    type: string
                  amount:
                    type: number
                    description: >-
                      What the charge bills, after any cancellation credit
                      against it.
                  paidAmount:
                    type: number
                  refundedAmount:
                    type: number
                    description: Returned from its payments by a refund.
                  outstandingAmount:
                    type: number
                  invoiceIds:
                    type: array
                    items:
                      type: integer
                  orderId:
                    type: integer
                    nullable: true
            credits:
              type: array
              description: 'Cancellation credits: what a cancellation took off a charge.'
              items:
                type: object
                properties:
                  transactionId:
                    type: integer
                  chargeTransactionId:
                    type: integer
                    nullable: true
                  amount:
                    type: number
                  paymentStatus:
                    type: string
                  date:
                    type: string
                    format: date-time
            payments:
              type: array
              items:
                type: object
                properties:
                  paymentId:
                    type: integer
                  amount:
                    type: number
                  status:
                    type: string
                  method:
                    type: string
                  source:
                    type: string
                    nullable: true
                  reference:
                    type: string
                    nullable: true
                  paidAt:
                    type: string
                    format: date-time
                  transactionIds:
                    type: array
                    items:
                      type: integer
            refunds:
              type: array
              items:
                type: object
                properties:
                  refundId:
                    type: integer
                  transactionId:
                    type: integer
                    nullable: true
                  paymentId:
                    type: integer
                  amount:
                    type: number
                  destination:
                    type: string
                    enum:
                      - stripe
                      - wallet
                      - cash
                      - manual
                  reason:
                    type: string
                    nullable: true
                  refundedAt:
                    type: string
                    format: date-time
        cancellation:
          type: object
          description: >-
            The cancellation terms stamped on the booking and how it was
            cancelled. Present only with `include=cancellation`.
          properties:
            terms:
              type: object
              nullable: true
              description: >-
                The terms the customer accepted with the booking - not the
                club's policy today, which may since have changed. `null` for an
                event ticket, whose event keeps its own terms, and for a booking
                accepted before terms were stamped.
              properties:
                acceptedAt:
                  type: string
                  format: date-time
                rules:
                  type: array
                  items:
                    type: object
                    properties:
                      hoursInAdvance:
                        type: number
                      refundPercentage:
                        type: number
                      description:
                        type: string
                deadlines:
                  type: array
                  description: >-
                    Every band that refunds something, as the moment to cancel
                    by, widest notice first.
                  items:
                    type: object
                    properties:
                      until:
                        type: string
                        format: date-time
                      refundPercentage:
                        type: number
                freeCancellationUntil:
                  type: string
                  format: date-time
                  nullable: true
                  description: >-
                    The last moment a cancellation refunds in full; `null` when
                    no band does.
                fullyCovered:
                  type: boolean
                  description: A membership or pass covered the whole booking.
                collectsFromWallet:
                  type: boolean
                  description: The Cancellation Charge is taken from the Wallet.
                waiver:
                  type: object
                  nullable: true
                  properties:
                    at:
                      type: string
                      format: date-time
                    byUserId:
                      type: integer
                      nullable: true
                    reason:
                      type: string
            cancelled:
              type: object
              nullable: true
              description: '`null` while the booking is not cancelled.'
              properties:
                at:
                  type: string
                  format: date-time
                by:
                  type: object
                  nullable: true
                  properties:
                    userId:
                      type: integer
                    name:
                      type: string
                reason:
                  type: string
                  nullable: true
                noticeHours:
                  type: number
                  description: >-
                    Hours between the cancellation and the start; negative when
                    it came after the start.
                refundPercentage:
                  type: number
                  nullable: true
                refundAmount:
                  type: number
                  nullable: true
                policyWaived:
                  type: boolean
                notes:
                  type: string
                  nullable: true
        payment:
          type: object
          properties:
            status:
              type: string
              enum:
                - paid
                - unpaid
                - no_charge
              description: >-
                `no_charge` means there is nothing to collect: either nothing
                was ever owed (a free area, an amount of 0) or an entitlement
                covers it - a membership or external program the attendee
                already holds, which is why a covered booking can carry a price
                and still owe nothing. `paid` counts every financially closed
                transaction status (`paid`, `settled`, `refunded`), and every
                charge type, not only the one this API records.
            amount:
              type: number
              nullable: true
              description: >-
                The booking's recorded price in the organization's currency;
                `status` is what says whether anything was collectable. Where
                the price is 0 but a real charge exists - an external program's
                per-visit fee lives entirely in its transaction - the ledger
                total is reported instead.
            paidAmount:
              type: number
              description: >-
                What has been collected on the booking, gross, before any
                refund. `0` when nothing has.
            refundedAmount:
              type: number
              description: >-
                What has been returned to the customer, across every refund
                recorded. `paidAmount - refundedAmount` is what the club kept.
            reference:
              type: string
              nullable: true
              description: >-
                The collecting partner's own id for the payment, when one
                reported it through this API (`payment.reference`); null for
                money 1club collected itself.
            fee:
              type: number
              nullable: true
              description: >-
                What the collecting partner kept out of the amount, when it
                reported one (`payment.fee`).
    PlatformError:
      type: object
      properties:
        error:
          type: string
        code:
          type: string
          description: >-
            A stable, machine-readable reason, when the error has one (e.g.
            `CONTACT_EXISTS`).
        details:
          type: object
          additionalProperties: true
          description: >-
            Structured values behind the error. A 409 naming an existing record
            carries its id here, e.g. `contactId`.
    PlatformContact:
      type: object
      description: >-
        A contact (customer) in the organization. Reference the `id` as
        `contactId` when creating a booking.
      properties:
        id:
          type: integer
        uuid:
          type: string
          format: uuid
          description: >-
            Stable unguessable identifier. Prefer this over `id` when storing a
            reference.
        name:
          type: string
        firstName:
          type: string
        lastName:
          type: string
          nullable: true
        email:
          type: string
          nullable: true
        phone:
          type: string
          nullable: true
        type:
          type: string
          nullable: true
          description: >-
            Derived contact type (e.g. contact, member, lead). `archived` marks
            a retired contact — hidden from the list and from search, but still
            readable by id.
        pipelineStageId:
          type: integer
          nullable: true
          description: >-
            The sales pipeline stage the contact is in, or null when they are
            not in the pipeline.
        sourceId:
          type: integer
          nullable: true
          description: Where the contact came from.
        assignedToId:
          type: integer
          nullable: true
          description: The staff member (organization user id) following the contact up.
        createdAt:
          type: string
          format: date-time
  responses:
    PlatformUnauthorized:
      description: Invalid or missing API key
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/PlatformError'
    PlatformNotFound:
      description: The requested resource does not exist in this organization
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/PlatformError'
    PlatformRateLimited:
      description: Rate limit exceeded
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/PlatformError'
  securitySchemes:
    customerApiAuth:
      type: http
      scheme: bearer
      description: >-
        Organization-scoped bearer credential: a customer API key
        (1club_sk_live_...) or an MCP OAuth access token.
    platformOAuth:
      type: oauth2
      description: >-
        OAuth 2.1 authorization code flow with PKCE (S256), as used by MCP
        connectors. Register a client dynamically (RFC 7591) at
        https://api.1club.ai/v1/mcp/oauth/register. Each scope grants one
        capability; request only what the integration needs.
      flows:
        authorizationCode:
          authorizationUrl: https://auth.1club.ai/oauth/authorize
          tokenUrl: https://api.1club.ai/v1/mcp/oauth/token
          scopes:
            '*': >-
              Full access - read and manage everything in this organization,
              including capabilities added later
            classes:read: View classes, class types, schedules and progression tracks
            classes:write: >-
              Create, reschedule, and cancel classes, and maintain the class
              types they are run as
            checkins:read: View check-ins
            checkins:write: Check people in, to a booking or on their membership
            content:read: View published content
            content:write: >-
              Create, edit and delete content - posts, FAQs, documents and
              reusable blocks
            website:read: >-
              View your website: pages, sections, layout, styles and publish
              history
            website:write: >-
              Edit your website - pages, sections, navigation and styles. Edits
              stay unpublished until published.
            website:publish: >-
              Publish your website, making unpublished edits visible to the
              public, and roll back to an earlier version
            media:read: View your media library
            media:write: >-
              Add images and videos to your media library and edit their names
              and descriptions
            media:delete: Permanently delete files from your media library
            clubs:read: View club information
            clubs:write: >-
              Edit your clubs' details - name, contact details, address, opening
              hours, amenities and sports. Live on your website, member portal,
              app and 1club.me at once; a club listed on 1club.me sends its
              listing's changes for review.
            transactions:read: View transactions, revenue reports and your chart of accounts
            transactions:write: >-
              Record revenue collected outside 1club, such as a marketplace
              payout for your bookings, and refunds of it. Nothing is charged to
              anyone.
            invoices:read: >-
              View your invoices - who they were issued to, their amounts and
              status - and how much was invoiced over a period
            areas:read: View areas, area types, blocks and availability
            areas:write: >-
              Create and update areas and area types, and block or unblock an
              area
            bookings:read: View bookings
            bookings:write: >-
              Create, change, reschedule and cancel bookings, and add or remove
              participants
            contacts:read: >-
              View contacts, their tags and custom field values, and your sales
              pipeline stages and sources
            contacts:write: >-
              Create and update contacts and tag them, and maintain your tags,
              smart tag rules and contact custom fields
            contacts:delete: >-
              Archive contacts, which cancels their active memberships and all
              their future bookings. Separate from creating and updating because
              those cancellations are not undone by restoring the contact.
            instructors:read: View instructors and their availability
            instructors:write: >-
              Create and update instructors (coaches) and the instructor types
              they are grouped by, including what members pay for a one-to-one
              session and how you pay the coach. Does not grant admin-portal
              access: an instructor created this way has no login and is never
              invited to your staff portal.
            plans:read: View plans and pricing
            plans:write: Create, edit and delete plans and plan categories
            memberships:read: View memberships
            memberships:write: >-
              Sell memberships, set their price and whether they are paid, and
              pause, resume or cancel them
            products:read: >-
              View the product catalogue and its categories, including prices
              and how many are left to sell
            products:write: Create and edit products and product categories
            forms:read: >-
              View your forms and their questions, and how many people have
              completed each. Not the answers themselves.
            forms:write: >-
              Create and edit forms and their questions, including sign-up forms
              that sell your plans and products. Forms are public as soon as
              they are created or changed. Does not delete forms.
            organization:write: >-
              Change organization-wide settings, which take effect immediately.
              That includes which languages you offer, across your website, the
              member portal, the mobile app and email, and what your clubs call
              their booking cards (private training, group activities, areas)
              wherever members book.
            revenue-accounts:write: >-
              Add accounts to your chart of accounts and edit them, including
              which one sales post to by default. Reading the chart needs no
              scope of its own - any of plans, products or transactions admits
              it.
            pricing-rules:read: >-
              View pricing rules - the surcharges, discounts and set prices
              applied when a booking is priced - and quote what a booking would
              cost
            pricing-rules:write: >-
              Create, edit, turn off and delete pricing rules. Changes apply to
              every booking priced afterwards.
            booking-policies:read: >-
              View booking policies: cancellation and refund rules, payment
              timing and booking windows
            booking-policies:write: >-
              Create, edit, duplicate and delete booking policies. Changes apply
              to every booking they cover from then on.
            document-rules:read: >-
              View document rules - which members are asked to sign or accept
              which documents, such as waivers
            document-rules:write: >-
              Create, edit and delete document rules, which decide who must sign
              or accept a document and whether it is required for entry
            templates:read: View your email and SMS templates
            templates:write: Create, edit and delete email and SMS templates
            events:read: View events, their tickets and how many are sold
            events:write: >-
              Create, edit, duplicate and delete events, and publish, unpublish
              or cancel them. Cancelling an event also cancels its attendees'
              bookings.
            messages:read: >-
              View messages sent to and received from your members, and their
              delivery status
            messages:write: >-
              Prepare draft messages to members. Drafts are never sent from
              here: someone at your club reviews and sends them from 1club.
            analytics:read: >-
              View statistics over a period - revenue, bookings, check-ins, how
              full your classes, courts and coaches were, memberships added and
              cancelled, and new members - as totals, never the people or sales
              behind them
            payment-methods:write: >-
              Add, rename and remove the payment methods staff record by hand,
              such as cash or a bank transfer, and choose which ones members may
              pick when paying for a booking. Reading them needs no scope of its
              own - transactions:read admits it.
            staff:read: >-
              View your staff accounts, their roles, and what each role is
              allowed to do
            staff:write: >-
              Change a staff member's role, and what the manager, front desk and
              instructor roles are allowed to do. Only a signed-in person can
              use it, with the same limits as in 1club: an API key is always
              refused.
            notifications:read: >-
              View which notifications each role gets by default, and on which
              channels
            notifications:write: >-
              Change which notifications each role gets and on which channels,
              and apply those defaults to everyone in a role, replacing their
              own choices

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.