Data queries
Overview KPIs, analytics, ad-hoc funnels and retention, the user directory, and value-user segments.
Retidal’s data-query surface answers “what happened” — KPIs, event/visit breakdowns, attribution, funnels, retention cohorts, and per-user detail. Every endpoint on this page is read-only (VIEWER+ is enough).
Authentication
Section titled “Authentication”This is a console-session surface, with one scoped exception
Every endpoint below authenticates with the trackly_session session cookie — not a project API key.
If you’ve used the ingestion or MCP surfaces, don’t assume the project key/secret pair also opens these
routes; it doesn’t. The single exception is GET /api/projects/{projectId}/metric-definitions/{metricId}/query, covered under
Metric definitions below, which also accepts a scoped API key with the
metrics:read scope.
Overview
Section titled “Overview”GET /api/projects/{projectId}/overview?range=todayThe dashboard’s headline KPIs (visits, conversions, conversion rate, active links, total spend) with a
previous-period delta, plus a health block (ingest/postback/reach/incentive each ok/warn/
crit/disabled) and a 5-step funnel summary (clicks → visits → events → conversions → postback).
range defaults to today; other relative labels (yesterday, etc.) are accepted.
Percentages here are rounded to 1 decimal. kpis[].value for total spend is in yuan (already divided
from the stored minor units) and can be fractional — this is one of the few aggregates that is not in
minor units; check the field before assuming cents everywhere.
Event, visit, and attribution analytics
Section titled “Event, visit, and attribution analytics”GET /api/projects/{projectId}/analytics/events?startDate=&endDate=GET /api/projects/{projectId}/analytics/visits?startDate=&endDate=GET /api/projects/{projectId}/analytics/attribution?startDate=&endDate=startDatestringYYYY-MM-DD, inclusive. If either startDate or endDate is missing or invalid, the route silently
falls back to a rolling default window instead of erroring — 30 days for events/visits/attribution, 7 days
for growth (below).
endDatestringYYYY-MM-DD, inclusive.
analytics/events returns byDay / byType (top 15 event names) / byDevice (top 10 device types),
internal events excluded. analytics/visits is the same shape for sessions (by day/source/device/landing
page). analytics/attribution breaks down first-touch/last-touch attribution.
There is no /analytics/funnel?funnelId= endpoint
Older references to a /analytics/funnel?funnelId= query param style are wrong — ad-hoc funnel math is
POST /api/projects/{projectId}/funnel/calculate (below); saved funnel definitions are plain CRUD at /funnels.
Growth (AARRR)
Section titled “Growth (AARRR)”GET /api/projects/{projectId}/growth?startDate=&endDate=GET /api/projects/{projectId}/growth/metricsGET /api/projects/{projectId}/growth/engagementGET /api/projects/{projectId}/growth/activationGET /api/projects/{projectId}/growth/revenueSame startDate/endDate convention, default window 7 days when omitted/invalid.
Ad-hoc funnels
Section titled “Ad-hoc funnels”POST /api/projects/{projectId}/funnel/calculate{ "steps": ["page_view", "click_register", "register_success"], "windowHours": 24 }All fields are optional (unset ones fall back to your saved growth definition), but POST /api/projects/{projectId}/funnel/calculate
needs at least 2 effective steps or you get 400. Three request fields change the shape of the
response, and only one takes effect at a time:
| If you send | You get back | Precedence |
|---|---|---|
comparePeriod: true |
{ current, previous, change, _meta } — current vs. previous period |
Wins over breakdownBy if both are set |
breakdownBy (a valid dimension) |
{ breakdown: [...], _meta } — one funnel per distinct value |
|
| neither | { steps: [...], overall, _meta } — flat result |
Default |
breakdownBy/filterBy only accept one of: utmSource, deviceType, visitorId, userId, sessionId,
account — anything else is silently normalized to “no breakdown,” not an error. Step conversion rates are
raw floats 0–100 (not rounded); avgTimeToNext is in seconds.
Saved funnel definitions are separate CRUD at /funnels (create/update/delete require ADMIN; read is
VIEWER).
Retention / cohorts
Section titled “Retention / cohorts”POST /api/projects/{projectId}/retention{ "startEvent": "sign_up", "returnEvent": "page_view", "granularity": "week", "cohortStart": "2026-06-01", "cohortEnd": "2026-07-01" }granularity is day | week | month; week buckets snap to Monday, month buckets to the 1st.
retentionDays (default [0, 1, 3, 7, 14, 30]) picks which intervals get computed.
The retention matrix has dynamic keys
Each row’s retention object is keyed by the stringified interval you asked for — so a
retentionDays: [0, 7, 30] request produces keys "0", "7", "30", not a fixed schema. Values are
percentages 0–100 rounded to 2 decimals (unlike the funnel endpoint’s unrounded rates). The top-level
response is one of two shapes distinguished by a literal segmented boolean that’s always present:
segmented: false gives you a flat matrix/curve; segmented: true (only when segmentBy resolves to
a valid dimension — same whitelist as breakdownBy above) gives you a segments array, each with its own
matrix/curve.
Visitors, users, and value segments
Section titled “Visitors, users, and value segments”GET /api/projects/{projectId}/visitors?page=&pageSize=&search=&startDate=&endDate=GET /api/projects/{projectId}/visitors/{visitorId}?days=90 — full visitor timeline
GET /api/projects/{projectId}/users?locale=&hasEmail=&hasPhone=&paidStatus=&valueTier=&minLtv=&maxLtv=&sort=...GET /api/projects/{projectId}/users/export.csvGET /api/projects/{projectId}/users/{userId}/metaGET /api/projects/{projectId}/users/{userId}/overviewGET /api/projects/{projectId}/users/{userId}/visitsGET /api/projects/{projectId}/users/{userId}/eventsGET /api/projects/{projectId}/users/{userId}/profileGET /api/projects/{projectId}/users/{userId}/valueGET /api/projects/{projectId}/users/{userId}/reach-history
GET /api/projects/{projectId}/value-users/summaryGET /api/projects/{projectId}/value-users/queuesGET /api/projects/{projectId}/value-users/queues/{queueKey}?limit=&offset=
GET /api/projects/{projectId}/saved-audiencesPOST /api/projects/{projectId}/saved-audiences (EDITOR)The user directory (/users) supports the widest filter set of any list endpoint here — locale, contact
info presence, LTV/balance/usage ranges, purchase recency, and free-text userIdLike. queueKey on the
value-users queues uses the same six-value enum as
campaign audience filters: new_paid_activation,
key_created_low_usage, high_balance_inactive, high_value, low_balance_renewal, churn_risk_paid.
Saved audiences created here are reusable by savedAudienceId in both campaign audienceFilter and trigger
match.userFilter.
Metric definitions
Section titled “Metric definitions”GET /api/projects/{projectId}/metric-definitionsGET /api/projects/{projectId}/metric-definitions/{metricId}PUT /api/projects/{projectId}/metric-definitions/{metricId}POST /api/projects/{projectId}/metric-definitions/{metricId}/publishPOST /api/projects/{projectId}/metric-definitions/{metricId}/rollback { "targetVersionId": "..." }GET /api/projects/{projectId}/metric-definitions/{metricId}/query?from=&to=Metric definitions are versioned: PUT /api/projects/{projectId}/metric-definitions/{metricId} only mutates a draft; publish
cuts an immutable version (optionally kicking off a backfill job); rollback reactivates a prior version.
query reads the active version’s value over a window — from/to are Unix milliseconds, both
required, and the query runs against the underlying transactions data source. Trying to mutate a
published/archived metric with PUT /api/projects/{projectId}/metric-definitions/{metricId} returns 409.
This is the one query endpoint a project API key can reach
metric-definitions (list/get/query = read; create/update/publish/rollback = write) accepts a scoped API
key with metrics:read or metrics:write in addition to the session cookie — call it out if you’re
building an automated dashboard that shouldn’t hold a browser session.