Affiliates
The partner program as your back office reads it, the sponsor tree, referrals and the upline they are credited under, the customers each affiliate brought in, and the program's settings.
An organization that sells on its Cuvo storefront can run a partner program: affiliates share a link or a coupon code, a customer who buys through one is referred to that affiliate, and affiliates recruit other affiliates beneath them. These endpoints let your own system read all of it, so a back office, a commission engine or a partner portal can be built on the same records the storefront credits sales against.
Everything here needs affiliates:read, which an organization's administrator grants on its own.
It is read-only: creating and editing affiliates comes in a later release, under
affiliates:write.
| Operation | What it answers |
|---|---|
GET /v1/affiliates | Affiliates, newest first |
GET /v1/affiliates/{id} | One affiliate, with its links and coupon codes |
GET /v1/affiliates/{id}/customers | The customers one affiliate referred, as an affiliate may see them |
GET /v1/affiliate_referrals | Referrals, newest first |
GET /v1/affiliate_referrals/{id} | One referral, with its upline and acquisition |
GET /v1/affiliate_program | The program's settings, or 404 when the organization runs none |
Every list takes limit and starting_after and answers { data, has_more }; see
Pagination. Lists also take created_after, and the affiliate and referral
lists take updated_after: pass back the newest updated_at you have seen to poll for what moved.
Affiliates and the tree
{
"id": "aff_test_jordan",
"name": "Jordan Lee",
"email": "jordan@example.com",
"kind": "individual",
"status": "approved",
"sponsor": "aff_test_avery",
"depth": 1,
"ref_code": "JORDAN",
"referral_url": "https://shop.example.com/r/JORDAN",
"links": [{ "id": "lnk_test_jordan", "slug": "jordan", "label": "Main link", "url": "https://shop.example.com/r/jordan", "destination_path": null, "archived_at": null }],
"coupon_codes": [{ "code": "JORDAN15", "active": true, "percent_off_bps": 1500, "fixed_off_cents": null }],
"external_id": null,
"metadata": {}
}
sponsoris the affiliate who recruited this one, anddepthhow many levels below the top of the tree it sits:0has no sponsor. Filter bysponsorto walk the tree one level at a time.kindisindividualfor a person andcorporatefor a business (a med spa, a practice), whose own sales earn the program's corporate reward rows.- Only an
approvedaffiliate is credited.pendingwaits for approval or for the onboarding steps the program requires;onboardingrecords when each was completed and which version of the terms and agreement was signed. referral_urland a link'surlare null while the organization has no storefront domain to build them on.external_idandmetadataare your own key and values for the affiliate.emailfilters an exact address without regard to case;ref_codeandexternal_idfilter exactly.
An affiliate never carries money. Commission totals, reward overrides, payout details and the clinic's private notes are not part of this API.
Referrals, the upline and the acquisition
A referral is one customer credited to one affiliate, by their tracked link (attributed_via: "cookie") or their coupon code at checkout ("coupon").
{
"id": "afr_8Qm2",
"affiliate": "aff_4kT9",
"customer": "cus_51Hk",
"patient": "pat_7d3a",
"attributed_via": "coupon",
"coupon_code": "GLOW20",
"upline": { "l2": "aff_2bW1", "l3": "aff_9xQe", "frozen_at": "2026-11-05T14:02:11Z" },
"acquisition": { "kind": "affiliate", "affiliate": "aff_4kT9", "decided_at": "2026-11-05T14:02:11Z" },
"created_at": "2026-11-05T14:00:03Z",
"updated_at": "2026-11-05T14:02:11Z"
}
The upline is frozen; the sponsor is live. upline.l2 and upline.l3 are the affiliate's
sponsor and that sponsor's sponsor at the moment the referral was first credited, and
frozen_at is that moment. An organization can move an affiliate to a different sponsor later;
the affiliate's sponsor changes, the referrals it already holds do not. A commission on this
customer walks the frozen upline, so build tree payouts from upline, never from re-reading the
affiliates' current sponsors.
The acquisition decides who is credited. A customer can arrive through an in-house affiliate
and through an outside tracking network at once. At their first purchase the storefront decides
who acquired them, and acquisition records it: kind is affiliate (with that affiliate),
network or none. While acquisition is null the referral's own affiliate is the one
credited; once it is set, it decides, and a referral whose acquisition went to a network credits
no affiliate at all.
customer is null until the referred person holds a store account, and patient until they have
a chart. Each change to a referral (the upline frozen, the customer known, the acquisition decided)
moves updated_at and is announced as affiliate_referral.updated.v1.
An affiliate's customers
GET /v1/affiliates/{id}/customers is the projection meant for affiliate-facing systems: a partner
portal or a payout statement. One row per referral, newest first, paged by referral:
{
"customer": "cus_51Hk",
"referral": "afr_8Qm2",
"referred_at": "2026-11-05T14:00:03Z",
"attributed_via": "coupon",
"first_order_at": "2026-11-05T14:02:11Z",
"last_order_at": "2026-11-05T14:02:11Z",
"order_count": 1,
"active": true,
"label": null
}
active means a paid purchase in the last 90 days or an active membership; filter on it with
active=true or active=false. There is never an email, a phone number, a date of birth, an
address, a product or anything clinical on this row.
Disclosure. label names the customer only as far as the program's customer_disclosure
allows, and the default discloses nothing:
customer_disclosure | label |
|---|---|
ids_only (the default) | null |
initials | Initials only |
first_name_last_initial | A first name and a last initial |
The organization sets this on its program. A system that shows customers to affiliates should
render label when it is present and the customer id when it is not.
The program
GET /v1/affiliate_program reads the organization's settings: whether the program is active,
the cookie window, the holding period before a commission can be paid, how many levels of the tree
a sale pays, whether new affiliates are approved automatically, and which onboarding steps are
required. commission_engine says who pays the affiliates: internal is Cuvo's own commission
ledger, and external means the organization runs commissions in its own system, named with a
link in external_engine. Under external, attribution carries on exactly as before, and Cuvo
posts no new commission lines.
Events
affiliate.created.v1, affiliate.updated.v1, affiliate_referral.created.v1 and
affiliate_referral.updated.v1 ride the existing feed and endpoints, and are delivered only to a
grant holding affiliates:read. Each carries the resource's id and never the affiliate's or
the customer's details: read the resource by that id. See
Events and realtime.
In test mode
Every sandbox starts with the same program and the same three-level tree, so you can build tree logic before a real partner exists:
| Affiliate | Kind | Sponsor | Coupon code |
|---|---|---|---|
aff_test_avery | individual | none | AVERY10, 10% off |
aff_test_jordan | individual | aff_test_avery | JORDAN15, 15% off |
aff_test_harbor | corporate | aff_test_jordan | HARBOR20, $20 off |
The sandbox program is active, uses the internal engine, discloses ids_only, requires no
onboarding step and approves affiliates on sign-up.
POST /v1/test/affiliates adds one of your own, with a link and a 10% coupon code, under any
affiliate in the tree, and announces it as affiliate.created.v1. It needs test:manage and an
Idempotency-Key; status defaults to approved.
curl -X POST https://api.cuvo.co/v1/test/affiliates \
-H "Authorization: Bearer cuvo_sk_test_…" \
-H "Idempotency-Key: affiliate-riley-1" \
-H "Content-Type: application/json" \
-d '{ "name": "Riley Park", "email": "riley@example.org", "sponsor": "aff_test_harbor" }'
Lists answer your own affiliates newest first, then the tree. Every affiliate you add is an
individual, and an email is used once across the sandbox, as it is in an organization.
Referrals in the sandbox come from test purchases: a purchase that names an affiliate opens the
referral that credits it. Its upline is the affiliate's sponsor chain, frozen when the referral was
opened; no acquisition is decided, since the sandbox has no outside network. In an affiliate's
customers, each referral reports the test purchase that opened it: one order, at the referral's
creation, and active. POST /v1/test/reset removes your affiliates and referrals; the tree
stays.