# Billing

Flatstack has three plans, each a flat monthly price in GBP, VAT exclusive. Usage never adds a charge: going over a limit slows things down or blocks them instead.

## Signing up

There are no free accounts and no trials. After you create a login, choose a plan and pay through Stripe Checkout. Your account exists once Stripe confirms the payment. Until then the login can only see the plan picker and checkout.

| Request | What it does |
| --- | --- |
| `GET /v1/billing/plans` | Lists the plans, prices in pence and limits. |
| `POST /v1/billing/checkout` | `{ "planId" }`. Returns `{ "url" }` for Stripe Checkout. |

## Changing plan

Only account owners can change billing.

| Request | What it does |
| --- | --- |
| `GET /v1/billing` | Current plan, any plan change scheduled for the end of the period, and the period end. |
| `POST /v1/billing/plan` | `{ "planId" }`. Upgrades apply now and you pay the difference for the rest of the period. Downgrades apply at the end of the period. |
| `POST /v1/billing/portal` | Returns `{ "url" }` for the Stripe Customer Portal: card, invoices, tax details and cancellation. |

A downgrade is refused with `409 downgrade_blocked` while you use more than the smaller plan allows. `details.blockers` lists each limit you're over, with how many you have and how many the plan allows. Remove those, then try again.

```json
{
  "error": {
    "code": "downgrade_blocked",
    "message": "Current usage is over the smaller plan's limits. Remove what's listed in details, then downgrade.",
    "details": {
      "targetPlanId": "hobby",
      "blockers": [{ "limit": "projects", "current": 2, "allowed": 1 }]
    }
  }
}
```

## Add-ons

Add-ons are only ever bought by an owner, on purpose. Nothing is added automatically.

| Add-on | Price | What it adds | How long |
| --- | --- | --- | --- |
| Extra project | £5/month | One more project. Usage limits stay pooled across the account. | Until removed |
| Always-on Postgres | £15/month per project | Postgres never sleeps for that project, and it gets 500 CU-h of its own. | Until removed |
| Extra seat | £5/month | One more team member. | Until removed |
| Compute pack | £10 | 200 CU-h. | This billing period |
| Email pack | £5 | 25,000 emails. | This billing period |
| AI credit | £10, £25 or £50 | That much prepaid model usage through the AI gateway. | Until used |

Always-on Postgres and the compute pack need a plan with Postgres (Pro or Studio).

| Request | What it does |
| --- | --- |
| `GET /v1/billing/limits` | Your limits now: plan plus add-ons, with per-project extras. |
| `GET /v1/billing/add-ons` | Add-ons you can buy, the ones you hold, and your AI credit balance. |
| `POST /v1/billing/add-ons` | `{ "addOnId", "quantity"?, "projectId"? }`. Monthly add-ons start now and the rest of this month is charged straight away. Packs and AI credit return a Stripe Checkout `url` and apply once paid. |
| `DELETE /v1/billing/add-ons/{purchaseId}` | Removes a monthly add-on now, with no refund for the rest of the month. |

You can't remove an add-on you're using, such as an extra seat while every seat is filled. The request returns `409 add_on_in_use` with `details.blockers`, as a blocked downgrade does.

## If a payment fails

Stripe tries the card again over 7 days. Nothing changes while it does, apart from an email to the account's owners and admins.

| Stage | When | Your projects | The dashboard and API |
| --- | --- | --- | --- |
| Payment failed | First failed payment | Work normally. | Everything works. |
| Locked | The last retry fails | Read-only: files, pages and reads work, writes return `402 account_locked`. Sign-in (`/auth/*`) still works. | Reads work. Writes return `402 account_locked`. |
| Suspended | 30 days locked | Not served: `403 account_suspended`. | Reads and export work. Writes return `403 account_suspended`. |
| Deleted | 30 days suspended | Gone. | Gone. |

Pay the invoice from the Customer Portal (`POST /v1/billing/portal`) at any point before deletion and the account is back as it was within a minute. Nothing is deleted while you're locked, and nothing is deleted without two warning emails: 7 days and 1 day before. If either warning is late, deletion moves back so there's always at least a day after the final one.

## Cancelling

Cancel from the Customer Portal. The account works normally until the end of the period you've paid for. It then becomes read-only for 30 days, with writes returning `402 account_cancelled`, so you can export everything. After the same two warning emails, the account and all its data are deleted.

## While an account is read-only

Reads keep working everywhere, so the dashboard can show what's happening and you can export. These writes still work too:

| Request | Why |
| --- | --- |
| `POST /v1/billing/portal` | Paying is how you get out. |
| `POST /v1/tokens`, `DELETE /v1/tokens/{tokenId}` | The CLI and MCP export with a token; revoking access is always allowed. |
| `DELETE /v1/members/{memberId}`, `DELETE /v1/members/invites/{inviteId}`, `POST /v1/members/leave` | Taking access away is always allowed. |
| `PUT /v1/notification-preferences` | Your email settings. |

Every other write returns the account's error:

```json
{
  "error": {
    "code": "account_locked",
    "message": "Payment failed, so this project is read-only. Update the card in billing to restore writes.",
    "docsUrl": "https://flatstack.dev/docs/errors/account_locked"
  }
}
```

`GET /v1/billing` shows where the account is: `accountStatus` (`active`, `past_due`, `past_due_locked`, `suspended` or `cancelled`), and `lockedAt`, `suspendedAt`, `cancelledAt` and `deleteAfter` as ISO timestamps or `null`.