# Billing portal

URL: /systems/finance/billing-portal · Id: `capability:billing-portal`

> Billing portal lets each Customer replace a card, fix billing details, download receipts, change plans and cancel, on the Startup’s own Domain.

Billing portal lets each [Customer](/business/nouns/customer) replace a card, fix billing details, download receipts, change plans and cancel, on the [Startup](/business/nouns/startup)’s own [Domain](/business/nouns/domain). It is the billing section of the [Portal](/business/nouns/portal), and nothing in it waits on the Startup.

## Contract
The section lives at `/portal/billing`, under the Startup’s [Brand](/business/nouns/brand). Stripe’s embedded form collects card details on that page, so no step sends the Customer to another domain.

| Customer need | What the Customer sees | Call |
| --- | --- | --- |
| Replace a card | Saved [Payment Sources](/business/nouns/payment-source), the default marked, and a form to add one | [PaymentSource.add](/business/nouns/payment-source/add) or [`PaymentSource.remove`](/business/nouns/payment-source/remove) |
| Fix billing details | Billing email, address and tax ids, printed on every new [Invoice](/business/nouns/invoice) | `Customer.billingEmail`, `Customer.billingAddress` and `Customer.taxIds`, set through [`Customer.update`](/business/nouns/customer/update) |
| Download a receipt | Every Invoice and receipt as a PDF that names the seller of record | `Invoice.pdf` |
| Pay a failed charge | The unpaid Invoice and one button that pays it with the new card | [Invoice.collect](/business/nouns/invoice/collect) |
| Switch plans | Each [Offer](/business/nouns/offer) the Customer can move to, and today’s prorated amount before confirming | [Subscription.switch](/business/nouns/subscription/switch) |
| Add or remove units | Units held against units in use, and the prorated amount | [Subscription.resize](/business/nouns/subscription/resize) |
| Pause | The date billing resumes | [Subscription.pause](/business/nouns/subscription/pause) |
| Cancel | One confirm; access lasts until the period closes | [Subscription.unsubscribe](/business/nouns/subscription/unsubscribe) |
| Undo a cancel | A button that keeps the [Subscription](/business/nouns/subscription) until the period closes | [Subscription.keep](/business/nouns/subscription/keep) |

| Rule | Detail |
| --- | --- |
| Preview first | A plan or unit change runs `Subscription.switch` or `Subscription.resize` as `try`, shows the prorated charge, then runs as `do` on confirm; see [Modes](/business/nouns/mode). |
| Exits never wait | `Subscription.unsubscribe`, a move to a cheaper Offer and `Subscription.keep` run at once, with no [Limit](/business/nouns/limit), [Approval](/business/nouns/approval) or delay window. |
| Cancel parity | `Subscription.unsubscribe` takes no more steps than buying did, as the renewals [floor row](/systems/legal-and-corporate/floor/renewals) requires. |
| Failed payments | Every [Dunning](/workflows/processes/billing/dunning) message links here, signed in, at the unpaid Invoice. |
| Own records only | `PaymentSource.list` and every view here return only the Customer’s own records; a Payment Source never moves to a sibling Startup. |
| Developer tools | A keyed Offer adds `Portal.tools`: keys, usage against each Limit, the request log, [Watches](/business/nouns/watch), the version pin and the test twin of each key. |

## Verbs
| Verb | Object | Event |
| --- | --- | --- |
| [PaymentSource.add](/business/nouns/payment-source/add) | [Payment Source](/business/nouns/payment-source) | [`PaymentSource.added`](/business/events/PaymentSource.added) |
| [PaymentSource.remove](/business/nouns/payment-source/remove) | [Payment Source](/business/nouns/payment-source) | [`PaymentSource.removed`](/business/events/PaymentSource.removed) |
| [Customer.update](/business/nouns/customer/update) | [Customer](/business/nouns/customer) | `Customer.updated` |
| [Subscription.switch](/business/nouns/subscription/switch) | [Subscription](/business/nouns/subscription) | [`Subscription.switched`](/business/events/Subscription.switched) |
| [Subscription.resize](/business/nouns/subscription/resize) | [Subscription](/business/nouns/subscription) | [`Subscription.resized`](/business/events/Subscription.resized) |
| [Subscription.pause](/business/nouns/subscription/pause) | [Subscription](/business/nouns/subscription) | [`Subscription.paused`](/business/events/Subscription.paused) |
| [Subscription.unsubscribe](/business/nouns/subscription/unsubscribe) | [Subscription](/business/nouns/subscription) | [`Subscription.unsubscribed`](/business/events/Subscription.unsubscribed) |
| [Subscription.keep](/business/nouns/subscription/keep) | [Subscription](/business/nouns/subscription) | [`Subscription.kept`](/business/events/Subscription.kept) |
| [Invoice.collect](/business/nouns/invoice/collect) | [Invoice](/business/nouns/invoice) | [`Invoice.collected`](/business/events/Invoice.collected) |

## Interfaces
- [API](/code/api/reference)
- [SDK](/code/sdk/reference)
- [CLI](/code/cli/reference)
- [MCP](/code/mcp/reference)
- Webhooks

## Example
A clinic’s renewal on shiftnote.app fails on an expired card. The [Dunning](/workflows/processes/billing/dunning) email opens `shiftnote.app/portal/billing` at the unpaid Invoice, and the office manager adds a new default card, which pays it at once. She then lowers the plan from 12 members to 9 and sees that the lower price starts at the next renewal.