# Plans and billing

<!-- docs-test setup: owner -->

Every plan has every feature: analytics, crawler and AI-bot analytics, journeys, A/B tests, bot protection, Search
Console, the API and MCP. Plans differ only by volume, and data is kept forever on all of them.

| | Free | Starter | Growth | Business |
|---|---|---|---|---|
| Price (monthly / yearly) | $0 | $9 / $90 | $29 / $290 | $99 / $990 |
| Human pageviews / month | 100k | 250k | 1M | 5M |
| Server requests / month | 1M | 5M | 20M | 75M |
| Sites | 5 | 20 | unlimited | unlimited |
| Proxy-mode hostnames | 1 | 3 | 10 | 50 |
| Team members | 2 | 5 | 15 | unlimited |

Months are calendar months in UTC. Paid subscriptions renew on the 1st, so a billing period is the month that usage
is counted in; the first payment covers the rest of the current month, pro rata.

## Above the allowance

On a paid plan nothing is dropped. Usage above the allowance is billed at the end of the period, pro rata:

- **$10 per extra 1M pageviews** (one cent per 1,000),
- **$1 per extra 1M server requests** (one cent per 10,000),
- **$1 per extra proxy hostname per month**, for each month it exists.

Worked examples, for one month:

| Plan | Pageviews | Server requests | Proxy hostnames | Overage |
|---|---|---|---|---|
| Starter | 250,000 | 5,000,000 | 3 | $0.00 |
| Starter | 400,000 | 5,000,000 | 3 | $1.50 |
| Growth | 1,500,000 | 22,000,000 | 10 | $7.00 |
| Business | 5,000,000 | 80,000,000 | 52 | $7.00 |
| Free | 900,000 | 3,000,000 | 1 | $0.00 |

**Free never bills.** The owners get an email at 80% and at 100% of an allowance. Pageviews keep counting past 100%;
past 150% of its server requests, Free stops logging server requests until the month turns. Paid plans get the 80%
and 100% emails too, as a heads-up.

## Your plan and usage

`GET /v1/billing` (scope `billing:read`, which owners and admins have) answers the plan, the subscription's status,
this month's usage, the overage so far in cents, and a projection to the end of the month.

```sh
curl https://api.usherstats.com/v1/billing \
  -H "Authorization: Bearer $US_TOKEN"
```
<!-- expect 200 -->

## Subscribe

Paying is the owner's: it needs `billing:write`, which only owners have (and tokens they make with it). Tokens
restricted to some sites cannot change billing.

`POST /v1/billing/checkout` answers the URL of a Stripe Checkout page for the plan, monthly or yearly. The plan
changes when Stripe confirms the payment, not before.

```sh
curl https://api.usherstats.com/v1/billing/checkout \
  -H "Authorization: Bearer $US_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"plan": "growth", "interval": "month"}'
```
<!-- expect 201; CHECKOUT_URL=.url -->

Until the payment is confirmed there is no subscription to change, so a plan change is refused:

```sh
curl https://api.usherstats.com/v1/billing/plan \
  -H "Authorization: Bearer $US_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"plan": "starter"}'
```
<!-- expect 409 -->

## Change plan, update the card, cancel

`POST /v1/billing/plan` changes the plan of a subscription:

- an **upgrade** applies once the difference for the rest of the period, invoiced immediately, is paid; if the card
  is declined the answer is `402` and the plan stays as it was;
- a **downgrade** applies when the period you paid for ends; until then you keep the larger plan;
- `{"plan": "free"}` **cancels** at the end of the period. Nothing is deleted: the workspace moves to Free and keeps
  its data. Choosing a plan again before the period ends withdraws the cancellation.

Switching between monthly and yearly billing in place is not available yet: cancel, and subscribe again when the
period ends.

`POST /v1/billing/portal` answers a link to Stripe's customer portal, where you update the card, download invoices
and receipts, or cancel at the end of the period. It needs a billing account, which the first subscription creates:

```sh
curl -X POST https://api.usherstats.com/v1/billing/portal \
  -H "Authorization: Bearer $US_TOKEN"
```
<!-- expect 409 -->

## When a payment fails

The owners get an email with a link to pay the invoice, and nothing changes for 7 days. If the invoice is still
unpaid then, the workspace moves to Free (data kept, collection continues within Free's allowances) and the owners
are told. Paying the invoice later restores the plan.

## Stripe's webhook

`POST /v1/billing/webhook` is for Stripe only. It accepts a request only with a valid `Stripe-Signature` made in the
last 5 minutes, and applies each event once:

```sh
curl https://api.usherstats.com/v1/billing/webhook \
  -H "Content-Type: application/json" \
  -d '{"id": "evt_1", "type": "customer.subscription.deleted"}'
```
<!-- expect 400 -->

| Route | Scope |
|---|---|
| `GET /v1/billing` | `billing:read` |
| `POST /v1/billing/checkout` | `billing:write` (not for site-restricted tokens) |
| `POST /v1/billing/portal` | `billing:write` (not for site-restricted tokens) |
| `POST /v1/billing/plan` | `billing:write` (not for site-restricted tokens) |
| `POST /v1/billing/webhook` | none; Stripe's signature |
