# Subscriptions and billing

## Quote, then buy

`POST /checkout/quote` prices a purchase and writes nothing. It uses the same resolution, placement, discount and tax rules as `POST /checkout`, so the total you quote is the total you are invoiced. A code is validated but not redeemed.

**curl**

```bash
# same body as /checkout, minus name and slug: writes nothing
curl -sS -X POST "$ZAROZ_API/checkout/quote" -H "$AUTH" -H "Content-Type: application/json" -d '{
  "product_id": "<product uuid>", "plan_id": 3, "region_id": "<region uuid>",
  "organization_id": "<org uuid>", "discount_code": "WELCOME10"
}'
# then, to buy: the same body plus "name" and "slug" on POST /checkout
```

**TypeScript**

```ts
const body = { product_id: PRODUCT, plan_id: 3, region_id: REGION, organization_id: ORG, discount_code: "WELCOME10" };
const quote = await call("POST", "/checkout/quote", body);
console.log(quote.body.total_cents, quote.body.due_now_cents, quote.body.cadence_label);
const bought = await call("POST", "/checkout", { ...body, name: "My API", slug: "my-api" });
console.log(bought.body.subscription.id, bought.body.invoice.id);
```

**Python**

```python
body = {"product_id": PRODUCT, "plan_id": 3, "region_id": REGION, "organization_id": ORG, "discount_code": "WELCOME10"}
_, quote = call("POST", "/checkout/quote", body)
print(quote["total_cents"], quote["due_now_cents"], quote["cadence_label"])
_, bought = call("POST", "/checkout", {**body, "name": "My API", "slug": "my-api"})
print(bought["subscription"]["id"], bought["invoice"]["id"])
```


The quote answers `lines`, `subtotal_cents`, `tax`, `total_cents` (per billing cycle), `due_now_cents` (what you pay today, which is not always the total: a setup fee, a prepaid term or a prorated first period change it), `cadence_label` and the `resources` that were priced. If you name public ports, `metered_estimate` shows what holding them can cost.

`POST /checkout` creates a **pending subscription** and the **establishing invoice**. Pay the invoice by the existing invoice payment routes; when it is paid the service is provisioned. You need Owner or Admin on the organization named by `organization_id`; without it the purchase bills your personal organization.

## Renewal and cancellation

A subscription renews at the end of each period. Two different things you can ask for:

- `PUT /subscriptions/{id}/renewal {auto_renew}`: stop or resume the **next invoice**. The service keeps running.
- `PUT /subscriptions/{id}/cancellation`: schedule the service itself to end.

**curl**

```bash
curl -sS -X PUT "$ZAROZ_API/subscriptions/<subscription uuid>/renewal" -H "$AUTH" \
  -H "Content-Type: application/json" -d '{"auto_renew": false}'   # stops the NEXT invoice, not the service
```

**TypeScript**

```ts
await call("PUT", `/subscriptions/${SUB}/renewal`, { auto_renew: false });   // not a cancellation
```

**Python**

```python
call("PUT", f"/subscriptions/{SUB}/renewal", {"auto_renew": False})   # not a cancellation
```


Resizing is a quote and a confirm: `POST /subscriptions/{id}/resize/quote`, then `POST /subscriptions/{id}/resize`. A plan change is the same shape under `/resize/plan-change`.

## How Units are billed

A Workload's plan includes a number of units (`included_units`). Units above that are **extra units**.

- An extra unit is billed **per hour while it has a pod**, plus a **minimum charge per start** of 5 minutes. Churn is never free.
- Which units are "included" is decided when a pod is created, by its slot number. A stopped code-controlled unit releases its slot, so the included slots are always the first ones that run.
- A **stopped unit costs nothing**. A **warm** unit is billed like any running unit, because it holds CPU and memory.
- Public ports are billed by the hour they are held.
- Events, webhooks and service principals are included at no charge.

You control the worst case with limits: the policy's `max_units`, your organization's unit limit (default 50), the start rate (default 600 per hour) and a monthly `spend_cap_cents` for extra units. `POST .../units/policy/quote` shows the worst case of a policy before you save it. When a cap or limit is reached, calls that would start a pod are refused with a stable code (see [Units API errors](/guides/units-api/errors)); running units are never stopped by a limit.

A worked example:

| Setup | Calculation | Result |
|---|---|---|
| 1 included unit, 3 warm units kept all month | 3 extra units × 720 h × the extra hourly rate | the maximum the warm buffer can cost in a 30-day month |
| a job that runs 40 s, 100 jobs a day | 100 starts × the 5-minute minimum | billed as 500 minutes of unit time, not 67 |
| a plot stopped between visits | 0 while stopped | pay only while players are in |

The hourly rate is the plan's price; read it from the quote.
