Base URL and authentication
Authorization: Bearer <YOUR_API_KEY> (details) and the stats:read permission.
Every endpoint on this page rejects a parameter it does not recognise with a
422, rather than
ignoring it. A filter we cannot apply would otherwise come back as a 200 carrying a number that
answers a different question, and you would have no way to tell.site_id is the site’s snippet key, the ttid_ value your install snippet carries as data-site. You can read it from GET /v1/sites.
Currency
Every amount is stored in USD.amount_usd, revenue_usd and ltv_usd are always USD, whatever your site’s display currency is.
Where a display currency is set, the _display field beside each amount carries the same money converted at the European Central Bank reference rate for that row’s own date, so a figure for last Tuesday does not move when today’s rate does. If any date in the window has no published rate, the _display field is null. It is never the USD number relabelled.
The response tells you what happened:
Refunds
Every amount on this page is net of refunds. A refund is subtracted from whichever AI source the original payment was attributed to, on all six revenue paths: Stripe, Shopify, Lemon Squeezy, Polar, Paddle, and anything you post to the Payment API. Four things follow, and they are worth knowing before you build a report on these numbers:- A refund is dated on the day it was issued, not the day of the original payment. A window you queried last week returns the same figures today. Refunds land in the window they happened in.
- An amount can be negative. A day, or a source, that gave back more than it took in during your window is a negative number. Do not clamp it at zero: the negative is the answer.
- Conversion counts stay gross.
attributed_sessionson/breakdownandpaymentson/ltvcount the purchase that happened. A refund does not erase it. So a source can show conversions and negative revenue in the same window, and both are correct. - A cancelled subscription is not a refund. Cancelling stops future charges; the charges already paid stay in
ltv_usdand in every window that contains them.
GET /v1/revenue/breakdown
Which AI sources produced revenue in the window. Top 5 by revenue, highest first.
Query parameters
Sending
from without to (or the reverse) is a 422.
When you omit attribution_model, every model is summed together. Pass the model you actually report on if you want a single view.
source_kind: "unmatched" is revenue we could not tie back to any session. It is worth reading: it is the gap between what your payment provider recorded and what Traceten could attribute.
Response fields
GET /v1/revenue/timeseries
Daily revenue per AI source, ordered by date and then by source. This is the data behind the revenue chart in the dashboard.
/breakdown, plus interval. Only interval=day is supported today.
GET /v1/revenue/sku-breakdown
Which products the AI-referred traffic actually bought. Shopify only.
units_sold and revenue_usd are both net of returns. A partial refund takes back only the line items that came back, so a returned SKU loses its units and its revenue while the rest of the order keeps both. Either figure can be negative in a window where returns outran sales.
Query parameters
This endpoint takes a narrower set than the other revenue routes.attribution_model and source_kind are not supported here, because the underlying product-level rollup has no column for either. Sending one is a 422, not a silent no-op.
Any other parameter is rejected.
If you have not connected Shopify
This endpoint returns403 when the account has no Shopify connection, and 404 when the site is not one your key can reach. The two are distinct on purpose, and they answer different questions: by the time you can see the 403 you have already proved you own the site, so it tells you only that the connection is missing.
Connect Shopify in the dashboard, then retry.
GET /v1/revenue/ltv
Cumulative attributed recurring revenue per subscription. This is the SaaS number: Claude referred this customer, and they have now paid you this much.
This endpoint takes no date range
Lifetime value is cumulative and all-time. A window would contradict the number it reports, sofrom, to and days are rejected with a 422, not ignored. Use /breakdown or /timeseries when you want a period.
Query parameters
Reading the response
ai_source is the source that acquired the customer, not the source on the most recent payment. That is the whole point of the metric.
ltv_usd is always USD, and it is net of refunds. This endpoint does not convert to a display currency. payments stays gross, so a subscription that was billed twice and refunded once reads two payments and one payment’s worth of value.
first_payment_at and last_payment_at are the first and last payments. A refund never becomes a subscription’s last payment.
total_count counts (subscription_id, ai_source) pairs, because that is the unit rows lists. A subscription credited to two sources is two rows and two counts.
has_more is derived from the page coming back full, so it can be true on an exact multiple of page_size with an empty next page. total_count is exact if you would rather do the arithmetic.
Errors
Standard error shapes apply. The ones specific to these endpoints:Next
- Sessions API to drill from a revenue number into the sessions behind it.
- Sources API for session counts per source.

