Move a customer between plans
Move-plan preflight, then apply, one customer or in bulk.
Goal
Move Acme Corp from its current package to growth at its next renewal boundary, without an unplanned invoice.
A move is two writes: updateEndDate on the current contract at the cutover, then a successor contract on the target package with a RENEWAL transition from the old one. Balances carry over by each grant's rollover_fraction. See Contracts.
Preview
metro contract move-plan preflight --to=growth --ingest-alias=acme-corp \
--cutover-mode=renewal-boundary --sandbox --out=move.jsonThe cutover is one of:
| Flag | Cutover |
|---|---|
--cutover-mode=renewal-boundary (default) | The next monthly boundary after the last paid plan invoice, or the contract start |
--cutover-mode=next-hour | The next UTC hour |
--cutover-mode=current-hour | The current UTC hour. May finalize the plan fee immediately. |
--cutover=<ISO> | An exact UTC hour |
Pass --from=<alias> to assert which package the customer is on now. Preflight then stops if the current package doesn't carry that alias.
metro contract move-plan preflight (sandbox, key sha256:80f0714120b8) - read-only, nothing written to Metronome.
contract 00000000-0000-4000-8000-000000000002.ending_before: null -> "2027-01-01T00:00:00.000Z"
successor contract: null -> {"package_id":"00000000-0000-4000-8000-000000000104","alias":"growth","starting_at":"2027-01-01T00:00:00.000Z","transition":"RENEWAL"}
Steps: POST /v1/contracts/updateEndDate ; POST /v1/contracts/create
Payload hash: sha256:...Preflight needs one active contract to move from: the single open-ended one, or the only active one. An active trial qualifies with warning MTR-MOVE-TRIAL-001, because apply truncates it at the cutover. An expired customer doesn't; create a fresh contract instead. Preflight pins the target package live at the cutover, warns about balances that won't roll over, and warns MTR-TIME-002 when the cutover isn't in the future.
Credits on the new plan
move-plan no longer takes a target credit amount. Grant credits on the successor afterwards with metro ledger-adjustment preflight --grant=credit, through the same ladder. See Credits and commits.
Confirm
Show the human the cutover hour, the old contract's new end, the target package, any rollover warning and the hash. Then dry-run:
metro contract move-plan apply --file move.json --sandbox --confirm-hash sha256:...Apply
metro contract move-plan apply --file move.json --sandbox --confirm-hash sha256:... \
--apply --confirm-live-apply=I_UNDERSTAND_THIS_WILL_WRITE_LIVE_METRONOME_SANDBOXIn bulk
Write a roster: one customer ID per line. Lines starting with #, and a customer_id header, are skipped.
metro contract move-plan bulk-preflight --to=growth --roster-file=roster.csv \
--cutover-mode=renewal-boundary --sandbox --out-dir=plans/bulk-movePlanned 40/40 moves (0 failed). Manifest: plans/bulk-move/manifest.json
Manifest hash: sha256:...
Canary: metro contract move-plan bulk-apply --file plans/bulk-move/manifest.json --sandbox --confirm-hash sha256:... --limit=1 --apply --confirm-live-apply=I_UNDERSTAND_THIS_WILL_WRITE_LIVE_METRONOME_SANDBOXRun the canary line first: --limit=1 moves one customer. Read it back, then run the same command without --limit. bulk-apply stops at the first failure, waits --delay-ms (default 300) between moves, and refuses a manifest edited after preflight.
Readback
Applied (sandbox).
Read-back (expected vs actual):
ok predecessor.ending_before: expected "2027-01-01T00:00:00.000Z" actual "2027-01-01T00:00:00.000Z"
ok successor exists: expected true actual true
ok successor.starting_at: expected "2027-01-01T00:00:00.000Z" actual "2027-01-01T00:00:00.000Z"
ok successor.package_id: expected "00000000-0000-4000-8000-000000000104" actual "00000000-0000-4000-8000-000000000104"
ok alias growth resolves to successor package at cutover: expected "00000000-0000-4000-8000-000000000104" actual "00000000-0000-4000-8000-000000000104"Illustrative IDs; the check names are exact. Any FAIL exits 3. Confirm from the customer's side with metro ops who "Acme Corp" --sandbox.