Skip to content

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:

KindRaised when
case_approvedA clinician approves a case
case_declinedA clinician declines a case
case_cancelled_after_reviewA case is cancelled after review began
video_visitA 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.