CSV export
GET /api/export/conversions — the eleven columns, what scopes a download, and why refunds appear as their own rows.
GET /api/export/conversions returns every conversion for the client you are currently working in, newest first, as a CSV download.
Authentication and scope#
Two things scope a download, and the second matters if you run more than one client:
- Your session. No session is a
401. - The active client. Not every business you own — the one currently selected. An agency with fifteen clients gets one client's conversions per download, which is what stops fifteen clients' revenue ending up in a file handed to one merchant. No active client is a
409.
Parameters#
| Parameter | Type | Description |
|---|---|---|
| code | string | Optional. A campaign's tracking_code, limiting the export to that campaign. Omit it for every campaign belonging to the active client. An unrecognised code is not an error — it matches no campaign and you get a file containing only the header row. |
Format#
text/csv; charset=utf-8, sent as an attachment with Cache-Control: no-store. Rows are separated by CRLF, and any cell containing a comma, quote or newline is quoted with doubled inner quotes, per RFC 4180 — campaign names with commas in them are safe.
The filename is proof-conversions.csv, or proof-<code>-conversions.csv when filtered.
proof- prefix is a leftover from the product's previous name and has not been renamed. It is cosmetic, but if you have a script matching on the filename, match on the suffix.date_utc,campaign,creator,goal_type,kind,source,order_value,currency,external_order_id,reverses_external_order_id,tracking_code
2026-09-14T11:02:44.120Z,Autumn push,Maya Okonkwo,sale,reversal,webhook,-20.00,EUR,shopify_refund:9876543210,5123456789012,a7f3k2
2026-09-12T09:41:03.880Z,Autumn push,Maya Okonkwo,sale,sale,webhook,49.90,EUR,5123456789012,,a7f3k2
2026-09-11T18:20:15.441Z,Autumn push,Maya Okonkwo,sale,sale,pixel,32.00,GBP,pixel:order-1234,,a7f3k2Columns#
| Parameter | Type | Description |
|---|---|---|
| date_utc | ISO 8601 | When the conversion was recorded, in UTC — not the business's timezone, which is what the dashboard and reports display. A late evening sale can appear on the previous day here. |
| campaign | string | Campaign name at time of export. |
| creator | string | Creator name at time of export. |
| goal_type | sale | signup | lead | The campaign's goal. Determines whether order_value is meaningful. |
| kind | sale | reversal | Stated explicitly so a spreadsheet reader never has to infer “refund” from a minus sign. Blank values are treated as sale. |
| source | webhook | pixel | manual | How the conversion arrived. A fourth value, code, exists in the schema but is never written by any current code path — discount-code attribution arrives through the Shopify webhook and is recorded as webhook. Do not write a parser that expects it, but do not assume it can never appear either. |
| order_value | number | Empty for signup and lead campaigns, which have no revenue. Negative on reversal rows. |
| currency | ISO-4217 | Per row. Not converted — see below. |
| external_order_id | string | The order id, carrying its source prefix: pixel:<your-id> from the conversion endpoint, a bare numeric id from Shopify orders, and shopify_refund:<id> on reversals. |
| reverses_external_order_id | string | On a reversal row, the external_order_id of the sale it reverses — so the file is self-contained and you never need to join on an internal id. Empty on sale rows. |
| tracking_code | string | The campaign's tracking code. |
Refunds are rows, not adjustments#
A refunded order appears twice: the original sale, and a reversal with a negative value. To get net revenue, sum order_value — the signs do the work. To count sales, filter to kind = sale, because a reversal is the undoing of a conversion, not a conversion of its own.
Currencies are not converted#
Each row carries the currency the shopper actually paid in. Summing order_value across mixed currencies produces a meaningless number. No exchange rate is applied anywhere in Yonto, deliberately — a rate would have to be picked for a date, and the result would look authoritative while being an estimate. Group by currency first.
Limits#
?code= to narrow it if a download starts taking a long time.The endpoint is not rate limited, unlike the public ones in Rate limits — it requires a session, so there is no anonymous traffic to throttle.