---
title: "Plan changes and proration"
url: /commerce/plan-changes
---

# Plan changes and proration

> What a Customer pays when its plan or seats change inside a period: up at once and prorated, down at renewal, previewed to the cent first.

A Customer changes plan or seats at any time, and proration settles the part of the period already paid, to the cent the preview showed.

```mermaid title="A change of plan, previewed and settled"
stateDiagram-v2
  [*] --> previewed: a plan is picked
  previewed --> applied: up: now
  previewed --> scheduled: down
  scheduled --> invoiced: renewal
  applied --> invoiced: prorated
```

## One period, day by day
Follow one Customer through a period. The portal, the API, a Customer's agent and your operator all make these moves the same way.

| Day | The Customer | The bill |
| --- | --- | --- |
| 1 | Starts the period on its plan | The plan for the whole period |
| 16 | Previews a move up, with more seats | Nothing yet: each line in words, the new allowance less what the period used, valid for 30 minutes |
| 16, confirmed | Sends the change that names the preview | Exactly the preview's total: the old plan's unused part credited, the new plan's rest charged, by the second |
| 20 | Adds one more seat | That seat, prorated as a move up |
| 25 | Removes a seat, or picks a smaller plan | Nothing now: the seat stays paid and usable, and the move down waits for renewal |
| 26 | Tries a second change | 409 [`change_scheduled`](/reference.md#change_scheduled), until the waiting move down is cancelled |
| Renewal | Starts the next period | The seat released, the smaller plan applied, and the new period charged |

Usage metered before a move up bills at the old plan's prices. `proration.bill: 'now'` charges the difference on an invoice that day; `'next'` adds its lines to the next invoice.

## Up, down and sideways
Every rule reads the [Offer](/reference/platform.md#offer). Find the change, then read across.

| The change | It applies | It bills |
| --- | --- | --- |
| A move up, `proration.up: 'now'` | At once | The unused part of the old plan credited, the rest of the new one charged |
| A move down, `proration.down: 'renewal'` | At the period's end | Nothing; with `'now'`, the unused part becomes credit on the Customer's balance, never cash |
| A seat added | At once | Prorated, as a move up |
| A seat removed | At renewal | Nothing more; the seat stays usable until then |
| Monthly to yearly | At once | The month's unused part credited |
| Yearly to monthly | At renewal | The new period at the monthly price |
| Any change during a trial | To the plan the trial converts to | Nothing until the trial ends |
| A move up while past due | Once the failed balance is paid | A move down is always allowed |
| Leaving a grandfathered price | At once, and the preview says so first | The price `changes.grandfather` kept is given up |

An agent changes a plan only inside a mandate that covers the new price; otherwise it gets 403 [`mandate_exceeded`](/reference.md#mandate_exceeded).

## Previewed to the cent
The preview is the contract for the change. The change that names it charges its total and nothing else.

### Example
The sales product moves a Customer up at once, bills the difference that day, and keeps a move down for renewal.

```ts title="definition.ts"
import { define } from 'apis.dev'

/** Deals for each Customer, on a free plan or a Team plan by the seat, changed at any time. */
export default define({
  name: 'pipeline', domain: 'pipeline.apis.dev',
  Company: { name: 'string', domain: 'url!#', deals: '<-Deal.company' },
  Deal: {
    title: 'string', amount: 'decimal(12,2)', company: '->Company.deals',
    stage: { open: [], qualify: 'opened', win: 'qualified', lose: ['opened', 'qualified'] },
  },
  offer: {
    Free: { month: 0, seats: 1, includes: { 'Deal.open': 25 } },
    Team: { month: 49, seats: 5, seat: 9, includes: { 'Deal.open': 1000 } },
    'Deal.open': 0.001,
    proration: { up: 'now', down: 'renewal', bill: 'now' },
  },
})
```

A Customer on Free previews Team with seven seats on 2026-10-16, halfway through its period.

```http title="Response"
HTTP/1.1 200 OK
Content-Type: application/json
X-Cost: price=0, currency="USD"
X-Request-Id: req_e6a03b9d27c14f8805b2

{
  "id": "prv_5",
  "from": "Free",
  "to": { "plan": "Team", "seats": 7 },
  "applies": "2026-10-16T00:00:00Z",
  "lines": [
    { "for": "Team, 16 of 31 days", "amount": 25.29 },
    { "for": "2 seats past 5, 16 of 31 days", "amount": 9.29 }
  ],
  "total": 34.58,
  "currency": "USD",
  "bill": "now",
  "next": { "at": "2026-11-01T00:00:00Z", "amount": 67 },
  "includes": { "Deal.open": 975 },
  "expires": "2026-10-16T00:30:00Z"
}
```

The Customer's admin sends `POST https://pipeline.apis.dev/subscription/preview`; your operator calls `Subscription.preview` on the [Operator plane](/trust/planes.md). `POST /subscription/change` with `prv_5` charges exactly the total.

### The lines a change leaves
Stripe carries every line on the invoice and the receipt, in words a finance team reads.

Each line names the plan, the days, the seats and the amount. Every line is also an Event on your sales record, so revenue and the bill agree.

A plan change is `previewed`, then `applied` or `scheduled`, or `expired` after 30 minutes, or `cancelled`. Each proration line is `pending`, then `invoiced`, then `paid` by Stripe or `credited` to the balance. A removed seat stays `paid` until renewal, then is `released`.