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

# Plans

> Find, create, update, retire, and delete membership plans.

A **plan** is what a membership is sold on. It carries its own price, joining fee,
billing frequency and session allowance, so creating a membership only has to name
the plan and the customer - the plan supplies the rest.

Read operations require `plans:read`. Create, update, retire, and delete operations require `plans:write`.

## Search plans

Requires the `plans:read` scope.

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

Query parameters:

* `search` (optional) - matches on plan name or description
* `clubId` (optional) - only plans limited to that gym
* `sport` (optional) - only plans listing that sport, for example `padel`
* `includeInactive` (optional) - `true` to include retired plans, which are hidden by default
* `limit` (optional) - 1 to 100, defaults to 25
* `offset` (optional) - defaults to 0

Response:

```json theme={null}
{
  "data": [
    {
      "planId": 9,
      "name": "Unlimited",
      "description": "All courts, all week",
      "clubId": 5,
      "price": 49,
      "signupFee": 20,
      "billingFrequency": "monthly",
      "isActive": true,
      "maxUses": null,
      "sessionUnit": null,
      "validityPeriod": null,
      "autoRenews": true,
      "sports": ["padel"]
    }
  ],
  "total": 1,
  "limit": 25,
  "offset": 0
}
```

By default only plans the gym still sells are returned, so everything here can be
sold. Add `includeInactive=true` when you need retired plans as well - to find one
you retired and want back, for example. Name
matching works like
[contact search](/api-reference/channel-integration/contacts#matching-is-case-insensitive-and-cross-script):
case-insensitive and cross-script.

## Open a plan

Requires the `plans:read` scope.

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

Returns the same object as one row of the search response.

<Note>
  Get-by-id returns retired plans too. Resolving an id you already hold is not the
  same as offering a plan for sale, so a plan you retired stays readable here - and
  reachable for a `PATCH` that un-retires it. Only [plan search](#search-plans)
  hides retired plans, and only until you pass `includeInactive=true`.
</Note>

## Create a plan

Requires `plans:write`. Send an `Idempotency-Key` so a retry cannot create a duplicate.

```bash theme={null}
curl -X POST "https://api.1club.ai/v1/platform/plans" \
  -H "Authorization: Bearer 1club_sk_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: plan-unlimited-2026-08" \
  -d '{
    "name": "Unlimited",
    "description": "All classes and courts",
    "price": 49,
    "signupFee": 20,
    "billingFrequency": "monthly",
    "autoRenews": true,
    "maxUses": null,
    "validFor": {
      "scopes": ["classes", "areas"]
    },
    "visibility": "Public"
  }'
```

`validFor.scopes` controls which booking resources the plan can cover. Omit a type list to cover every type in that scope. For example, `scopes: ["classes"]` covers every class type. Add `classTypes: [3, 7]` to limit it to those class type IDs.

An empty `validFor` creates a generic plan for direct gym entry. It does not make class, area, or instructor bookings free.

For a custom billing frequency, send both `customPeriod` and `customPeriodUnit`.

## Update or retire a plan

Requires `plans:write`. Send only fields that should change.

```bash theme={null}
curl -X PATCH "https://api.1club.ai/v1/platform/plans/9" \
  -H "Authorization: Bearer 1club_sk_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: retire-plan-9" \
  -d '{ "isActive": false }'
```

Set `isActive` to `false` to retire a plan. Existing memberships keep their snapshotted plan details, but the plan is no longer something the gym sells.

A retired plan drops out of [plan search](#search-plans), so an integration that lists plans stops offering it. It does not disappear:

* `GET /v1/platform/plans/{id}` still returns it, with `isActive: false`.
* `GET /v1/platform/plans?includeInactive=true` lists it again alongside the active ones.
* `PATCH` with `{ "isActive": true }` puts it back on sale.

## Delete a plan

Requires `plans:write`.

```bash theme={null}
curl -X DELETE "https://api.1club.ai/v1/platform/plans/9" \
  -H "Authorization: Bearer 1club_sk_live_..." \
  -H "Idempotency-Key: delete-plan-9"
```

Deletion returns `204 No Content`. If memberships or other records still reference the plan, the API returns `409 Conflict`. Retire an in-use plan instead of deleting it.

## What the fields mean for a sale

| Field              | Why it matters                                                                                                                                   |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `price`            | Charged each billing period.                                                                                                                     |
| `signupFee`        | Charged **once** when the membership starts, on top of `price`. A plan can charge one even when `price` is 0, so the total can exceed the price. |
| `billingFrequency` | How often `price` recurs, and what the membership's end date is computed from when you do not set one.                                           |
| `maxUses`          | Session allowance a membership gets. `null` means unlimited.                                                                                     |
| `sessionUnit`      | What a use is counted in - a booking or an hour.                                                                                                 |
| `validityPeriod`   | Days the membership stays valid, when the plan is time-limited.                                                                                  |
| `autoRenews`       | Whether a membership on this plan renews by default.                                                                                             |

<Tip>
  Read the plan before selling it and you can show the customer the real total -
  `price` plus `signupFee` - rather than discovering the joining fee after the
  charge lands.
</Tip>

## Next step

Pass the `planId` to
[Create a membership](/api-reference/channel-integration/memberships#create-a-membership).
