Skip to main content

Base URL and authentication

Takes 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. In particular the per-session endpoints take site_id and nothing else.
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.

What a session is, and what it is not

A session_id is an opaque first-party cookie value. It groups one visit. It is not a person, it is not an account, and it does not identify anyone by itself. Traceten never returns an IP address, an ip_hash, a user agent, a language or a timezone from these endpoints, and visitor_id is never returned at any depth in any response on this page.
One class of field is an exception, and it is yours rather than ours: the page URLs, in entry_url, exit_url and page_urls. We do not generate those, so we cannot make a guarantee about them on your behalf. If you do not want personal data in this API, keep it out of your URLs. See what we collect.

Page URLs

A page URL arrives by two paths with two different guarantees, so no single statement covers it. From the browser snippet, sanitizeUrl (packages/snippet/src/track.ts:389-415) rewrites the URL before it is sent. It works only on the QUERY STRING, and there it does exactly three things:
  • It replaces the VALUE of any query parameter whose name matches one of 19 exact names, compared case-insensitively: email, e, mail, phone, tel, name, firstname, lastname, fname, lname, token, access_token, id_token, code, state, password, pwd, ssn, dob (the PII_PARAMS list, packages/snippet/src/track.ts:182-202).
  • It also replaces the value of any parameter whose name contains token, secret or key.
  • Replaced values become the literal string REDACTED. The parameter NAME is kept.
