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

# Create a pay rate policy

> Creates a rule for what the club pays its coaches. `earnings.kind` picks what the policy is about and which rates it accepts: `individual` for one-to-one sessions (`perSession`, `sessionRevenuePercentage`) and `class` for group classes (`perClass`, `perBooking`, `classRevenuePercentage`). At least one rate must be non-zero, and an individual policy cannot be scoped by class type.

`instructorIds` must be sent. An empty list makes this the organization's default policy - it then applies to every coach who has no policy of their own, and creating it re-resolves every coach's pay - so that has to be asked for, never fallen into by omitting the field. `classTypeIds` and `entranceScopes` may be omitted; empty covers every class type or every way in.

Two policies may not claim the same coach for overlapping work. That is a 409 with code `PAY_RATE_POLICY_COLLISION`, carrying the coaches involved and the policy each one is already on, so the caller can say which assignment to undo. Supports the `Idempotency-Key` header.

Needs `instructors:write` - the same scope that creates a coach, because both are "how this organization staffs and pays its sessions". An OAuth connection made before that scope shipped has to be re-authorized.




## OpenAPI

````yaml /openapi-platform.json post /v1/platform/pay-rate-policies
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.


    ## 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 |
  contact:
    name: 1club API Support
    email: support@1club.ai
servers:
  - url: https://api.1club.ai
    description: Production API
security:
  - customerApiAuth: []
tags: []
paths:
  /v1/platform/pay-rate-policies:
    post:
      tags:
        - Pay Rate Policies
      summary: Create a pay rate policy
      description: >
        Creates a rule for what the club pays its coaches. `earnings.kind` picks
        what the policy is about and which rates it accepts: `individual` for
        one-to-one sessions (`perSession`, `sessionRevenuePercentage`) and
        `class` for group classes (`perClass`, `perBooking`,
        `classRevenuePercentage`). At least one rate must be non-zero, and an
        individual policy cannot be scoped by class type.


        `instructorIds` must be sent. An empty list makes this the
        organization's default policy - it then applies to every coach who has
        no policy of their own, and creating it re-resolves every coach's pay -
        so that has to be asked for, never fallen into by omitting the field.
        `classTypeIds` and `entranceScopes` may be omitted; empty covers every
        class type or every way in.


        Two policies may not claim the same coach for overlapping work. That is
        a 409 with code `PAY_RATE_POLICY_COLLISION`, carrying the coaches
        involved and the policy each one is already on, so the caller can say
        which assignment to undo. Supports the `Idempotency-Key` header.


        Needs `instructors:write` - the same scope that creates a coach, because
        both are "how this organization staffs and pays its sessions". An OAuth
        connection made before that scope shipped has to be re-authorized.
      parameters:
        - 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:
              $ref: '#/components/schemas/PlatformPayRatePolicyCreate'
      responses:
        '201':
          description: Pay rate policy created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlatformPayRatePolicy'
        '400':
          $ref: '#/components/responses/PlatformBadRequest'
        '401':
          $ref: '#/components/responses/PlatformUnauthorized'
        '403':
          description: API key is missing the required `instructors:write` scope
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlatformError'
        '409':
          description: >-
            An instructor on this policy is already covered by another one for
            the same work
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlatformError'
        '429':
          $ref: '#/components/responses/PlatformRateLimited'
      security:
        - customerApiAuth: []
