Purchases and customers
What a clinic's storefront sold, to whom, what was refunded or disputed, which affiliate it is credited to, and how to keep your own copy in step.
A purchase is one paid charge to a customer: a storefront checkout, one charge of a membership, or a visit bought on its own. A customer is one storefront account and what it has bought. Both are read-only here: the storefront and its payment processor own every transition, and you hear about them as events.
Every operation on this page needs purchases:read. A clinic grants it separately from the
clinical scopes, so a back office that settles money can be given it without patients:read.
Listing purchases
curl "https://api.cuvo.co/v1/purchases?affiliate=aff_3c9d00e5&updated_after=2026-11-01T00:00:00Z" \
-H "Authorization: Bearer cuvo_sk_live_…"
{
"data": [
{
"id": "pur_7c1f9a2e",
"kind": "treatment",
"customer": "cus_4b8e0d17",
"patient": "pat_9f2c1a7b",
"affiliate": "aff_3c9d00e5",
"referral": "afr_51d2c8aa",
"case": "case_7d1e4b2a",
"order": "ord_0e6b93c4",
"membership": null,
"cycle": null,
"product": { "id": "prod_2a7f", "variation_id": null, "purchase_option": "monthly", "rx": true },
"amount_cents": 29900,
"refunded_cents": 0,
"currency": "USD",
"discount": { "code": "SPRING", "amount_cents": 3000 },
"payment_status": "paid",
"dispute": null,
"processor": "payengine",
"transaction_id": "txn_8841",
"fulfillment_status": "shipped",
"follow_up_due_at": "2027-02-02T15:04:00Z",
"created_at": "2026-11-02T15:04:00Z",
"paid_at": "2026-11-02T15:04:00Z",
"refunded_at": null,
"updated_at": "2026-11-03T09:12:00Z"
}
],
"has_more": false
}
customer,patient,affiliate,kind,payment_status,created_afterandupdated_afterfilter the list.limitandstarting_afterpage it, newest first. See Pagination.GET /v1/purchases/{id}reads one.
What a purchase says
kind is visit for a consult bought on its own, membership_cycle for one charge of a
membership (membership and cycle say which), and treatment for every other checkout. A
membership charge that wrote no storefront order has an id that starts pur_mc_; treat it like
any other id.
amount_cents is what the customer was charged, in integer cents of currency. discount is the
code applied at checkout, and its amount_cents is null when the ledger did not record the amount
taken off.
payment_status is paid, then partially_refunded or refunded as money goes back.
refunded_cents is the total given back and refunded_at the latest refund. A chargeback sets
dispute, with its amount and when it was opened, and never changes payment_status: a disputed
charge stays paid until a refund says otherwise. Its status is open, because the ledger
records that a chargeback was opened, not how it ended; treat any other value as one added later. A read never calls the
processor; these fields are the platform's own ledger.
case is the case the checkout opened, order the latest pharmacy order raised from it, and
fulfillment_status where that stands in the storefront's words. All three are null for a
purchase with no case. product is null for a charge that names no product.
Which affiliate a sale is credited to
affiliate is the in-house affiliate the sale is credited to, by the partner program's own rule.
When an acquisition has been decided for the customer, it decides: its affiliate, or no affiliate
when an outside tracking network won the customer. Until one is decided, the customer's referral
decides. referral is the customer's referral either way, so a purchase with a referral and a
null affiliate is a customer an affiliate referred whose sale a network was credited with.
A customer carries the same referral and affiliate, so GET /v1/customers?affiliate= is an
affiliate's book of business and GET /v1/purchases?affiliate= is what that book has bought.
Customers
curl "https://api.cuvo.co/v1/customers?active=true" \
-H "Authorization: Bearer cuvo_sk_live_…"
A customer is ids and totals: patient once the account is linked to a chart, referral and
affiliate, first_order_at and last_order_at, order_count, lifetime_amount_cents (every
charge, before refunds) and refunded_amount_cents. active is true while the customer has a paid
purchase in the last 90 days or a membership that is active.
No name, email or address is on a customer. Who the person is belongs to the
patient, read through patient under patients:read.
affiliate, patient, active, created_after and updated_after filter the list.
GET /v1/customers/{id} reads one.
Keeping your copy in step
Events tell you something moved; the list tells you everything that did. Use both.
- Subscribe to
purchase.paid.v1,purchase.refunded.v1andpurchase.disputed.v1. Each names the purchase. Read it withGET /v1/purchases/{id}and store it whole, becauseupdated_atmoves on every change and a later read always supersedes an earlier one. - On a schedule, list
GET /v1/purchases?updated_after=<the newest updated_at you have stored>and page untilhas_moreis false. Store every row, then move your cursor to the newestupdated_atamong them. The list is ordered by when a purchase was made, not by when it changed, so read the whole window before you move the cursor. - Do the same with
GET /v1/customers?updated_after=for the totals.
A delivery that arrives late or twice is harmless under this loop: you store what the API says now, never what the event implied.
In test mode
The sandbox sells the four products of Products and has no storefront, so you
record the checkouts yourself. All three controls need test:manage.
curl https://api.cuvo.co/v1/test/purchases \
-H "Authorization: Bearer cuvo_sk_test_…" \
-H "Content-Type: application/json" \
-d '{ "product": "prod_test_semaglutide", "variation_id": "starter", "patient": "pat_9f2c1a7b", "affiliate": "aff_test_3c9d", "coupon_code": "SPRING" }'
productis required.variation_idandpurchase_optionchoose among the product's options, the first option by default, andamount_centsdefaults to that option's price.- With no
customer, the patient's existing customer is used, or a new one is made. Acustomeryou name must belong to thepatientyou name, if you name both. affiliatemust be a sandbox affiliate. A customer with no referral gets one, crediting that affiliate,attributed_viacouponwhen you send acoupon_codeandcookieotherwise. A customer already credited to another affiliate is refused.- The response is the purchase,
paid. The sandbox writesaffiliate_referral.created.v1when it wrote a referral, thenpurchase.paid.v1.
POST /v1/test/purchases/{id}/refund gives back amount_cents, or everything not yet refunded, and
writes purchase.refunded.v1. POST /v1/test/purchases/{id}/dispute opens a chargeback for
amount_cents, or everything not yet refunded, and writes purchase.disputed.v1. A purchase can
be disputed once, and a purchase refunded in full has nothing left to refund or dispute.
A sandbox purchase has no case, order or membership, processor is sandbox, and test mode writes
no charges for any of it.