Everything else is sent verbatim. In particular:
  • The path is never touched. /u/jane.doe@example.com and /reset/<token> arrive intact.
  • The fragment (#...) is never examined. The redaction reads URL.searchParams, which cannot see past the #, and the fragment survives into the value we store. An OAuth implicit-flow callback such as https://example.com/cb#access_token=eyJ...&id_token=... is sent verbatim, even though access_token and id_token are on the list above. Being in the fragment is what makes the difference:
    If your app receives tokens in a fragment, do not let Traceten see that page. Strip the hash before the snippet runs, or exclude the callback route.
  • Any query parameter not on that list is sent as-is, including ones that plainly carry personal data under another name, such as ?customer=jane.doe@example.com or ?address=....
  • If the URL cannot be parsed, it is sent unchanged.
From a server-side SDK, none of the above applies. Server-side ingestion does not run the snippet, so sanitizeUrl never executes and the URL is stored exactly as your code supplied it. If you send events from your server, scrub URLs before you emit them. Nothing downstream does it for you. A session is one visit. If your question counts people rather than visits, use the Visitors API, which returns one row per visitor and can be filtered by source, campaign, landing page, country, city, device and browser. A visitor row carries latest_session_id, so a person leads to their visits, and each visit opens here with /detail. For what a visit actually did, event by event, use GET /v1/visitors/{handle}/timeline. It returns the events for a page of one person’s visits, so a single request covers the trail across every visit they made rather than one visit at a time. Neither direction accepts an identity. No endpoint takes a name, an email address, an IP address or a visitor_id as an input, and none returns one. Visitor rows are addressed by an opaque handle that is sealed against a single site, so a handle from one site returns 404 on another, and nothing links a person’s activity across two customers.

GET /v1/sessions

One row per session, newest first, with the AI source, the confidence and the detection method behind it.

The window, and what happens when you ask for more than we kept

If you send neither from nor to, the window is the last 30 days, not all of history. Send both together, or neither: a partial range is a 422. You may ask for a window reaching further back than we retain, and you will get an answer. It will cover only the part we still hold. Session records are guaranteed for at least 730 days, and past that boundary the store does not report “no data”, it reports a smaller number, precisely and confidently. So meta always tells you which window was actually answered:
meta is present on every response, clamped or not, so you never have to branch on its absence. Label your charts with effective_window, not with what you asked for.

Query parameters

Paging depth

(page - 1) * page_size may not exceed 100,000. Past that you get a 422. The database reads every row it skips, so a very deep page is the most expensive request this endpoint can serve. If you are paging that far you want a narrower window or a filter, not a higher page number. campaign is a substring search rather than an exact match because campaign names are free text with no canonical form: spring_sale and spring-sale are two different campaigns, and an exact filter would silently return nothing for a near miss.

Reading the response

is_dark_traffic is true when no header identified the source, so the classification came from other signals rather than a Referer or a utm_source. This is the ~70% of AI traffic that arrives with nothing to read, and it is the reason this product exists. stage is a number identifying which detection check made the decision, recorded so you can debug a classification alongside detection_method. 0 means no AI source was identified. active_time_seconds is foreground time on page, summed from what the snippet measured. It is not wall-clock session duration. 0 means nothing was measured, not that the visit was instantaneous. If you want wall-clock, subtract first_event_ts from last_event_ts, both of which are returned. utm_campaign and ref are informational. Neither influenced the ai_source, detection_method or stage on the same row. They are also customer-supplied free text echoed back unchanged, so escape them before rendering. city and country_code are empty when they could not be resolved. Empty is a real, permanent value. Render it as a dash, not as the word “unknown”.

GET /v1/sessions/{id}/detail

Everything known about one session.
The path is /detail, not /{id}. detection_trace is why the classifier decided what it did, so you can argue with a classification you disagree with: the rules that fired and the other evidence behind the score. One field is removed before it reaches you: fingerprint_id, an identifier that is shared across sites by design, which is exactly why we do not hand it out. Nothing else in the trace is withheld. A 404 means the session is no longer retained. Session records live for at least 730 days and at most about 760. The store’s TTL is toDateTime(partition_month) + INTERVAL 760 DAY, set by packages/db/clickhouse/migrations/071_raise_event_and_session_retention.sql. That migration is an ALTER, so the earlier files that created and rebuilt the table still read 120 and are no longer the live definition. partition_month is the start of the month the session began in, so a session that started on the 31st is measured from the 1st: 760 minus up to 30 days of slack leaves a guaranteed floor of 730. That floor is SESSION_GRAIN_RETENTION_DAYS in packages/db/src/clickhouse/queries.ts, and it is the number this API clamps to. 730 is the floor you can rely on, not the point of deletion. If you are restating our retention in your own privacy notice, quote the range. A session can outlive its own events. When that happens you get a row back with an empty page_urls, which is the honest answer, rather than a 404 that would read as “no such session”.

exit_url, and why the two endpoints can disagree

exit_url is the last page measured in the session, the counterpart to entry_url. Both are page URLs, so the same query-parameter redaction applies to each. The two endpoints compute it differently, and for older sessions they can return different answers for the same session:
  • This endpoint derives it from the session’s own event records, so it is populated for the full raw-event retention window.
  • GET /v1/sessions reads a value summarized when the session was recorded. Sessions that started before we began recording exit pages have no such value, so the field comes back empty there.
Empty always means “not recorded”, never “the visitor did not leave a last page”. Every session that viewed a page has one. If you need the exit page for a session older than the feature, read it from this endpoint. One consequence worth stating plainly: a visit whose only page-load event arrived after a late consent grant still reports an exit page. We count both ordinary page loads and that first post-consent event as page loads, so a single-page visit reports the same URL for entry_url and exit_url rather than reporting an entry with no exit.

GET /v1/sessions/{id}/journey

Every AI touchpoint leading to this session’s conversion, with the credit each one gets under all four attribution models at once.
All four models come back in one response so you can let a user switch between them without another request. Within a model, credit_fraction sums to 1.0 whenever there is at least one AI touch. The session_id in every attribution entry is copied verbatim from touches, so you can join the two on it. All four arrays are built from the same touch list, so they are all populated or all empty. They never disagree about which sessions are involved, only about how much credit each one gets. The single-touch example above is the degenerate case where every model agrees; the models diverge once there are two or more touches. When conversion is null the session has not converted yet. The model arrays are then a preview of how credit would be split, and every revenue_cents_attributed is 0.

Errors

Standard error shapes apply.

Next