Skip to content

Plan changes and proration

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.

A change of plan, previewed and settled

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.

DayThe CustomerThe bill
1Starts the period on its planThe plan for the whole period
16Previews a move up, with more seatsNothing yet: each line in words, the new allowance less what the period used, valid for 30 minutes
16, confirmedSends the change that names the previewExactly the preview's total: the old plan's unused part credited, the new plan's rest charged, by the second
20Adds one more seatThat seat, prorated as a move up
25Removes a seat, or picks a smaller planNothing now: the seat stays paid and usable, and the move down waits for renewal
26Tries a second change409 change_scheduled, until the waiting move down is cancelled
RenewalStarts the next periodThe 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. Find the change, then read across.

The changeIt appliesIt bills
A move up, proration.up: 'now'At onceThe unused part of the old plan credited, the rest of the new one charged
A move down, proration.down: 'renewal'At the period's endNothing; with 'now', the unused part becomes credit on the Customer's balance, never cash
A seat addedAt onceProrated, as a move up
A seat removedAt renewalNothing more; the seat stays usable until then
Monthly to yearlyAt onceThe month's unused part credited
Yearly to monthlyAt renewalThe new period at the monthly price
Any change during a trialTo the plan the trial converts toNothing until the trial ends
A move up while past dueOnce the failed balance is paidA move down is always allowed
Leaving a grandfathered priceAt once, and the preview says so firstThe 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.

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.

definition.ts
17 lines
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.

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