# Billing

> Part of the Cowliss docs. Markdown for every page: append `index.md` to its URL. Site map: /llms.txt

Cowliss has two plans, **Free** and **Plus**. Every feature (automations, custom sending domains, segments) is on both from the first event: a plan decides only how much usage is included each month and how many apps and sending domains an org may hold. Spend past the included usage draws a prepaid wallet, and nothing is ever billed in arrears.

## Plans

{/* Rendered from the plan constants in packages/shared, like the rates below. */}
<table>
	<thead>
		<tr>
			<th></th>
			<th>Free</th>
			<th>Plus</th>
		</tr>
	</thead>
	<tbody>
		{plans.map(([label, free, plus]) => (
			<tr key={label}>
				<td>{label}</td>
				<td>{free}</td>
				<td>{plus}</td>
			</tr>
		))}
	</tbody>
</table>

- **Plus** is a monthly subscription. An org admin starts it from **Settings → Billing** in the dashboard and pays by card; the CLI and MCP server never start one. The Plus fee never touches the wallet.
- **Cancelling** happens on the same page, at any time. Plus stays on until the end of the month already paid for, then the org is on Free. A failed renewal does not end it straight away: Plus stays on while the card is retried, and ends only once the payment is given up on.
- **Limits** are checked when you create an app or a sending domain, and only then. An archived app frees its slot; a sending domain holds one from the moment you add it, verified or not, until you delete it. A create past the limit answers 403 `plan_limit`, its message naming the limit and the way up.
- **Over the limit after cancelling**: an org that left Plus with more apps or sending domains than Free allows keeps every one of them, and they keep working. Creating another is refused until the org is back under the limit.

### Organizations a partner runs

An organization a [partner](/guides/partners/) created on a customer's behalf is billed by that partner, not here: the partner sets the plan over the API (its choice wins over any subscription), may let usage run past the included amount (counted as `overageMicros` on the billing summary, never refused and never charged to a wallet), and the top-up and subscription routes answer **409** `partner_managed`. Included usage and the limits per plan are the same.

## The model

- **Included usage**: every org gets its plan's included usage (**{dollars(FREE_ALLOWANCE_MICROS)}** on Free, **{dollars(PLUS_ALLOWANCE_MICROS)}** on Plus) each UTC calendar month, spent before the wallet. Unused included usage does not roll over.
- **Wallet**: spend past the included usage draws a prepaid balance, funded by fixed-amount one-time payments (**{TOPUP_PRESETS_MICROS.map(dollars).join(" / ")}**) through Stripe Checkout. Wallet credit that is not spent **carries over** month to month and never expires. When a month closes, what it spent past its included usage leaves the wallet for good; a top-up raises the balance, and a refunded payment or a lost chargeback lowers it.
- **Remaining credit** = `max(0, included usage + balance − spend)`. At zero, the platform **hard-stops**: ingestion answers 429 `over_quota` with `Retry-After`, and email/webhook sends record `skipped_quota` in the delivery log. Nothing is ever billed in arrears: there is no overage invoice, because nothing can run past zero.
- **Warning**: from {Math.round(QUOTA_WARN_THRESHOLD * 100)}% of credit used, the dashboard shows a top-up banner, so a rejection never surprises you.
- **Refunds and chargebacks**: a refunded payment or a lost chargeback takes its amount back out of the wallet, as a separate line in the transactions table beside the payment it answers. That can leave the balance below zero; once nothing is left, it is the same hard stop, and a top-up brings it back up and starts sending again. Nothing is invoiced, and no usage already delivered is clawed back.

## What usage costs

Usage is one weighted spend across four metered actions, computed in integer micro-dollars (1,000,000 per dollar: no float ever touches money):

{/* Rendered from USAGE_PRICES so the published rate can never drift from
    the one the meter charges. */}
<table>
	<thead>
		<tr>
			<th>Action</th>
			<th>Rate</th>
		</tr>
	</thead>
	<tbody>
		{rates.map(([label, key]) => (
			<tr key={key}>
				<td>{label}</td>
				<td>{ratePerUnit(USAGE_PRICES[key])}</td>
			</tr>
		))}
	</tbody>
</table>

What counts, and what does not:

- **Events** are customer-written rows, including batch backfill. System events Cowliss writes itself (`system.segment_entered`, `system.segment_exited`, `system.consent_revoked`, delivery feedback) are never metered.
- **Sends** count when settled; `failed`, skipped (`skipped_*`), and dry-run (`would_*`) deliveries are excluded by construction.
- **Automation runs** count real instances, and each `restart` step counts as one run; dry runs cost nothing.
- A price of 0 would make an action free without removing it from the meter: the catalog is one map in `packages/shared`, tweakable in one place.

Spend is **counted, never incremented**: every number is a count of records that already exist (your events, the delivery log, automation executions), so the meter can never drift from the thing it bills for, and a replayed webhook or a re-run backfill cannot double-charge.

## Topping up

```text
POST /v1/billing/topups { amountMicros: 10_000_000 }   (a preset, not free-form)
        → 201 { checkoutUrl }                          (Stripe-hosted Checkout)
… pay on Stripe …
Stripe webhook checkout.session.completed → wallet credited, ledger row completed
```

The wallet is credited **by the webhook and nowhere else**: the topup endpoint answers only a checkout URL, and the ledger row it writes stays `pending` until Stripe confirms. The `wallet_topups` ledger is the audit trail of every top-up and its state.

:::note[Scope of Stripe]
Stripe is payment processing only. Cowliss is not a merchant of record, computes no tax, and issues no invoices.
:::

## Reading usage

- Dashboard: **Settings → Billing** shows balance, this month's spend against included usage, remaining credit, and top-up history.
- API: `GET /v1/billing` returns the included usage (`allowanceMicros`), wallet balance, period bounds, spend, remaining credit with `warn`/`over` flags, the usage counts, and the price catalog that weighted them.
- CLI: `cow billing` prints the same summary.

## The hard stop is the same everywhere

One `creditMicros` function in `packages/shared` computes remaining credit for the API's 429 on ingestion, the worker's `skipped_quota` send gate, the billing page, and the dashboard banner: four surfaces, one arithmetic. A runaway automation hits the same wall as a runaway import: no bug can burn unbounded volume or credit.
