# Billing, quota and wallet

CallQA never advances money: a call is only placed when your quota or your prepaid wallet can
cover it.

## Plans

| Plan | Price | Test calls per month | Extra call (from the wallet) | Seats | Phone countries | Concurrent calls |
|---|---|---|---|---|---|---|
| Starter | $99/mo | 100 | $0.75 | 3 | 1 | 2 |
| Growth | $499/mo | 750 | $0.55 | 10 | 5 | 5 |
| Scale | $1,499/mo | 3,000 | $0.40 | unlimited | all | 15 |

Annual billing is 20% cheaper ($79, $399 and $1,199 per month, billed yearly). Every plan
includes scheduled tests, the API, CI/CD webhooks and alerts. Growth adds auto-derived test
coverage, custom test data injection, version tagging and comparison (**Dashboard > Versions
and environments**), and advanced analytics (90 days of history; Starter keeps 7 and 30
days). Scale adds SSO/SAML, custom reporting (**Monitoring > Reports**) and the advanced
scenario builder. `GET /me` returns your plan and its feature flags; `GET /plans` lists the
public plans with their prices.

A feature your plan does not include answers `403` with `code: feature_not_in_plan`,
`required_plan` and `upgrade_url` in the API, and shows a locked card with an **Upgrade**
button in the dashboard. A limit (seats, phone countries, concurrent calls) names the cheapest
plan that lifts it. See [Errors](errors.md).

## Free trial

A new organization comes with **10 free test calls to try CallQA, no card needed**. They are
bonus calls: no time limit, used before any wallet credit, and the ones left are kept when
you choose a plan. The trial has 1 phone country, 2 concurrent calls, no paid overage and the
Starter features. Once the 10 calls are used, new calls end with `block_reason:
quota_exhausted` until an admin chooses a plan in **Billing**. `GET /usage` shows what is
left in `bonus_remaining` (`plan: "trial"`).

The trial goes to the **first organization a person creates**, once: not to a second
organization, nor to one created again after deleting the first. An early access invitation
gives the free report (50 calls) instead.

## Subscribe or change plan

An admin subscribes in **Billing** (Monthly or Annual under a plan), or with an `admin` key:

```bash
curl -sS -X POST https://app.callqa.dev/api/v1/billing/subscription/checkout \
  -H "Authorization: Bearer $CALLQA_ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"plan": "growth", "interval": "annual"}' | jq -r .url
```

Open the returned Stripe Checkout URL to pay. The subscription becomes active when Stripe
confirms it (`GET /billing/subscription` shows the plan, `status`, `interval`, the current
period, `cancel_at_period_end` and, after a failed renewal, `payment_retry_until`).

Once subscribed, change the plan or the interval, update the card, cancel, or download invoices
in the Stripe billing portal: **Billing > Invoices and payment method**, or
`POST /billing/portal`, which returns the portal URL. A second checkout answers `409`
(`subscription_exists`), also while the subscription waits for a failed payment (update the
card in the portal) and for an organization invoiced manually. The plan buttons of the
**Billing** page open the portal's confirmation screen once you are subscribed. `success_url`, `cancel_url` and `return_url` are optional and must use
`https`.

## When a renewal payment fails

If the card is declined when your subscription renews:

1. Stripe emails the billing contact that the payment failed, with a link to update the card,
   and again at each failed retry.
2. Stripe retries the payment several times, at the moments most likely to succeed, for up to
   **about 2 weeks**.
3. During these retries the subscription is `past_due` and **your plan keeps working**: test
   calls, schedules, CI runs and the API go on as usual, within your quota. Every page of the
   dashboard shows a banner with the deadline; admins can update the card from it (or in
   **Billing > Invoices and payment method**, or `POST /billing/portal`).
   `GET /billing/subscription` returns `payment_retry_until`, the same deadline.
4. Once the card is updated, Stripe retries at once; when it succeeds, the banner goes away
   and nothing else changes.
5. If the last retry fails, Stripe **cancels the subscription**. The organization loses its
   plan: new calls end with `block_reason: subscription_inactive` until you subscribe again in
   **Billing**.

The grace period covers the plan only. Calls beyond the monthly quota are still paid from the
prepaid wallet: CallQA does not advance money during the retries either.

## Wallet credit when the subscription ends

