# Build a per-seat subscription model

**Bill a unit price for every seat - with a seat count that's fixed on the plan or reported as it changes.**

Per-seat pricing bills a unit price for each user, licence, or device: £15.00 per seat per month, however many seats the customer has. The Merchant API supports two shapes, and the one you pick determines how seat counts are handled for the life of the subscription:

- **Static seats** - the seat count is fixed on the plan as a `flat` item with a `quantity`. Every cycle bills the same amount, and seat changes go through a scheduled plan change.
- **Dynamic seats** - the plan carries a `usage` item with `latest` aggregation. You report the current seat count through the usage API whenever it changes, and each cycle bills the count you most recently reported.

In this guide, you sell a **Team plan** - £15.00 per seat per month, offered three ways: a 5-seat pack for £75.00 a month, a 10-seat pack for £150.00, and a flexible option that bills whatever seat count you report.

The example demonstrates how to model seat-based pricing on a single plan. Each **variation** is one way to buy the same £15.00 seat: the packs fix the count on a `flat` item, while the flexible option leaves the count to you and bills the seats you report. Map your own pricing the same way - every seat arrangement becomes a variation, and each customer subscribes to exactly one of them.

:::tip[Combining pricing models]
Items can share a phase - a per-seat item alongside a flat platform fee, for example. For the rules, see [Subscription plans](/docs/guides/merchant/billing-subscriptions/subscription-plans).
:::

## How it works

