Conversions and sessions, row by row
Last updated Nov 4, 2026
Create a connector key, then copy each conversion and session into your data store over HTTP, picking up where the last read stopped.
On Business and Agency, a connector of your own can copy one project's conversions and sessions into your data store, row by row. TraceLog provides no connector and sends the rows nowhere: you build it, and it reads when you run it.
The connector key
A connector key reads the rows of one project and writes nothing. It reaches no other project and nothing else in your account.
The account owner creates it, in Settings → Installation and keys → Create connector key; until your project is first verified, the button sits under Other keys. A member sees the key and cannot change it. The value is shown once, at creation, and cannot be shown again: copy it then.
Rotate it from the same place: the current key keeps working for 24 hours, then stops, so your connector's next run does not fail on a key changed under it. A revoked key stops working immediately.
The two reads
https://api.tracelog.io/v1/connector/conversions
https://api.tracelog.io/v1/connector/sessions
Both are GET, and both take the key in this header:
Authorization: Bearer tl_ck_xxxxxxxxxxxxxxxxxxxxxxxxxxcurl "https://api.tracelog.io/v1/connector/conversions" \
--header "Authorization: Bearer tl_ck_xxxxxxxxxxxxxxxxxxxxxxxxxx"A page answers with five fields:
rows, the rows themselves;cursor, what the next read sends;more,truewhen the page is full;timezone, the project's time zone;unverifiableDays, the days Matching Analytics describes, below.
The rows
A conversion row is one conversion, keyed by name with identifier:
{
"name": "purchase",
"identifier": "order-1042",
"firstBrowserAt": "2026-10-03T09:41:12.000Z",
"firstServerAt": "2026-10-03T09:41:15.000Z",
"value": 89.5,
"currency": "EUR",
"items": [
{
"id": "sku-7",
"name": "Trail shoe",
"category": null,
"quantity": 1,
"price": 89.5
}
],
"sessionId": "6f1c2a54-8f3b-4d7e-9a10-2c4b7e1d9f03",
"channel": "paid_search",
"aiPlatform": null,
"utmSource": "google",
"utmMedium": "cpc",
"utmCampaign": "autumn",
"utmTerm": null,
"utmContent": null,
"landingPage": "https://shop.example/shoes?utm_source=google&utm_medium=cpc&utm_campaign=autumn&gclid",
"referrer": "https://www.google.com/",
"device": "mobile",
"country": "ES"
}firstBrowserAt or firstServerAt is null when that side never sent the
conversion. value, currency and items are each null when the conversion did not carry that field: a row can hold items with no value, or a value with no currency. A conversion only your server saw has no session: its sessionId and the
eleven fields after it are null.
A session row is one session, keyed by sessionId:
{
"sessionId": "6f1c2a54-8f3b-4d7e-9a10-2c4b7e1d9f03",
"startedAt": "2026-10-03T09:32:48.000Z",
"channel": "paid_search",
"aiPlatform": null,
"utmSource": "google",
"utmMedium": "cpc",
"utmCampaign": "autumn",
"utmTerm": null,
"utmContent": null,
"landingPage": "https://shop.example/shoes?utm_source=google&utm_medium=cpc&utm_campaign=autumn&gclid",
"referrer": "https://www.google.com/",
"device": "mobile",
"country": "ES"
}On both kinds of row, aiPlatform, the utm fields, referrer and country are null when the visit carried none.
Both reads exclude staging, internal, bot, synthetic and verification traffic, as Analytics does.
A row's landing page names the click identifiers the visit carried, never their values. If you need a value, keep it with your own order and join on identifier.
Reading page by page
A read with no cursor starts at the first row. Each page holds at most 1,000 rows;
send its cursor in the next read, as ?cursor=, and keep reading while more is
true. Keep the last cursor: your next run sends it and picks up where this one
stopped. The cursor is opaque, so store it as it came and never build one. Each
endpoint has its own.
Rows come in the order they last changed, not by date, so a page is not a day. A page with no rows returns the cursor you sent: if the last page came exactly full, the next one comes empty and the read ends there.
Each key can make 60 requests a minute. Beyond that the read answers 429 with a
retry-after header holding the seconds to wait.
Merging what you read
A conversion's row appears within the hour, once TraceLog has counted its day; a session's appears at once. Each appears again, whole, each time it changes. A row carries no modification date, so merge on its key,
name with identifier for a conversion row and sessionId for a session row,
the row read later winning.
A past day can still change when a late arrival lands on it, and a conversion can move to another day, so group after merging.
Matching Analytics
Group the merged rows by the project-local day in timezone: the day of the earlier of firstBrowserAt and firstServerAt for a conversion and of startedAt for a session. For conversions, sum value per currency. These counts and sums match Analytics for every past day TraceLog has counted, but for the days unverifiableDays lists. The rows of those days are what arrived and may fall
short.
Where the record starts
The record of a project created before this read was released starts 30 days before its release; the earliest day among your merged rows shows where. Earlier days stay out, because their value and items can no longer be read by the rule Analytics used. A project created later is read whole.
A pause
A paused account's reads are refused, with the sentence that says what resumes them. Nothing is lost meanwhile: once it resumes, the same cursor carries on.
Refusals
A refusal after the key is recognised names its code and says what happened, in English:
{
"refused": "tier",
"sentence": "Copying this project's conversions and sessions, row by row, into your data store is included in Business and Agency."
}401, with no body: the key is missing, malformed, unknown or another kind of key.tier(403): the project's plan is not Business or Agency.paused(402): the account is paused; its sentence says what resumes it.cursor_unknown(400): TraceLog did not issue this cursor. Read again without one, from the beginning, merging over what you hold.cursor_restored(410): TraceLog restored its data from a backup. Follow the steps in After a restore, below.
After a restore
A restore can lose rows or changes you already copied. Those rows are never read again, so merging a new read into what you hold keeps rows TraceLog no longer has, and your sums exceed its record.
- Read without a cursor, from the beginning, into an empty copy, merging on each row's key as usual.
- When
moreisfalse, replace what you hold from that endpoint with that copy and carry on from its last cursor. - Do this on each endpoint: each refuses its own cursor.