# Plans and usage

Source: https://solidhooks.com/docs/plans

> How SolidHooks bills per organisation, what counts as a delivery, what each plan includes, and how to read and change your plan over the API.

Billing is per organisation. A project never carries billing state. The billable unit is the delivery: one event routed to one connection.

For prices, see [solidhooks.com/#pricing](https://solidhooks.com/#pricing). Prices are not listed here because they live in the plan catalogue and can change.

## What counts as a delivery

| Case                                               | Billed?                              |
| -------------------------------------------------- | ------------------------------------ |
| One event routed to one connection                 | One delivery                         |
| One event fanned out to N connections              | N deliveries                         |
| Automatic retries of a delivery                    | No. Retries reuse the same delivery. |
| A replay of an event                               | Yes. It creates new deliveries.      |
| A duplicate (idempotent) request                   | No. It is never counted or blocked.  |
| A request that fails verification (rejected event) | No. It creates no delivery.          |

Manually retrying one delivery with `POST /v1/deliveries/{id}/retry` is free too, since no new delivery is created. See [Retries](https://solidhooks.com/docs/retries).

## Plans

Four tiers exist: Hobby (free, the default), Launch and Scale (self-serve), and Enterprise (sales-led). These are the seeded defaults for each entitlement. `null` means unlimited or custom.

| Entitlement                   | Hobby    | Launch   | Scale     | Enterprise |
| ----------------------------- | -------- | -------- | --------- | ---------- |
| Included deliveries per month | 10,000   | 50,000   | 1,000,000 | `null`     |
| Event retention               | 3 days   | 7 days   | 30 days   | 90 days    |
| Seats                         | 1        | 5        | `null`    | `null`     |
| Delivery attempts             | 3        | 8        | 8         | 8          |
| Retry window                  | 1 hour   | 36 hours | 36 hours  | 36 hours   |
| Deliveries per second         | 10       | 50       | 200       | `null`     |
| Delivery burst                | 20       | 100      | 400       | `null`     |
| Connectors                    | 0        | 1        | 5         | `null`     |
| Replicas per connector        | off      | 2        | 5         | `null`     |
| Overage                       | hard cap | metered  | metered   | negotiated |

> **Read live values from the API**
>
> The table shows seeded defaults. The database is the source of truth, and individual organisations can have overrides. Call `GET /v1/billing/plan` for the entitlements that apply to your organisation, and `GET /v1/billing/plans` for the catalogue.

A few of these are worth knowing:

* **Attempts and retry window** bound the retry schedule. Whichever runs out first ends it. Both are also clamped by retention.
* **Delivery pacing** never produces an error. A delivery over the rate waits and sends automatically; see [Rate limits](https://solidhooks.com/docs/rate-limits).
* **Connectors** gate [private delivery](https://solidhooks.com/docs/private-delivery). A plan with none cannot create one.

### Read your plan and the catalogue

```bash
curl -s "$API/v1/billing/plan" \
  -H "authorization: Bearer $SDHK_KEY"
```

The response includes your active plan, its `currency` and `priceCents`, and the effective entitlements.

```bash
curl -s "$API/v1/billing/plans" \
  -H "authorization: Bearer $SDHK_KEY"
```

The catalogue lists plans in display order. Each plan carries `currency` and `priceCents` in your organisation's billing currency, plus a `prices` map of every supported currency for comparison. `priceCents: null` means custom (Enterprise). Amounts are minor units.

### Currency

Prices are offered in GBP, USD and EUR. The owner's country at sign-up sets the organisation's currency, and it is fixed once the organisation first subscribes. `GET /v1/countries` lists the countries available for sign-up with their billing currency; it needs no authentication.

## Usage

```bash
curl -s "$API/v1/billing/usage" \
  -H "authorization: Bearer $SDHK_KEY"
```

You get deliveries used against included, plus `resetAt`. On plans with overage it also returns accrued pay-as-you-go units and an estimated charge (`currency`, `pricePer100kCents`, `estimatedCents`) in your billing currency.

The usage window is the subscription's billing period for subscribed organisations, resetting at renewal. For everyone else it is the UTC calendar month.

## Hard cap or overage

On Hobby, once the included deliveries are used, ingest rejects further requests until the window resets or you upgrade:

```json
{
  "code": "QUOTA_EXCEEDED",
  "message": "Monthly delivery quota exceeded (10000/10000). ...",
  "data": { "limit": 10000, "current": 10000, "resetAt": "2026-11-01T00:00:00.000Z" }
}
```

The response is HTTP 429. Replays are gated by the same check, because they create billable deliveries.

Paid plans with overage enabled never hard-cap. Deliveries beyond the included quota are metered and billed as overage, and the plan's overage rate is in the catalogue.

SolidHooks emails organisation owners and admins when usage reaches 80% of the included quota and again at 100%. Each alert is sent once per usage window.

## Change your plan

The first subscription is a browser step. Stripe Checkout runs in the dashboard at `https://dash.solidhooks.com/settings/plan`, and only an organisation owner or admin can start it. If you are an agent, ask your human to do it, then confirm with `GET /v1/billing/plan`.

After that, everything is API-driven:

| Endpoint                                   | What it does                                                                                                                                                |
| ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `POST /v1/billing/preview-plan-change`     | Prices an immediate change to `{ planKey }` without making it.                                                                                              |
| `POST /v1/billing/change-plan`             | Changes between paid tiers. Upgrades apply immediately and invoice the prorated difference. Send `scheduleAtPeriodEnd: true` for a downgrade at period end. |
| `POST /v1/billing/revert-scheduled-change` | Cancels a pending scheduled change.                                                                                                                         |
| `POST /v1/billing/cancel`                  | Cancels at the end of the current period.                                                                                                                   |
| `POST /v1/billing/resume`                  | Undoes a scheduled cancellation.                                                                                                                            |
| `POST /v1/billing/refresh`                 | Drops the cached plan and returns the freshly resolved one.                                                                                                 |

```bash
curl -s "$API/v1/billing/change-plan" \
  -H "authorization: Bearer $SDHK_KEY" \
  -H 'content-type: application/json' \
  -d '{ "planKey": "scale" }'
```

`preview-plan-change` returns `available: false` with a 200 when Stripe cannot price the change, and while a scheduled change is pending. Check `available` before showing a figure, and call `revert-scheduled-change` first if you need a quote. Hobby is a cancellation, not a plan change, and Enterprise is sales-led.

### Permissions

The mutations (`change-plan`, `preview-plan-change`, `revert-scheduled-change`, `cancel`, `resume`) need a key scoped `billing: read & write`, or an owner or admin session. Anything less gets `INSUFFICIENT_API_KEY_PERMISSIONS`. The reads and `refresh` work with any key. See [API keys](https://solidhooks.com/docs/api-keys).

## Next steps

* [Rate limits](https://solidhooks.com/docs/rate-limits) — ingest and delivery pacing
* [Retries](https://solidhooks.com/docs/retries) — how attempts and the retry window apply
* [Errors](https://solidhooks.com/docs/errors) — `QUOTA_EXCEEDED` and the other codes