Skip to main content
A read-only, server-to-server API that a partner’s backend polls to see how its referral program is performing: clicks, conversions, reversals and commission, for the stores it operates.
  • Server to server only. Call it from your backend, never from a browser. The key must stay secret.
  • Informational, never a payout surface. Credit your users only from the payout endpoint of your partner wire and its checkpoint protocol. These figures come from the same ledger, aggregated differently, without the anti-double-credit protocol.
  • Additive versioning. Fields may be added; existing fields are never removed or repurposed. Ignore keys you do not recognise.

Authentication

  • Paths are served at the root, without /api.
  • Each key is issued per partner and bound to an explicit list of store ids. A valid key used on a store outside that list gets the same 404 as a store that does not exist.
  • This is a read credential. It is never the secret you use to sign order events; the two are deliberately separate so a leaked read key cannot forge orders.
  • Every response carries Cache-Control: no-store. Do not cache responses.

Endpoints

Store totals

  • clicks.total counts genuine redirect clicks across all of the store’s campaigns, including ended ones.
  • conversions.total counts distinct referred orders that converted. A later refund does not remove an order from this count.
  • reversals.affectedConversions counts orders currently refunded, partially refunded or charged back.
  • held is a point-in-time escrow balance and can go up or down. releasedLifetime and reversedLifetime only ever increase.
  • asOf is the single database instant every figure was true at.

Campaigns

Returns every campaign of the store in every state (DRAFT, ACTIVE, END, COMPLETE), newest first, capped at 100.
  • campaignId is opaque. It is not a UUID; never parse it. title is display-only and may be null.
  • scope.externalProductId is present only on product campaigns; the key is absent on store-wide ones.
  • rewardMode is LAST_CLICK_ONLY (the last-click creator receives the whole order commission) or FIRST_LAST_80_20 (if another eligible creator first introduced the shopper, they receive 20% of it and the last-click creator the remaining 80%). It is campaign metadata only: no earnings figure changes with it.
  • returningOrders counts orders tracked but not paid because the buyer had already bought in this campaign. It never overlaps conversions.
  • Reconciliation guarantee. For each of held, releasedLifetime and reversedLifetime, the sum over campaigns[].earnings plus earnings.unallocated equals the store total from the same snapshot, even when truncated is true. unallocated holds money that cannot be placed on a returned row (normally zero).
  • Ended campaigns keep updating: a late refund still moves their reversals.

Campaign creators

Query: limit (default 50, 1 to 100) and cursor.
Every creator provisioned on the store is listed, including those with no activity (all zeros), ordered by externalUserId. This route carries no money.

Creator clicks

Query: limit (default 100, 1 to 500) and cursor.
clickId is your reconciliation key. occurredAt is when the click happened. outcome describes the click’s own order: returningCustomer is true when any order through this click was tracked but not paid because the buyer was returning. Outcomes change over time (no-conversion to converted to refunded). Re-read by clickId and replace the older value.

Time series

Query parameters: Each bucket reports clicks, conversions, commissionAccrued, released and reversed, plus a totals object that always equals the sum of the buckets. startsAt is an absolute UTC instant; bucket boundaries follow the chosen zone, so a daylight-saving day has 23 or 25 hourly buckets. Empty buckets are included. At most 1,000 buckets per request. The series reports flows only. There is no held (a balance has no meaning per bucket) and no reversal count. Conversions land on your event’s occurrence time if you send one, otherwise on ingestion time, which can lag.

Pagination

The creator and click routes use keyset cursors:
  1. Call without cursor.
  2. While nextCursor is not null, call again with cursor=<nextCursor> exactly as received, not re-encoded.
  3. Stop when nextCursor is null.
There is no total count. asOf is per page, not a frozen snapshot of the walk. New clicks arrive at the front of the clicks feed, so restart from page one for a fresh reconciliation. Cursors are traversal tokens, not bookmarks; do not store or construct them.

Errors

Gotchas

  • Money is a decimal string. Parse with a decimal type, not a float.
  • Counts never shrink; balances can. Use releasedLifetime deltas, not held, to reason about what was paid.
  • Late refunds. A refund arriving after a commission was already released writes no reversal entry today, so reversedLifetime can understate total reversals.
  • Two requests are two snapshots. The store totals and the campaigns list agree when read at the same instant; separate calls can differ by activity in between.
  • This is referred activity, not your order book. Orders that were never referred are not counted.
  • Personal data. externalUserId is your own pseudonymous id. It is returned only to the partner that supplied it. Use it only for referral attribution and reconciliation, and do not cache it beyond that. Requests are access-logged with credentials redacted.