components:
  schemas:
    PlatformPayRatePolicyCreate:
      type: object
      description: >-
        Fields accepted when creating a pay rate policy. `instructorIds` must be
        present: an empty list is the organization default and applies to every
        coach without a policy of their own, so it has to be asked for rather
        than fallen into. The other coverage lists mean "everything" when
        omitted.
      required:
        - name
        - earnings
        - instructorIds
      properties:
        name:
          type: string
          maxLength: 255
        earnings:
          $ref: '#/components/schemas/PlatformPayRatePolicyEarnings'
        requirePaidBooking:
          type: boolean
          default: false
          description: >-
            When true, attendance that was never paid for earns the coach
            nothing.
        instructorIds:
          type: array
          items:
            type: integer
          description: >-
            Coaches this policy covers. An empty list makes it the organization
            default, applying to every coach with no policy of their own.
        classTypeIds:
          type: array
          items:
            type: integer
          description: >-
            Class types it covers, from GET /v1/platform/class-types. Empty
            covers every class type. Rejected on an `individual` policy, which
            has no classes to scope.
        entranceScopes:
          type: array
          description: How the attendee got in. Empty covers every route in.
          items:
            $ref: '#/components/schemas/PlatformPayRateEntranceScope'
    PlatformPayRatePolicy:
      type: object
      description: >-
        A rule for what the club pays a coach. Not an instructor's `hourlyRate`,
        which is what a member pays for an hour of their time. Each coverage
        list means "everything" when empty: no `instructors` makes it the
        organization default, no `classTypes` covers every class type, no
        `entranceScopes` covers every way in.
      properties:
        policyId:
          type: string
          format: uuid
        name:
          type: string
        kind:
          type: string
          enum:
            - individual
            - class
        earnings:
          $ref: '#/components/schemas/PlatformPayRatePolicyEarnings'
        requirePaidBooking:
          type: boolean
        instructors:
          type: array
          description: >-
            Coaches this policy covers. Empty means every coach with no policy
            of their own.
          items:
            type: object
            properties:
              id:
                type: integer
              name:
                type: string
                nullable: true
        classTypes:
          type: array
          description: Class types it covers. Empty means every class type.
          items:
            type: object
            properties:
              id:
                type: integer
              name:
                type: string
        entranceScopes:
          type: array
          description: How the attendee got in. Empty means every route in.
          items:
            $ref: '#/components/schemas/PlatformPayRateEntranceScope'
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    PlatformError:
      type: object
      properties:
        error:
          type: string
    PlatformPayRatePolicyEarnings:
      type: object
      description: >-
        What the coach earns, and for what unit of work. `kind` decides which of
        the rate fields apply and which are rejected: an `individual` policy
        takes `perSession` and `sessionRevenuePercentage`, a `class` policy
        takes `perClass`, `perBooking` and `classRevenuePercentage`. At least
        one of the applicable rates must be greater than zero, and `kind` cannot
        be changed once the policy exists.
      required:
        - kind
      properties:
        kind:
          type: string
          enum:
            - individual
            - class
        perSession:
          type: number
          nullable: true
          minimum: 0
          description: 'Individual policies: a flat amount for each private session.'
        sessionRevenuePercentage:
          type: number
          nullable: true
          minimum: 0
          maximum: 100
          description: 'Individual policies: a share of what the session took.'
        perClass:
          type: number
          nullable: true
          minimum: 0
          description: 'Class policies: a flat amount for running the class at all.'
        perBooking:
          type: number
          nullable: true
          minimum: 0
          description: 'Class policies: a flat amount for each attendee.'
        classRevenuePercentage:
          type: number
          nullable: true
          minimum: 0
          maximum: 100
          description: 'Class policies: a share of what the class took.'
    PlatformPayRateEntranceScope:
      type: object
      description: >-
        One way an attendee got in, narrowing what a policy pays for.
        `membershipPlanId` applies only to `membership`, and `externalProgramId`
        only to `external_program`; sending the wrong pairing is a 400.
      required:
        - entranceMethod
      properties:
        entranceMethod:
          type: string
          enum:
            - direct_payment
            - membership
            - external_program
        membershipPlanId:
          type: integer
          nullable: true
          description: Narrow to one membership plan, from GET /v1/platform/plans.
        externalProgramId:
          type: integer
          nullable: true
          description: Narrow to one external program.
  responses:
    PlatformBadRequest:
      description: Invalid request (bad parameters, or a body that fails validation)
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/PlatformError'
    PlatformUnauthorized:
      description: Invalid or missing API key
      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.

````