This guide walks the full flow: all API calls run between your backend and the Merchant API, and only [step 3](#3-collect-first-payment) - collecting the first payment - reaches the customer's browser.

1. **Create a per-seat plan** - model the seat price as items on variations: static seat packs carry a `flat` item with a `quantity`, and the dynamic option carries a `usage` item with `latest` aggregation.
2. **Subscribe the customer** - create the subscription, which starts in `pending` state until the first payment is collected.
3. **Collect the first payment** - retrieve the setup order, hand the customer a payment widget or Revolut's Hosted Checkout Page, and confirm the subscription is `active` - Revolut bills automatically from there.
4. **Operate the subscription** - report the seat count on the dynamic path, switch seat packs on the static paths, retrieve the billing cycles as they accrue, and cancel the subscription when the customer leaves.

The two seat shapes differ in how seat counts are handled for the life of the subscription:

| | Static seats | Dynamic seats |
|---|---|---|
| Plan configuration | `flat` item with `quantity: N` | `usage` item with `usage_aggregation_method: latest` |
| Charge each cycle | Unit `amount * quantity` | Unit `amount` * most recently reported count |
| Seat changes | Switch to another variation, scheduled `at_cycle_end` | Report the new count via the usage API |
| Unreported cycle | N/A - always bills the plan `quantity` | Charges `0` - reported values don't carry over |
| Best for | Stable team sizes, predictable invoices | Growing teams, monthly seat true-ups |

The Team plan you'll build in this guide looks like this:

```mermaid
flowchart TD
    subgraph plan ["Team plan"]
        subgraph seats5 ["5-seat variation - £75.00/month"]
            item1["flat item - quantity 5 x £15.00"]
        end
        subgraph seats10 ["10-seat variation - £150.00/month"]
            item2["flat item - quantity 10 x £15.00"]
        end
        subgraph flex ["Flexible seats - £15.00 per reported seat"]
            item3["usage item - latest reported count x £15.00"]
        end
    end
```

### Before you begin

Before you start, make sure you have the following:

- [ ] You've completed **[Get started with the Subscriptions API](/docs/guides/merchant/billing-subscriptions/api/get-started)** - it introduces the universal subscription flow this guide builds on
- [ ] An **existing customer** - see [Create a customer](/docs/api/merchant#create-customer)
- [ ] Familiarity with the **core subscription concepts** - see [Subscription plans](/docs/guides/merchant/billing-subscriptions/subscription-plans) and [Subscription lifecycle](/docs/guides/merchant/billing-subscriptions/subscription-lifecycle)

---

## Implement per-seat subscription

The steps below follow the Team plan narrative: you create the plan with its three variations, subscribe a customer to the 5-seat pack, collect the first payment, and confirm the subscription is `active`.

### 1. Create plan for per-seat pricing

Your Team plan sells the same £15.00 seat in three variations. Each variation is a single phase, and the seat price lives on an item in the phase: the seat packs carry a `flat` item with the count fixed in `quantity`, and the flexible variation carries a `usage` item that bills whatever count you report.

:::info
For the plan, variation, and phase hierarchy these items sit in, see [Get started with the subscriptions API](/docs/guides/merchant/billing-subscriptions/api/get-started).
:::

Call [Create a subscription plan](/docs/api/merchant#create-subscription-plan), turning your seat pricing into a plan your customers can subscribe to:

- ![Request]
  ```http [Request example]
  POST /api/subscription-plans HTTP/1.1
  Host: merchant.revolut.com
  Authorization: Bearer sk_abcdef12347890_...
  Revolut-Api-Version: 2026-08-17
  Content-Type: application/json

  {
    "name": "Team plan",
    "variations": [
      {
        "phases": [
          {
            "ordinal": 1,
            "cycle_duration": "P1M",
            "amount": 7500,
            "currency": "GBP",
            "subscription_items": [
              {
                "type": "flat",
                "name": "Team seats",
                "unit": "seat",
                "quantity": 5,
                "amount": 1500,
                "currency": "GBP"
              }
            ]
          }
        ]
      },
      {
        "phases": [
          {
            "ordinal": 1,
            "cycle_duration": "P1M",
            "amount": 15000,
            "currency": "GBP",
            "subscription_items": [
              {
                "type": "flat",
                "name": "Team seats",
                "unit": "seat",
                "quantity": 10,
                "amount": 1500,
                "currency": "GBP"
              }
            ]
          }
        ]
      },
      {
        "phases": [
          {
            "ordinal": 1,
            "cycle_duration": "P1M",
            "subscription_items": [
              {
                "type": "usage",
                "name": "Active seats",
                "unit": "seat",
                "code": "active_seats",
                "usage_aggregation_method": "latest",
                "amount": 1500,
                "currency": "GBP"
              }
            ]
          }
        ]
      }
    ]
  }
  ```

  | Parameter | Description |
  |-----------|-------------|
  | `name` | The plan name - what your customers subscribe to |
  | `variations` | Purchasing options for the plan - here, two seat packs and the flexible option |
  | `phases` | Sequential pricing stages within a variation, executed in `ordinal` order |
  | `ordinal` | Execution order of the phase, starting at `1` |
  | `cycle_duration` | Length of the billing cycle, as an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) duration, e.g., `P1M` |
  | `amount` | Price per billing cycle, in minor units - `7500` is £75.00, `15000` is £150.00 |
  | `currency` | Billing currency, as an [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) code |
  | `subscription_items` | The seat price as items on the phase - here, one item per variation |
  | `subscription_items[].type` | `flat` bills a fixed `quantity` every cycle; `usage` bills a count you report |
  | `subscription_items[].quantity` | Seat count on a `flat` item - each cycle charges `amount * quantity` |
  | `subscription_items[].unit` | What the item bills - a free-form label, `seat` here |
  | `subscription_items[].package_size` | Units grouped into one billable package - optional, defaults to `1`; for seats, leave it at `1` |
  | `subscription_items[].code` | Merchant-defined identifier for a `usage` item - unique within the phase; you supply it when reporting the seat count |
  | `subscription_items[].usage_aggregation_method` | How reported counts become the charge - `latest` uses the most recently reported value at cycle end |
  | `subscription_items[].amount` | Price per seat in minor units - `1500` is £15.00 |

- ![Response]
  The response returns the plan with a system-generated `id`, an `id` for each variation and phase, and an `id` for each item.

  Save the `id` of the variation the customer subscribes to - here, the 5-seat variation for the walkthrough. The 10-seat variation comes back in [Change seat count](#change-seat-count), and on the dynamic path, customers subscribe to the flexible variation instead.

  ```json [Response example] {9}
  {
    "id": "4fe4870c-f968-42dc-b2fb-6ac4ac9cbb87",
    "name": "Team plan",
    "state": "active",
    "created_at": "2026-01-26T08:59:09.433527Z",
    "updated_at": "2026-01-26T08:59:09.433527Z",
    "variations": [
      {
        "id": "993236c2-fd84-4b1f-b90a-f9691537beb8",
        "phases": [
          {
            "id": "3de61e3f-c665-4627-b865-dc0980578cbc",
            "ordinal": 1,
            "cycle_duration": "P1M",
            "amount": 7500,
            "currency": "GBP",
            "subscription_items": [
              {
                "id": "c7a612e6-340e-4f0e-82bf-b1b0af58dc4d",
                "type": "flat",
                "name": "Team seats",
                "unit": "seat",
                "quantity": 5,
                "amount": 1500,
                "currency": "GBP"
              }
            ]
          }
        ]
      },
      {
        "id": "72222cd7-87d5-408b-b604-880a6ac59451",
        "phases": [
          {
            "id": "7bebc04a-b140-444b-915e-5a8fcd9c6145",
            "ordinal": 1,
            "cycle_duration": "P1M",
            "amount": 15000,
            "currency": "GBP",
            "subscription_items": [
              {
                "id": "56a2ee59-a864-45d4-8a3d-1c231136f3bb",
                "type": "flat",
                "name": "Team seats",
                "unit": "seat",
                "quantity": 10,
                "amount": 1500,
                "currency": "GBP"
              }
            ]
          }
        ]
      },
      {
        "id": "d62c249c-4f3f-49b5-adc5-f2678e24f30a",
        "phases": [
          {
            "id": "fb26e30e-4aaa-4639-ac79-e1db418703a4",
            "ordinal": 1,
            "cycle_duration": "P1M",
            "subscription_items": [
              {
                "id": "01e2ff5b-99f5-44e4-acac-2ae15aed4f95",
                "type": "usage",
                "name": "Active seats",
                "unit": "seat",
                "code": "active_seats",
                "usage_aggregation_method": "latest",
                "amount": 1500,
                "currency": "GBP"
              }
            ]
          }
        ]
      }
    ]
  }
  ```

  | Parameter | Description |
  |-----------|-------------|
  | `id` | The plan ID - the plan's identifier in the API |
  | `state` | `active` - the plan is ready to accept subscriptions |
  | `variations[].id` | The variation IDs - save the one the customer subscribes to; you'll use it in [step 2](#2-subscribe-customer) |
  | `phases[].id` | The phase IDs - each billing cycle is created under one of them |
  | `subscription_items[].id` | The item IDs - generated for each item you configured |


### 2. Subscribe customer

When a customer wants to subscribe to your **Team plan**, you create a subscription. The subscription connects the customer - and their consent to use their payment details for recurring charges - to the variation they chose: the 5-seat variation in this walkthrough.

Subscription creation is the same for every pricing model - the pricing lives entirely in the plan you built. Once created, the subscription starts in `pending` state - it becomes `active` once the customer completes the first payment.

Call [Create a subscription](/docs/api/merchant#create-subscription) with an idempotency key, so you can safely retry the request without creating duplicate subscriptions:

- ![Request]
  ```http [Request example] {9}
  POST /api/subscriptions HTTP/1.1
  Host: merchant.revolut.com
  Authorization: Bearer sk_abcdef12347890_...
  Revolut-Api-Version: 2026-08-17
  Content-Type: application/json
  Idempotency-Key: 916b2ee5-a6bf-441d-9077-e4b9fc93c5ba

  {
    "plan_variation_id": "993236c2-fd84-4b1f-b90a-f9691537beb8",
    "customer_id": "650e8400-e29b-41d4-a716-446655440001",
    "external_reference": "team_3f9a2c",
    "setup_order_redirect_url": "https://example.com/subscription/complete"
  }
  ```

  | Parameter | Description |
  |-----------|-------------|
  | `plan_variation_id` | The variation the customer subscribes to - the 5-seat variation `id` you saved in [step 1](#1-create-plan-for-per-seat-pricing) |
  | `customer_id` | The customer to bill |
  | `external_reference` | Optional: your own identifier for the subscription, e.g., the customer's ID in your system - returned in responses and echoed in webhook events |
  | `setup_order_redirect_url` | Optional: where the customer lands after completing payment on the hosted checkout page |

- ![Response]
  The subscription is created in `pending` state. Save the `setup_order_id` - [step 3](#3-collect-first-payment) collects the first payment with it - and the subscription `id` - [step 4](#4-verify-subscription-state) confirms activation with it, and the [Operate section](#operate-per-seat-subscription) uses it for seat adjustments, billing cycles, and cancellation.

  ```json [Response example] {2,11}
  {
    "id": "c33e76eb-bc87-4f4d-a56b-88c2a069268d",
    "external_reference": "team_3f9a2c",
    "state": "pending",
    "customer_id": "650e8400-e29b-41d4-a716-446655440001",
    "plan_id": "4fe4870c-f968-42dc-b2fb-6ac4ac9cbb87",
    "plan_variation_id": "993236c2-fd84-4b1f-b90a-f9691537beb8",
    "payment_method_type": "automatic",
    "created_at": "2026-01-26T09:15:00.036001Z",
    "updated_at": "2026-01-26T09:15:00.036001Z",
    "setup_order_id": "22425f3b-ac69-4c97-add5-905c4dc831fd",
    "current_cycle_id": "055f3a58-89d8-4a45-ac95-cbf27e053f2d"
  }
  ```

  | Parameter | Description |
  |-----------|-------------|
  | `id` | The subscription ID - used to adjust seats, retrieve the billing cycles, and cancel the subscription |
  | `state` | `pending` - the subscription becomes `active` once the customer completes the first payment |
  | `setup_order_id` | The setup order collecting the first payment - [step 3](#3-collect-first-payment) retrieves the order with it |
  | `current_cycle_id` | The cycle currently billing the subscription - retrieve it for entitlement checks in [Retrieve billing cycles](#retrieve-billing-cycles) |

:::tip[First-cycle charges differ by shape]
On the static paths, the first payment covers the first cycle's full seat charge - £75.00 here. On the dynamic path, the first payment is collected before any seat count is reported - the usage item contributes nothing until you report usage, so make sure your first report lands inside the cycle.
:::

### 3. Collect first payment

In [step 2](#2-subscribe-customer) you created the subscription. It is in `pending` state, and becomes `active` once the customer completes the first payment. That payment is collected through a **setup order** - an order that exists to start a subscription.

Revolut creates the setup order in the background when you create the subscription: it takes the first charge - £75.00 for the 5-seat variation - and saves the customer's payment method for the recurring cycles that follow. You already saved its `setup_order_id`.

#### 3.1 Retrieve setup order

Retrieve the setup order with [Retrieve an order](/docs/api/merchant#retrieve-order), passing the `setup_order_id` you saved in [step 2](#2-subscribe-customer) as the `order_id`:

- ![Request]
  ```http [Request example]
  GET /api/orders/{order_id} HTTP/1.1
  Host: merchant.revolut.com
  Authorization: Bearer sk_abcdef12347890_...
  Revolut-Api-Version: 2026-08-17
  ```

  | Parameter | Description |
  |-----------|-------------|
  | `order_id` | The ID of the setup order you saved in [step 2](#2-subscribe-customer) |

- ![Response]
  The response returns the setup order. The fields that unlock the checkout approaches below are - `token` for embedding a payment widget on your site and `checkout_url` for Revolut's Hosted Checkout Page.

  ```json [Response example] {3,9}
  {
    "id": "22425f3b-ac69-4c97-add5-905c4dc831fd",
    "token": "7fef7ce2-2627-4c35-9b07-ac6f46cbff01",
    "state": "pending",
    "created_at": "2026-01-26T09:15:00.036001Z",
    "updated_at": "2026-01-26T09:15:00.036001Z",
    "amount": 7500,
    "currency": "GBP",
    "checkout_url": "https://checkout.revolut.com/payment-link/7fef7ce2-2627-4c35-9b07-ac6f46cbff01"
  }
  ```

  | Parameter | Description |
  |-----------|-------------|
  | `token` | The public token for initialising a payment widget |
  | `state` | `pending` - the order completes once the customer pays the £75.00 first charge |
  | `checkout_url` | The link to Revolut's Hosted Checkout Page |

Like the calls in the previous steps, this is a server-side operation - it happens between your backend and the Merchant API.

#### 3.2 Build your payment acceptance solution

Collecting the payment reaches beyond your backend: the customer pays in their browser or mobile app, so this part of the flow has client-side work alongside your server. The goal is to hand the customer a way to pay the first charge and save their payment method. Revolut supports two approaches, and both do this for you:

- [:CodeRepository: Payment widget](# "Embed the payment flow in your site: you build the customer experience, and the customer never leaves your page")
- [:Browser: Hosted Checkout Page](# "Redirect to a payment page Revolut hosts for you: no payment UI to build, and Revolut handles the payment experience")

Choose the approach that fits your integration:

- ![Payment widget]
  Keep the customer on your site with an embedded payment widget or a card-not-present method:

  1. Build your payment acceptance solution on your frontend with the widget or payment method of your choice, initialising it with the `token` from the response.
  1. Configure it to save the customer's payment method for merchant-initiated recurring transactions.
  1. The customer completes payment without leaving your site, and Revolut saves their payment method.

- ![Hosted Checkout Page]
  Redirect the customer to Revolut's Hosted Checkout Page:

  1. Redirect the customer to the `checkout_url` from the response.
  1. The customer completes payment on the hosted page, and Revolut saves their payment method.
  1. If you set `setup_order_redirect_url` in [step 2](#2-subscribe-customer), the customer is redirected there after payment; otherwise they see Revolut's default completion screen.

:::info
This step assumes you're familiar with a standard payment method integration, see [Introduction to online payments](/docs/guides/merchant/accept-payments/online-payments/introduction).
:::

### 4. Verify subscription state

When the customer completes the first payment in [step 3](#3-collect-first-payment), the subscription becomes `active`. There's no webhook event for activation, so retrieve the subscription to confirm it's live.

Check the subscription details with [Retrieve a subscription](/docs/api/merchant#retrieve-subscription), passing the subscription `id` from [step 2](#2-subscribe-customer) in the request path:

- ![Request]
  ```http [Request example]
  GET /api/subscriptions/{subscription_id} HTTP/1.1
  Host: merchant.revolut.com
  Authorization: Bearer sk_abcdef12347890_...
  Revolut-Api-Version: 2026-08-17
  ```

  | Parameter | Description |
  |-----------|-------------|
  | `subscription_id` | The ID of the subscription you saved in [step 2](#2-subscribe-customer) |

- ![Response]
  When the first payment succeeds, the subscription moves to `active` with a saved `payment_method_id` and a `start_date`.

  ```json [Response example] {4,9,12,13}
  {
    "id": "c33e76eb-bc87-4f4d-a56b-88c2a069268d",
    "external_reference": "team_3f9a2c",
    "state": "active",
    "customer_id": "650e8400-e29b-41d4-a716-446655440001",
    "plan_id": "4fe4870c-f968-42dc-b2fb-6ac4ac9cbb87",
    "plan_variation_id": "993236c2-fd84-4b1f-b90a-f9691537beb8",
    "payment_method_type": "automatic",
    "payment_method_id": "92e6646e-8202-4ab6-816e-7fbbce66f4a7",
    "created_at": "2026-01-26T09:15:00.036001Z",
    "updated_at": "2026-01-26T09:20:00.036001Z",
    "start_date": "2026-01-26T09:20:00.036001Z",
    "current_cycle_id": "055f3a58-89d8-4a45-ac95-cbf27e053f2d"
  }
  ```

  | Parameter | Description |
  |-----------|-------------|
  | `state` | `active` - the subscription is live and charges on each billing cycle |
  | `payment_method_id` | The saved payment method - Revolut charges it automatically on each cycle |
  | `start_date` | When the subscription became `active` |
  | `current_cycle_id` | The cycle currently billing the subscription - [Retrieve billing cycles](#retrieve-billing-cycles) uses it |

Once the subscription is `active`, your per-seat subscription is live, and Revolut takes over from here. We charge the customer's saved payment method the seat charge of the subscribed variation - £75.00 a month on the 5-seat pack. Failed payments are retried automatically - see [Failed payments and retries](/docs/guides/merchant/billing-subscriptions/subscription-lifecycle#failed-payments-and-retries).

:::tip
**You've completed the per-seat subscription flow!** The Team plan is live with its three variations, and Revolut bills the customer automatically from here. Next, operate the subscription: report or change the seat count as the team evolves, retrieve the billing cycles as they accrue, and cancel it when the customer leaves.
:::

---

## Operate per-seat subscription

With the subscription active, Revolut bills the saved payment method automatically. What remains yours while it runs depends on the shape the customer chose.

On the dynamic path, you keep the reported seat count current; on the static paths, you move the customer between seat packs. On every path, you also retrieve the billing cycles - for entitlement checks, billing history, and reconciliation - and cancel the subscription when the customer leaves.

### Adjust seat count

Seat counts change as teams grow and shrink. How you keep the billed count current depends on the shape the customer subscribed to - report it on the dynamic path, switch seat packs on the static paths.

#### Report seat count

On the dynamic path, reporting seat count is a recurring operation: whenever the customer's team changes size - seats added, seats removed - report the current count, and Revolut charges the latest value at each cycle's end. The example below follows a subscription on the flexible variation, with the customer's team at 12 seats.

:::warning
The `Idempotency-Key` header is **required** for this endpoint - it prevents retries from creating duplicate usage records.
:::

Call [Create a subscription usage](/docs/api/merchant#create-subscription-usage), passing the subscription `id` and the `subscription_item_code` you configured in [step 1](#1-create-plan-for-per-seat-pricing), to tell Revolut the seat count to bill at the cycle's end:

- ![Request]
  ```http [Request example] {9,10}
  POST /api/subscription-usages HTTP/1.1
  Host: merchant.revolut.com
  Authorization: Bearer sk_abcdef12347890_...
  Revolut-Api-Version: 2026-08-17
  Content-Type: application/json
  Idempotency-Key: a5e687aa-251e-47e5-b2b0-cd6a773b7097

  {
    "subscription_id": "beebf6fb-f887-4bcf-af78-d46c7be5825f",
    "subscription_item_code": "active_seats",
    "usage_date": "2026-02-15T09:30:00Z",
    "quantity": 12
  }
  ```

  | Parameter | Description |
  |-----------|-------------|
  | `subscription_id` | The ID of the subscription on the dynamic variation |
  | `subscription_item_code` | The `code` you gave the usage item in [step 1](#1-create-plan-for-per-seat-pricing) - here `active_seats` |
  | `usage_date` | When the count applies - Revolut resolves the billing cycle from this date |
  | `quantity` | The current seat count - a gauge, not a delta: report the total count, not the change |

- ![Response]
  The response confirms the report and shows the billing cycle it's connected.

  ```json [Response example] {4}
  {
    "id": "e965f21e-7d98-4e3b-847f-ae1a562c5468",
    "subscription_id": "beebf6fb-f887-4bcf-af78-d46c7be5825f",
    "subscription_cycle_id": "12e698d4-5110-4c65-8446-d4d879309144",
    "subscription_item_code": "active_seats",
    "usage_date": "2026-02-15T09:30:00Z",
    "quantity": 12,
    "created_at": "2026-02-15T09:30:05Z",
    "updated_at": "2026-02-15T09:30:05Z"
  }
  ```

  | Parameter | Description |
  |-----------|-------------|
  | `subscription_cycle_id` | The cycle the report landed in - Revolut identified it from `usage_date` |
  | `quantity` | The seat count this cycle will bill - echoed back as reported |

How reported counts become the cycle charge:

| Reporting scenario | What happens |
|--------------------|--------------|
| Report multiple times in a cycle | The most recent report at the cycle's cutoff date wins - report 5 seats, then 12, and the cycle bills 12 seats: 12 x £15.00 = £180.00 |
| Report nothing during a cycle | The item charges `0` - values don't carry over, so if you stop reporting, billing stops |
| Correct a past cycle | Accepted until its `usage_cutoff_date` - 12 hours after `end_date` by default. Send a new report with a `usage_date` inside the cycle being corrected |
| Report ahead for the next cycle | Accepted and held in a `pending` cycle until that cycle starts |

#### Change seat count

On the static paths, the seat count is fixed on the plan's `flat` item - there's no mid-cycle quantity update. Instead, move the customer to a different seat pack: [Change a subscription plan](/docs/api/merchant#change-subscription-plan) with the new variation's `plan_variation_id`, scheduled to land at the end of the current cycle. The walkthrough customer grows into the 10-seat variation you created in [step 1](#1-create-plan-for-per-seat-pricing):

- ![Request]
  ```http [Request example] {8}
  POST /api/subscriptions/{subscription_id}/change-plan HTTP/1.1
  Host: merchant.revolut.com
  Authorization: Bearer sk_abcdef12347890_...
  Revolut-Api-Version: 2026-08-17
  Content-Type: application/json

  {
    "plan_variation_id": "72222cd7-87d5-408b-b604-880a6ac59451",
    "scheduled": "at_cycle_end"
  }
  ```

  | Parameter | Description |
  |-----------|-------------|
  | `plan_variation_id` | The new seat pack - the 10-seat variation `id` you saved in [step 1](#1-create-plan-for-per-seat-pricing) |
  | `scheduled` | When the change takes effect - `at_cycle_end` completes the current cycle on the old pack first |

- ![Response]
  The plan change is scheduled - the current cycle completes on the 5-seat pack.

  ```http [Response example]
  HTTP/1.1 204 No Content
  ```

The next cycle bills the new pack, with no proration in between: the customer moves from 5 seats (£75.00 a month) to 10 (£150.00 a month) at the cycle boundary. For a team size your plan doesn't model, create a plan with the right `quantity` first, then switch the subscription to it the same way.

### Retrieve billing cycles

While the subscription is active, Revolut creates a billing cycle for every billing period - one a month on the 5-seat variation. Each cycle carries its period, `state`, and the `order_id` of the order that charged it, so a complete billing history builds up as the subscription runs.

The subscription's runtime resources look like this:

```mermaid
flowchart TD
    subgraph subscription ["Subscription c33e76eb - state: active"]
        subgraph cycle2 ["Cycle 004c9dd4 - current, state: active"]
            order2["Order 30b9d75b - charges £75.00"]
        end
        subgraph cycle1 ["Cycle 055f3a58 - state: finished"]
            order1["Order 22425f3b - charged £75.00"]
        end
    end
```

The examples below cover two common use cases - a billing-history view and an entitlement check - but they're just a starting point. Explore how the same calls can serve your own business cases: the cycle data can power reconciliation, customer-facing billing pages, dunning workflows, and anything else your product needs.

#### Retrieve cycle list

The cycle list gives you the subscription's billing history in one call - every cycle so far, its state, and the order that charged each period. Use it for reconciliation and billing-history views, and to discover the current cycle's `id` if you don't have it saved.

Call [Retrieve a subscription cycle list](/docs/api/merchant#retrieve-subscription-cycle-list), passing the `id` of the subscription you created in [step 2](#2-subscribe-customer) as the `subscription_id`:

- ![Request]
  ```http [Request example]
  GET /api/subscriptions/{subscription_id}/cycles HTTP/1.1
  Host: merchant.revolut.com
  Authorization: Bearer sk_abcdef12347890_...
  Revolut-Api-Version: 2026-08-17
  ```

- ![Response]
  The response lists the subscription's cycles - each carries its `id`, period, `state`, and the `order_id` that charged it.

  ```json [Response example] {10}
  {
    "cycles": [
      {
        "id": "004c9dd4-434c-44be-af4e-8d3c8f9864a6",
        "subscription_id": "c33e76eb-bc87-4f4d-a56b-88c2a069268d",
        "plan_variation_id": "993236c2-fd84-4b1f-b90a-f9691537beb8",
        "plan_variation_phase_id": "3de61e3f-c665-4627-b865-dc0980578cbc",
        "number": 2,
        "previous_cycle_id": "055f3a58-89d8-4a45-ac95-cbf27e053f2d",
        "state": "active",
        "start_date": "2026-02-26T09:20:00.036001Z",
        "end_date": "2026-03-26T09:20:00.036001Z",
        "order_id": "30b9d75b-08f1-4de8-a431-25627f691493",
        "trial": false
      },
      {
        "id": "055f3a58-89d8-4a45-ac95-cbf27e053f2d",
        "subscription_id": "c33e76eb-bc87-4f4d-a56b-88c2a069268d",
        "plan_variation_id": "993236c2-fd84-4b1f-b90a-f9691537beb8",
        "plan_variation_phase_id": "3de61e3f-c665-4627-b865-dc0980578cbc",
        "number": 1,
        "state": "finished",
        "start_date": "2026-01-26T09:20:00.036001Z",
        "end_date": "2026-02-26T09:20:00.036001Z",
        "order_id": "22425f3b-ac69-4c97-add5-905c4dc831fd",
        "trial": false
      }
    ]
  }
  ```

  | Parameter | Description |
  |-----------|-------------|
  | `id` | The cycle's identifier - retrieve a single cycle with it |
  | `number` | Cycle sequence number, starting at `1` for the first cycle |
  | `state` | `active` while the cycle runs, `finished` once complete |
  | `start_date` / `end_date` | The cycle's billing period - `end_date` is when the next cycle starts |
  | `order_id` | The order charging this cycle - use it to reconcile the seat charge for the period |
  | `trial` | Whether the cycle is a trial cycle - always `false` for per-seat plans without trials |

#### Run entitlement check

A common pattern is gating platform access on the subscription's state: each time the customer logs in, retrieve the current cycle - its `id` is the `current_cycle_id` from the subscription response - and grant access while its `state` is `active`.

The check runs on every login, so keep it lightweight: retrieve just the current cycle instead of pulling the whole list. Call [Retrieve a subscription cycle](/docs/api/merchant#retrieve-subscription-cycle), passing the current cycle's `id` as the `cycle_id`:

- ![Request]
  ```http [Request example]
  GET /api/subscriptions/{subscription_id}/cycles/{cycle_id} HTTP/1.1
  Host: merchant.revolut.com
  Authorization: Bearer sk_abcdef12347890_...
  Revolut-Api-Version: 2026-08-17
  ```

- ![Response]
  The response is the cycle object itself - the same shape as the list items above. For the entitlement check, `state` is the field that matters: grant access while it's `active`.

  ```json [Response example] {8}
  {
    "id": "004c9dd4-434c-44be-af4e-8d3c8f9864a6",
    "subscription_id": "c33e76eb-bc87-4f4d-a56b-88c2a069268d",
    "plan_variation_id": "993236c2-fd84-4b1f-b90a-f9691537beb8",
    "plan_variation_phase_id": "3de61e3f-c665-4627-b865-dc0980578cbc",
    "number": 2,
    "previous_cycle_id": "055f3a58-89d8-4a45-ac95-cbf27e053f2d",
    "state": "active",
    "start_date": "2026-02-26T09:20:00.036001Z",
    "end_date": "2026-03-26T09:20:00.036001Z",
    "order_id": "30b9d75b-08f1-4de8-a431-25627f691493",
    "trial": false
  }
  ```

### Cancel subscription

When the customer wants to leave, cancel the subscription - Revolut stops billing and cancels any pending orders. You can cancel in any state except `cancelled` or `finished`.

Call [Cancel a subscription](/docs/api/merchant#cancel-subscription), passing the `id` of the subscription you created in [step 2](#2-subscribe-customer) as the `subscription_id`:

- ![Request]
  ```http [Request example]
  POST /api/subscriptions/{subscription_id}/cancel HTTP/1.1
  Host: merchant.revolut.com
  Authorization: Bearer sk_abcdef12347890_...
  Revolut-Api-Version: 2026-08-17
  ```

- ![Response]
  The subscription is marked `cancelled` - no further cycles are created.

  ```http [Response example]
  HTTP/1.1 204 No Content
  ```

The subscription moves to `cancelled` - a final state: it can't be reactivated, so if the customer returns, create a new subscription for them. A `SUBSCRIPTION_CANCELLED` webhook event is sent whether the cancellation came from you or the customer - see [Subscription states](/docs/guides/merchant/billing-subscriptions/subscription-plans#subscription-states) and [Track subscriptions with webhooks](/docs/guides/merchant/billing-subscriptions/api/webhooks).

If you [run the entitlement check](#run-entitlement-check), this is where it stops granting access: end access immediately, or - if you let customers finish what they paid for - stop at the current cycle's `end_date`. Cancelling stops future charges only - it doesn't refund completed cycles.

---

## Implementation checklist

Confirm your per-seat integration works end to end. Run the checks in the Sandbox environment first - set the base URL of your API calls to `sandbox-merchant.revolut.com` - then repeat them in production before going live:

- [ ] Created a plan with the seat price as an item - `flat` with `quantity` on the static paths, `usage` with `latest` aggregation on the dynamic path - and saved the variation `id` the customer subscribes to
- [ ] Subscribed a customer, collected the first payment, and confirmed the subscription is `active`
- [ ] Static paths - confirmed each cycle bills `amount` x `quantity`, and a scheduled variation switch moves the customer to the new seat pack at cycle end
- [ ] Dynamic path - reports always send an `Idempotency-Key` and target the right `subscription_item_code`
- [ ] Dynamic path - confirmed an unreported cycle charges `0`, and corrections land before the cycle's `usage_cutoff_date`
- [ ] Cancelled a test subscription - `204` response, no new cycles created, `SUBSCRIPTION_CANCELLED` event received

For verifying the payment solution itself - widget behaviour, test cards, webhook receipt for the order - use the implementation checklist in the payment method guide you followed.

<!--
TODO (PTTD-824)

1. RESOLVED 2026-09-08 per PO: anchor format confirmed - articles are stripped from operation-
   summary anchors (e.g. #create-subscription-plan, matching master-page precedent). Historical
   inventory: (create-a-subscription-plan, create-a-subscription, create-subscription-usage, change-
   a-subscription-plan).

2. Spec discrepancy: Subscription-Plan-Phase-Creation.yaml marks phase amount + currency as
   required, but the official usage-item examples (Req/Res-Subscription-Plan-Usage-Base.yaml) omit
   them on usage-only phases - the flexible variation's phase follows the shipped examples while the
   static variations carry amount + currency. Same additive-vs-total question as fixed.md: static
   phase amount 7500 = 5 x item amount 1500. Confirm both with the API team.

3. Cross-reference: PO answer on mid-cycle seat changes (subscription-plans.md per-seat section)
   documents usage reporting for seat-count changes - the dynamic path implements it; the static
   paths switch variations via change-plan (no quantity-update parameter exists in the API, mall-
   verified). Reconcile if a quantity parameter ever ships.

4. Plan-change deep dive deferred to the upcoming manage/ category - add cross-links here when those
   pages land.
-->

---

## What's next

- [:Repayment: Build a fixed-rate subscription](/docs/guides/merchant/billing-subscriptions/api/fixed 'Charge a fixed recurring amount - the simplest subscription flow')
- [:LimitHigh: Build a usage-based subscription](/docs/guides/merchant/billing-subscriptions/api/usage-based 'Meter usage, report it via the API, and settle charges at cycle end')
- [:Voucher: Build a subscription with trials and introductory pricing](/docs/guides/merchant/billing-subscriptions/api/trials 'Offer free trials and promotional pricing phases')
- [:Webhook: Track subscriptions with webhooks](/docs/guides/merchant/billing-subscriptions/api/webhooks 'Receive real-time events when subscription states change')