Billing
What gets charged, how to read the usage ledger, and how an invoice reconciles to it.
Three read-only endpoints, all billing:read, all answering in either mode. Usage and the invoice
list give a test credential an honest empty answer rather than a refusal, because nothing is charged
for a sandbox and nothing is invoiced to one. Reading one invoice by id gives a test credential a
404, for the same reason one resource at a time: there is no invoice of any id for a sandbox to
hold.
What is billable
Charges are raised per event, not per API call. Reading the API is free; a clinician's time is not. Today four kinds are recorded:
| Kind | Raised when |
|---|---|
case_approved | A clinician approves a case |
case_declined | A clinician declines a case |
case_cancelled_after_review | A case is cancelled after review began |
video_visit | A visit is completed |
A visit is charged when the call happens, which is visit.completed.v1 and nothing else. Booking
one costs no clinical time, moving it costs none either, and a no-show is unpriced in this release:
visit.no_show.v1 raises no charge at all.
kind is a string rather than an enum on purpose. A new kind arrives with the feature that produces
it, and you must not have to upgrade an SDK to keep reading your own usage.
Every charge also emits charge.created.v1, so you can mirror the ledger as it is written rather
than poll for it.
Usage
GET /v1/usage aggregates the charge ledger over a window.
curl "https://api.cuvo.co/v1/usage?from=2026-09-01T00:00:00Z&to=2026-10-01T00:00:00Z" \
-H "Authorization: Bearer cuvo_sk_live_…"
{
"period_start": "2026-09-01T00:00:00Z",
"period_end": "2026-10-01T00:00:00Z",
"currency": "usd",
"data": [
{ "kind": "case_approved", "count": 412, "priced_cents": 823000, "unpriced_count": 0 },
{ "kind": "case_declined", "count": 18, "priced_cents": 0, "unpriced_count": 18 }
],
"totals": { "count": 430, "priced_cents": 823000, "unpriced_count": 18 }
}
The window is half open: a charge at or after period_start is counted, a charge at
period_end is not. from defaults to the first instant of the current calendar month in UTC, and to to the
first instant of the next.
unpriced_count is the number that matters when a total looks too small. A charge with no rate card
for its kind is usage that happened and will never reach an invoice. It is counted rather than
hidden, so you can see it instead of inferring it.
Invoices
GET /v1/invoices lists the invoices raised against your integration, newest first. This is a
mirror of what the billing system holds, so id is the payment processor's own invoice id and
carries no Cuvo prefix.
{
"data": [
{
"id": "in_1QpX…",
"number": "C4A2B1-0007",
"status": "open",
"amount_due_cents": 823000,
"amount_paid_cents": 0,
"hosted_invoice_url": "https://invoice.stripe.com/…",
"period_start": "2026-09-01T00:00:00Z",
"period_end": "2026-10-01T00:00:00Z",
"due_at": "2026-10-15T00:00:00Z",
"paid_at": null,
"created_at": "2026-10-01T02:14:09Z"
}
],
"has_more": false
}
status is draft, open, paid, uncollectible or void. number is null on a draft that was
never finalized. hosted_invoice_url is where the invoice can be read and paid.
Reconciling an invoice
GET /v1/invoices/{id} returns the same mirror row plus charges: one line per billable event,
oldest first. The hosted invoice shows a handful of aggregated lines, which is what the processor
was told to bill. This is the ledger underneath it, which is the level an invoice is actually
argued at.
{
"id": "in_1QpX…",
"number": "C4A2B1-0007",
"status": "open",
"amount_due_cents": 823000,
"amount_paid_cents": 0,
"hosted_invoice_url": "https://invoice.stripe.com/…",
"period_start": "2026-09-01T00:00:00Z",
"period_end": "2026-10-01T00:00:00Z",
"due_at": "2026-10-15T00:00:00Z",
"paid_at": null,
"created_at": "2026-10-01T02:14:09Z",
"charges": [
{
"id": "chg_9d2f11ab",
"kind": "case_approved",
"resource_id": "case_7d1e4b2a",
"amount_cents": 2000,
"currency": "usd",
"created_at": "2026-09-08T14:07:44Z",
"reversal_of": null
}
]
}
resource_id is the same public id the charge.created.v1 event carries, so a line traces back to
the case that caused it without a second lookup. amount_cents is null when no rate card priced the
kind, and negative on a reversal. A correction is always a new row with reversal_of set, never an
edit to the row it corrects.
An account billed outside the processor, or one on a comped agreement, has no invoices to list here. Its usage still reads normally.