# Maintenance plans

> Read the maintenance plans a company sells and which customers are enrolled in them, and enroll a customer.

- Canonical URL: https://couplrfsm.com/developers/api-reference/maintenance-plans
- Markdown: https://couplrfsm.com/developers/api-reference/maintenance-plans.md
- API version: v1 (base URL https://couplrfsm.com/api/v1)
- OpenAPI specification: https://couplrfsm.com/developers/openapi.yaml
- All documentation pages: https://couplrfsm.com/llms.txt

> **Flip:** A maintenance plan is a membership a company sells: regular tune-ups for a monthly or yearly price. Here you can see the plans on offer and who is enrolled, and you can start an enrollment. I do not charge anyone because an API call asked. I give the customer a checkout page, and the plan starts when they finish it.

Base URL: `https://couplrfsm.com/api/v1`. Conventions shared by every endpoint (examples, ids, dates, money) are on the [API reference](https://couplrfsm.com/developers/api-reference) page. Errors, pagination and idempotency are in [Errors, limits and retries](https://couplrfsm.com/developers/errors-and-limits).

## List maintenance plans

`GET /api/v1/maintenance-plans` · requires the `plans:read` ability

The plans the company sells, in name order. A plan counts as changed for `updated_since` when one of its prices changes.

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `page` | integer | No | Default: `1`. |
| `per_page` | integer | No | Values above 100 are served as 100. Default: `25`. |
| `updated_since` | string (date-time) | No | Only records changed at or after this time. ISO 8601, for example `2026-11-14T15:00:00Z`. |

**Example request**

```bash
curl "https://couplrfsm.com/api/v1/maintenance-plans" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Example response** (`200`)

```json
{
    "data": [
        {
            "id": 12,
            "name": "Comfort Club",
            "description": "Two tune-ups a year and priority scheduling.",
            "visits_per_year": 2,
            "prices": [
                {
                    "billing_frequency": "month",
                    "price": "19.00"
                },
                {
                    "billing_frequency": "year",
                    "price": "199.00"
                }
            ],
            "created_at": "2026-03-02T16:00:00+00:00",
            "updated_at": "2026-03-02T16:00:00+00:00"
        }
    ],
    "meta": {
        "page": 1,
        "per_page": 25,
        "total": 1,
        "last_page": 1
    }
}
```

**Errors**

| Status | When |
| --- | --- |
| `401` | No key (`api_key_missing`), the key is unknown or revoked (`api_key_invalid`), the key had an end date that has passed (`api_key_expired`), or the key belongs to a private trial sandbox whose access window has ended (`sandbox_expired`). |
| `403` | The key lacks the ability this endpoint needs (`ability_missing`), no admin user is left to act for the key (`no_acting_admin`), or the company's account is not yet verified. |
| `422` | A parameter or field is invalid (`validation_failed`). `errors` maps each field to its messages. |
| `429` | Too many requests (`rate_limited`). Wait `Retry-After` seconds. |

## Get a maintenance plan

`GET /api/v1/maintenance-plans/{id}` · requires the `plans:read` ability

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | integer | Yes |  |

**Example request**

```bash
curl "https://couplrfsm.com/api/v1/maintenance-plans/1842" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Example response** (`200`)

```json
{
    "data": {
        "id": 12,
        "name": "Comfort Club",
        "description": "Two tune-ups a year and priority scheduling.",
        "visits_per_year": 2,
        "prices": [
            {
                "billing_frequency": "month",
                "price": "19.00"
            },
            {
                "billing_frequency": "year",
                "price": "199.00"
            }
        ],
        "created_at": "2026-03-02T16:00:00+00:00",
        "updated_at": "2026-03-02T16:00:00+00:00"
    }
}
```

**Errors**

| Status | When |
| --- | --- |
| `401` | No key (`api_key_missing`), the key is unknown or revoked (`api_key_invalid`), the key had an end date that has passed (`api_key_expired`), or the key belongs to a private trial sandbox whose access window has ended (`sandbox_expired`). |
| `403` | The key lacks the ability this endpoint needs (`ability_missing`), no admin user is left to act for the key (`no_acting_admin`), or the company's account is not yet verified. |
| `404` | No such record in this company (`not_found`). |
| `429` | Too many requests (`rate_limited`). Wait `Retry-After` seconds. |

## List plan enrollments

`GET /api/v1/maintenance-plan-enrollments` · requires the `plans:read` ability

Each customer's enrollment in a plan, newest first.

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `page` | integer | No | Default: `1`. |
| `per_page` | integer | No | Values above 100 are served as 100. Default: `25`. |
| `updated_since` | string (date-time) | No | Only records changed at or after this time. ISO 8601, for example `2026-11-14T15:00:00Z`. |
| `customer_id` | integer | No |  |
| `plan_id` | integer | No |  |
| `status` | string | No | Exact status match, e.g. `active`. |

**Example request**

```bash
curl "https://couplrfsm.com/api/v1/maintenance-plan-enrollments" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Example response** (`200`)

```json
{
    "data": [
        {
            "id": 340,
            "plan_id": 12,
            "plan_name": "Comfort Club",
            "customer_id": 1842,
            "customer_name": "Dana Whitfield",
            "address_id": 2210,
            "status": "active",
            "start_date": "2026-03-15",
            "number_of_systems": 1,
            "billing_frequency": "month",
            "recurring_amount": "19.00",
            "next_billing_date": "2026-12-15",
            "checkout_url": null,
            "created_at": "2026-03-15T17:20:05+00:00",
            "updated_at": "2026-11-15T09:00:00+00:00"
        }
    ],
    "meta": {
        "page": 1,
        "per_page": 25,
        "total": 1,
        "last_page": 1
    }
}
```

**Errors**

| Status | When |
| --- | --- |
| `401` | No key (`api_key_missing`), the key is unknown or revoked (`api_key_invalid`), the key had an end date that has passed (`api_key_expired`), or the key belongs to a private trial sandbox whose access window has ended (`sandbox_expired`). |
| `403` | The key lacks the ability this endpoint needs (`ability_missing`), no admin user is left to act for the key (`no_acting_admin`), or the company's account is not yet verified. |
| `422` | A parameter or field is invalid (`validation_failed`). `errors` maps each field to its messages. |
| `429` | Too many requests (`rate_limited`). Wait `Retry-After` seconds. |

## Enroll a customer in a plan

`POST /api/v1/maintenance-plan-enrollments` · requires the `plans:write` ability · accepts `Idempotency-Key`

Starts an enrollment. It is created with status `pending payment`
and a `checkout_url`: Couplr's checkout page for this customer and
plan. **Nothing is charged by this call.** Billing starts when the
customer completes the checkout page, and the enrollment then
becomes `active`.

Couplr also books the plan's first visit on the schedule and, if the
customer uses email, sends them the plan email with the checkout
link, as it does when someone enrolls a customer in the apps.

`billing_frequency` must be one of the plan's current `prices`. The
price is the plan's own; it cannot be set here, and neither can a
discount.

A repeat of the same enrollment within a few minutes returns the
first one with `200` instead of creating a second.

Refused with `422` when the account has no checkout page for the
customer to complete (on `plan_id`); enroll the customer in Couplr
instead. If the checkout page cannot be created at that moment, the
answer is also a `422`: the enrollment is kept as `pending payment`
in Couplr for the office to send, and is never made active. An enrollment cannot be changed or cancelled through the API.

**Headers**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `Idempotency-Key` | string | No | Makes a retry of this request safe. See "Retries and idempotency". |

**Request body** (`application/json`)

Fields not listed are ignored.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `customer_id` | integer | Yes |  |
| `plan_id` | integer | Yes |  |
| `billing_frequency` | string | Yes | One of the plan's `prices`, e.g. `month` or `year`. |
| `address_id` | integer or null | No | The address the plan covers. Omit to use the customer's main address. |
| `start_date` | string (date) or null | No | Defaults to today. |
| `number_of_systems` | integer | No | Default: `1`. |
| `notes` | string or null | No |  |

**Example request**

```bash
curl -X POST "https://couplrfsm.com/api/v1/maintenance-plan-enrollments" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: YOUR_UNIQUE_KEY" \
  -d '{"customer_id":1842,"plan_id":12,"billing_frequency":"month"}'
```

**Example response** (`200`)

```json
{
    "data": {
        "id": 340,
        "plan_id": 12,
        "plan_name": "Comfort Club",
        "customer_id": 1842,
        "customer_name": "Dana Whitfield",
        "address_id": 2210,
        "status": "active",
        "start_date": "2026-03-15",
        "number_of_systems": 1,
        "billing_frequency": "month",
        "recurring_amount": "19.00",
        "next_billing_date": "2026-12-15",
        "checkout_url": null,
        "created_at": "2026-03-15T17:20:05+00:00",
        "updated_at": "2026-11-15T09:00:00+00:00"
    }
}
```

**Example response** (`201`)

```json
{
    "data": {
        "id": 340,
        "plan_id": 12,
        "plan_name": "Comfort Club",
        "customer_id": 1842,
        "customer_name": "Dana Whitfield",
        "address_id": 2210,
        "status": "active",
        "start_date": "2026-03-15",
        "number_of_systems": 1,
        "billing_frequency": "month",
        "recurring_amount": "19.00",
        "next_billing_date": "2026-12-15",
        "checkout_url": null,
        "created_at": "2026-03-15T17:20:05+00:00",
        "updated_at": "2026-11-15T09:00:00+00:00"
    }
}
```

**Errors**

| Status | When |
| --- | --- |
| `401` | No key (`api_key_missing`), the key is unknown or revoked (`api_key_invalid`), the key had an end date that has passed (`api_key_expired`), or the key belongs to a private trial sandbox whose access window has ended (`sandbox_expired`). |
| `403` | The key lacks the ability this endpoint needs (`ability_missing`), no admin user is left to act for the key (`no_acting_admin`), or the company's account is not yet verified. |
| `422` | A parameter or field is invalid (`validation_failed`). `errors` maps each field to its messages. |
| `429` | Too many requests (`rate_limited`). Wait `Retry-After` seconds. |

## Get a plan enrollment

`GET /api/v1/maintenance-plan-enrollments/{id}` · requires the `plans:read` ability

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | integer | Yes |  |

**Example request**

```bash
curl "https://couplrfsm.com/api/v1/maintenance-plan-enrollments/1842" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Example response** (`200`)

```json
{
    "data": {
        "id": 340,
        "plan_id": 12,
        "plan_name": "Comfort Club",
        "customer_id": 1842,
        "customer_name": "Dana Whitfield",
        "address_id": 2210,
        "status": "active",
        "start_date": "2026-03-15",
        "number_of_systems": 1,
        "billing_frequency": "month",
        "recurring_amount": "19.00",
        "next_billing_date": "2026-12-15",
        "checkout_url": null,
        "created_at": "2026-03-15T17:20:05+00:00",
        "updated_at": "2026-11-15T09:00:00+00:00"
    }
}
```

**Errors**

| Status | When |
| --- | --- |
| `401` | No key (`api_key_missing`), the key is unknown or revoked (`api_key_invalid`), the key had an end date that has passed (`api_key_expired`), or the key belongs to a private trial sandbox whose access window has ended (`sandbox_expired`). |
| `403` | The key lacks the ability this endpoint needs (`ability_missing`), no admin user is left to act for the key (`no_acting_admin`), or the company's account is not yet verified. |
| `404` | No such record in this company (`not_found`). |
| `429` | Too many requests (`rate_limited`). Wait `Retry-After` seconds. |
