What is billed
Two things: the platform fee for your band, monthly, and overage on settled transactions above the included allowance. Nothing else, and the list of what is never billed below is exhaustive.
What a settled transaction is
A confirmed inbound deposit or a confirmed outbound withdrawal, counted once at the maximum confirmation threshold, keyed on transaction id.
Each phrase carries weight:
Confirmed, not detected. A transaction that never reaches its threshold is never billed.
Once at the maximum threshold. Bitcoin fires deposit_confirmed three times — at 1, 2 and 3
confirmations — and is billed once, at 3. You are not charged per webhook.
Keyed on transaction id. A reorg-and-replay of the same transaction bills once. A transaction paying two of your addresses in one Bitcoin transaction bills once, not twice.
Never billed
| Why | |
|---|---|
| Sweeps and consolidations | See below — this one is a design decision |
| Failed or reverted transactions | You paid the chain's fee. Paying us too would be absurd |
| Balance queries | Read-only. Rate limits apply, charges do not |
| Address generation | You already bought address capacity with the band |
| Address archiving | |
| Webhook deliveries and retries | Including every rung of the ladder and every replay |
| All sandbox activity | Every simulate call, every derived address, every event |
| Console usage | |
| Audit log reads | |
| API calls generally | Only settled transactions count |
Sandbox is completely free and unmetered. Load-test your integration there. That is what it is for, and there is no ceiling worth mentioning on the simulate endpoints.
Why sweeps are excluded
This is the exclusion worth understanding, because it changes how you should build.
A sweep is an artifact of the architecture rather than customer value: funds arrive at per-customer deposit addresses because that is how deposit attribution works, and consolidating them into treasury is bookkeeping you have to do as a consequence.
Billing for it would create an incentive to sweep less — which would leave funds scattered across thousands of deposit addresses and make your treasury operations worse. A pricing model that makes a customer's operations worse is a badly designed pricing model.
So sweep as often as the chain fees justify. ChainOS is not the cost you are optimising against. See Sweeps.
A sweep is identified as an outbound transaction to an address ChainOS knows is yours. That is not a loophole you can widen: a withdrawal to a customer's external destination is a settled transaction however you label it, because it is.
Active addresses
Billed as the maximum observed during the period, from a daily snapshot of
COUNT(*) WHERE status = 'active'.
Two consequences:
- Issuing 500 addresses and archiving them the same week still bills the peak for that period. The snapshot caught it.
- Archiving removes an address from the count from the next snapshot, not retroactively.
Archiving also removes the address from monitoring, so a deposit arriving there afterwards produces no webhook. Read Deposits before archiving in bulk.
Note again that ETH, BSC, Polygon, Avalanche and Base share one address — five chains, one address, one count.
Overage
overage = max(0, settledTx − band.includedTx) × band.overageRate
| Band | Included | Rate |
|---|---|---|
| Launch | 1,000 | $0.08 |
| Growth | 10,000 | $0.05 |
| Scale | 100,000 | $0.025 |
| Enterprise | 750,000 | $0.012 |
Overage is charged, not blocked. Exceeding the transaction allowance does not stop you trading; it
appears on the invoice. Exceeding the address cap is blocked, with
403 ADDRESS_LIMIT_EXCEEDED, because an address is a durable commitment rather than a one-off event.
If your steady state is well above the allowance, the next band up is usually cheaper than the overage. Compare against the blended column in Bands and tiers.
Checking
curl -s $CLOUD/v1/billing/usage -H "Authorization: Bearer $TOKEN"
{
"period": { "start": "2026-08-01", "end": "2026-08-31" },
"activeAddresses": { "current": 8421, "billed": 8790, "limit": 10000 },
"settledTransactions": { "count": 9102, "included": 10000 },
"overage": { "transactions": 0, "amount": "0.00", "currency": "USD" },
"projected": { "transactions": 12400, "overageAmount": "120.00" }
}
projected is a straight-line extrapolation from the period so far, so it is pessimistic after a busy
first week and optimistic after a quiet one. A signal, not a forecast.
The console warns at 80% of either quota, and shows invoice history under Billing.
Reconciling an invoice
If a settled-transaction count looks wrong, the transactions themselves are queryable:
curl -s "$EDGE/transactions?status=confirmed&from=2026-08-01&to=2026-08-31&size=500" \
-H "X-API-Key: $CHAINOS_EDGE_KEY" \
| jq '[.data.content[] | select(.isSweep == false)] | length'
Filter out sweeps, count distinct txid, and count each once regardless of how many thresholds it
crossed. A discrepancy after that is worth a ticket — include the period and the count you computed.
Do not compare against a count of webhooks received. Bitcoin's three thresholds and at-least-once delivery both make that number legitimately higher than the billed figure.
The metering implementation, briefly
For the reviewer who wants to know it is not guesswork:
@Transactional
public void recordSettledTransaction(Transaction tx) {
if (tx.isBilled() || tx.isSandbox() || tx.isSweep()) return;
UsagePeriod period = periods.currentFor(tx.getAccountId());
period.incrementSettled();
Band band = bands.forAccount(tx.getAccountId());
if (period.getSettledTxCount() > band.includedTx()) {
period.incrementOverage(band.overageRate());
}
periods.save(period);
tx.setBilled(true); // the idempotency guard
events.send("billing.usage", tx.getAccountId().toString(), UsageEvent.settled(tx));
}
The isBilled() check on the way in and the setBilled(true) on the way out, inside one transaction,
are what make double-billing impossible under the at-least-once semantics everything else in the system
runs on. The three early returns are the whole of the exemption list: already billed, sandbox, sweep.