Wallet credit is only used for calls, so it is of no use once the subscription is canceled
(by you, or after failed payments) or the account is closed. Unused credit is **refunded on
request**: an admin writes to us from their account email, and we refund it to the card used
for the top-ups. The refund shows as a `refund` line in `GET /wallet/transactions`. See
section 8.2 of the [Terms of Service](https://app.callqa.dev/legal/terms/).

## Units

The quota counts **units**. A call costs:

```
units = weight x ceil(duration / 10 minutes)
```

- A call of up to 10 minutes to a weight 1 destination costs 1 unit. Every started 10-minute
  slice counts as one more. The duration is what the carrier bills, from answer to hangup:
  ringing time does not count.
- The weight depends on the destination. US and Canada: 1. Other fixed lines, toll free and
  VoIP: 1. Mobile numbers outside the US and Canada cost more to reach and weigh 2 or 3
  (for example 2 for French mobiles, 3 for UK mobiles).
- A call lasts at most `limits.max_duration_s` (5 minutes by default), and never more than
  your plan's maximum call duration (10 minutes).

Units are taken, in this order, from:

1. your **monthly quota** (calendar month, UTC);
2. your **bonus calls** (free trial, free report, referrals, one-time grants; they have no
   time limit);
3. your **prepaid wallet**, at your plan's extra call price per unit.

## Reserve, then capture

Before dialing, CallQA **reserves** the worst case: the call's maximum duration. After the
call it **captures** what was really used and releases the rest. A call that ends in `error`,
is canceled or is never dialed releases its whole reservation. A call that is dialed but not
answered still counts at least one slice.

- `GET /usage` shows `used`, `held` (reserved by calls in progress), `remaining`,
  `bonus_remaining` and `wallet_available_cents`.
- `GET /wallet` shows `balance_cents`, `held_cents` and `available_cents`
  (`available = balance - held`).
- `GET /wallet/transactions` is the append-only ledger: every change of the balance has a line
  (`topup`, `hold`, `release`, `capture`, `manual_credit`, `manual_debit`, `refund`).

Each call reports what it cost: `quota_units` and `cost_cents` (the wallet part only, `0` while
you are within quota). Each run sums `cost_cents`.

## Top up the wallet

In the dashboard (**Billing**), or with an `admin` key:

```bash
curl -sS -X POST https://app.callqa.dev/api/v1/wallet/checkout \
  -H "Authorization: Bearer $CALLQA_ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"amount_cents": 5000}' | jq -r .url
```

Open the returned Stripe Checkout URL to pay. The wallet is credited when Stripe confirms the
payment, not before. Top-ups are between $20 and $5,000: `amount_cents` from `2000` to
`500000` (`422` outside this range). `success_url` and `cancel_url` are optional.

Prices and top-ups are before tax: VAT or sales tax is added at checkout where it applies,
and the wallet is credited the amount before tax. A refunded top-up takes its credit back
from the wallet (in proportion for a partial refund), never more than what is left: the
ledger shows a `refund` line.

## Overage: on or off, monthly cap, block or pause

Calls beyond the monthly quota and the bonus calls are **paid overage**, taken from the
wallet. An admin decides, in **Billing > Overage** or with `PATCH /billing/settings`:

- `allow_paid_overage` (on by default): off, calls stop once the quota and bonus calls are
  used, even with credit in the wallet (block reason `overage_disabled`).
- `monthly_spend_cap_cents`: the most the wallet may spend in a calendar month (UTC),
  reservations of calls in progress included. A call that would go over it is not placed
  (block reason `spend_cap_reached`). No cap by default.
- `on_limit`: what happens when a limit stops calls. `block_calls` (default): each call ends
  as `blocked_insufficient_credit` with the reason. `pause_schedules`: the calls are blocked
  too, and the organization's schedules are paused so scheduled runs stop piling up blocked
  calls. They resume on the 1st of next month (UTC), or at once when an admin turns overage
  on, raises or removes the cap, or upgrades the plan.

The first time a limit stops calls in a month, the admins get an email and the audit log a
`billing.limit_reached` line. Nothing is charged for calls that were not placed. Changing
these settings needs the admin role (`billing.overage`); with an API key, the `admin`
scope.

With an `admin` key:

```bash
curl -sS -X PATCH https://app.callqa.dev/api/v1/billing/settings \
  -H "Authorization: Bearer $CALLQA_ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"monthly_spend_cap_cents": 50000}'
```

```json
{ "monthly_spend_cap_cents": 50000, "spent_this_month_cents": 12650, "low_balance_threshold_cents": 2000,
  "allow_paid_overage": true, "on_limit": "block_calls", "schedules_paused": false }
```

`GET /billing/settings` reads the same object. Only the fields you send change; `null` removes
the cap. Every change is recorded in the audit log.

## Low balance warning

When a charge takes the wallet balance under a threshold, CallQA sends the
`wallet.low_balance` [webhook](webhooks.md) once. It is sent again only after a top-up brought
the balance back to the threshold or above, and never twice within 6 hours. The threshold is
$20 by default; change it with `low_balance_threshold_cents` in `PATCH /billing/settings`, or
set it to `null` to turn the event off.

## When credit runs out

A call that neither the quota, nor bonus calls, nor the wallet can cover is **not placed**. It
ends immediately with:

```json
{
  "status": "blocked_insufficient_credit",
  "block_reason": "insufficient_credit",
  "error": "This call needs $0.55 of prepaid credit ($0.10 available). Top up your prepaid wallet (Billing > Prepaid wallet, or POST /api/v1/wallet/checkout) or upgrade your plan. Calls beyond the monthly quota are paid from the wallet."
}
```

| `block_reason` | Meaning | Fix |
|---|---|---|
| `insufficient_credit` | Quota used up and the wallet cannot cover the reservation | Top up the wallet |
| `quota_exhausted` | Quota used up and your plan has no pay-as-you-go option (or the 10 calls of the free trial are used) | Upgrade the plan (choose one after the free trial) |
| `spend_cap_reached` | Your monthly wallet spending cap is reached | An admin raises it (`PATCH /billing/settings`) |
| `overage_disabled` | Quota and bonus calls used up, and paid overage is off | An admin turns it on, or upgrades |
| `subscription_inactive` | No active subscription (status `blocked_policy`): none yet, or canceled after the failed payment retries | Choose a plan in **Billing** (during the retries the plan keeps working: see [When a renewal payment fails](#when-a-renewal-payment-fails)) |

A blocked call counts in the run's `blocked` counter, makes the run verdict `fail`, and fails
any CI gate. It also sends the `call.blocked` webhook. Find them with
`GET /calls?status=blocked_insufficient_credit`.

The trigger request itself still succeeds (`201`): credit is checked per call, when the call
is about to be dialed, not when the run is created.

## Forecast of the month

**Billing** and the **Dashboard** show a gauge of the month: units already used or reserved, plus what
your schedules will use until the quota resets (the 1st, 00:00 UTC), against your quota and
bonus calls. When the projection goes over, an alert says how many units of paid overage it
means and what they cost from the wallet, or how many calls will be blocked (overage off, no
overage on the plan, or the monthly cap).

A scenario has one schedule per environment where it is enabled (see
[Environments](environments.md)). For each enabled schedule the forecast counts:

- the runs left this month, from its cron expression in its time zone (a run at 23:30 in Los
  Angeles on the 30th is already next month in UTC, so next month's quota);
- the test cases per run (enabled variants, 1 without variants);
- the destination weight of the number dialed in that environment;
- the average 10 minute slices of the scenario's calls in that environment over the last 30
  days (in any environment when it never ran there), or its maximum duration when it never
  ran at all (a conservative estimate).

The answer lists each schedule (`schedules`) and the total per environment
(`environments`).

Manual, API and CI runs are not planned, so they are not in the forecast. With a `read` key:

```bash
curl -sS -H "Authorization: Bearer $CALLQA_API_KEY" https://app.callqa.dev/api/v1/billing/forecast \
  | jq '{projected_units, included, will_exceed, estimated_overage_cents, blocked_units}'
```

## Referral: both get 50 test calls

Your referral link is on the **Billing** page (and `GET /billing/referral`). A team that opens
it and creates a new organization is linked to yours. When that organization starts its
first paid subscription, both organizations get 50 bonus calls, used after the monthly quota
and before the wallet. Signing up alone gives nothing.

No bonus when the new organization is created by a member of yours, pays with the same Stripe
customer as yours, or is created by someone who already belongs to another organization. At
most 20 rewarded referrals per organization per year.

## Check before a big run

```bash
curl -sS -H "Authorization: Bearer $CALLQA_API_KEY" https://app.callqa.dev/api/v1/usage \
  | jq '{remaining, bonus_remaining, wallet_available_cents, overage_price_cents}'
```

`GET /usage` and `GET /plans` need the `read` scope. `GET /wallet`, the ledger, top-ups, the
subscription, the portal and the billing settings need `admin`.
