Displaying Serge analytics
You are rendering Serge data in your own dashboard or in client reports. This page is the display contract: what each number is, what to call it, and what never to claim — for every acquisition source Serge records (referral, direct, search, tagged, paid, AI), not just the AI slice. The field-level shapes live in the API reference; the rules here are the same ones Serge's own surfaces follow.
Paste it into your LLM
Building the surface with an assistant or a code generator? Copy the whole contract — definitions, rules, and sample responses as plain markdown, generated from the same source as this page — and put it in the context before you ask for the dashboard.
Also fetchable at /docs/displaying-analytics/llm.txt — point a tool or an agent at it directly.
Three populations, never blurred
Serge measures three different things. Most display mistakes are one of them wearing another one's label.
- AI-assistant sessions
- An assistant active on the site itself — fetching, browsing, or acting for a user. This is what the /traffic endpoints count.
- Assistant clicks — organic
- A person who clicked a link inside an assistant and landed on the site. ChatGPT tags its outbound links; Serge counts the landings. Not ads.
- Ad clicks — paid
- A person who clicked a paid placement, identified by a platform click ID or an explicit paid utm_medium. A landing that merely carries UTM parameters is not an ad click.
Paid and organic may sit side by side. They never share a number, and they never share a label.
Wording rules
- 01
Tracked, not driven.
Serge counts what landed and what it did on the site. "12 orders from tracked ChatGPT ad clicks" is supportable; "ChatGPT drove 12 orders" claims incrementality that nobody measured.
- 02
Counts are counts.
No error bars or ± on a count. Below roughly ten events, show the bare count and skip rates — a precise-looking percentage on a noise-sized sample misleads.
- 03
No conversation claims.
No platform exposes prompts or user-level data. Never imply the data shows what people asked an assistant.
- 04
Name the window.
Every number carries its period — the API returns period_start and period_end. Compare only equal windows.
- 05
Platform names verbatim.
ChatGPT, Perplexity, Claude, Gemini, Copilot — brand names are not translated, abbreviated, or folded into a house taxonomy.
The same number, said honestly
Don't"ChatGPT drove $4,200 in revenue."
Do"$4,200 in orders from tracked ChatGPT ad clicks (30 days)."
Don't"Verified AI traffic: 1,020 sessions" — when 117 of them are heuristic detections.
Do"1,020 AI-assistant sessions — 903 verified, 117 heuristic."
Don't"100% completion rate" — from one session.
Do"1 session, 1 completed." Rates start when the sample can carry them.
Per endpoint: what to watch, with a sample
The samples below are the same validated examples the API reference publishes — they cannot drift from the contract. The notes cover only what matters once the number reaches a human.
The wireframes below use the same numbers as the sample responses. They show structure, not styling — bring your own design system.
Traffic sources
GET /api/v1/traffic/sources
coverage is part of the data, not metadata: ai_and_tagged means plain human visits are not recorded on this site — always render the coverage label next to the numbers; presenting partial coverage as "your traffic" is the exact claim these rules ban. When coverage_changed_at falls inside the period, BREAK every time series at it — a mode flip changes the recorded population, and a continuous line across it fakes growth. Referrer hosts and utm_source values are data — verbatim, never translated or grouped into a house taxonomy. The classes partition sessions: never merge paid with ai_assistant_link, and keep direct visible. ai_share_pct renders ONLY when non-null — it is null on partial coverage (wrong denominator) and below 10 sessions; state its definition (assistant sessions + assistant-link clicks, paid excluded) wherever you show it.
Assistant sessions + assistant-link clicks over all recorded sessions. Paid excluded.
ai_share_pct null → show bare counts, no percentage
- Referral · www.techweekly.example604
- Direct512
- AI-assistant sessions · chatgpt486
- Paid ads · chatgpt312
- www.techweekly.example604
- app.agentsolo.example213
- news.ycombinator.com88
Top entry page: /blog/agent-checkout-guide
Sample response
{
"domain": "yourstore.example",
"site_id": "site_9f2c41",
"period": "7d",
"period_start": "2026-07-23T00:00:00.000Z",
"period_end": "2026-07-30T00:00:00.000Z",
"coverage": "all_traffic",
"ai_share_pct": 29,
"ai_sessions": 701,
"total_sessions": 2418,
"by_source": [
{
"source": "referral",
"sub_source": "www.techweekly.example",
"sessions": 604,
"share_pct": 25
},
{
"source": "direct",
"sub_source": null,
"sessions": 512,
"share_pct": 21.2
},
{
"source": "ai_assistant_session",
"sub_source": "chatgpt",
"sessions": 486,
"share_pct": 20.1
},
{
"source": "paid",
"sub_source": "chatgpt",
"sessions": 312,
"share_pct": 12.9
},
{
"source": "search",
"sub_source": "www.google.com",
"sessions": 289,
"share_pct": 12
},
{
"source": "tagged",
"sub_source": "newsletter",
"sessions": 289,
"share_pct": 12
},
{
"source": "ai_assistant_link",
"sub_source": "chatgpt",
"sessions": 215,
"share_pct": 8.9
}
],
"top_referrer_hosts": [
{
"host": "www.techweekly.example",
"sessions": 604
}
],
"coverage_changed_at": "2026-07-25T09:00:00.000Z",
"top_entry_pages": [
{
"url": "/blog/why-aurora-x20",
"sessions": 692,
"by_source": [
{
"source": "search",
"sessions": 401
},
{
"source": "ai_assistant_link",
"sessions": 176
},
{
"source": "direct",
"sessions": 115
}
]
},
{
"url": "/products/aurora-x20",
"sessions": 486,
"by_source": [
{
"source": "paid",
"sessions": 312
},
{
"source": "ai_assistant_session",
"sessions": 174
}
]
}
],
"by_source_daily": [
{
"day": "2026-07-28",
"source": "search",
"sessions": 88
},
{
"day": "2026-07-28",
"source": "paid",
"sessions": 61
},
{
"day": "2026-07-29",
"source": "search",
"sessions": 97
},
{
"day": "2026-07-29",
"source": "paid",
"sessions": 44
}
]
}Traffic overview
GET /api/v1/traffic/overview
Sessions and unique visitors are different numbers — label the one you show. share_pct arrives computed; do not recompute it on a filtered subset. In by_outcome, "abandoned" means the session ended without completing — not that an error occurred. median_duration_ms is a median: label it "median", never "avg".
Sample response
{
"domain": "yourstore.example",
"site_id": "site_9f2c41",
"period": "7d",
"period_start": "2026-07-23T00:00:00.000Z",
"period_end": "2026-07-30T00:00:00.000Z",
"total_sessions": 1284,
"unique_visitors": 1102,
"by_platform": [
{
"platform": "chatgpt",
"sessions": 812,
"share_pct": 63.2
},
{
"platform": "perplexity",
"sessions": 301,
"share_pct": 23.4
},
{
"platform": "claude",
"sessions": 171,
"share_pct": 13.4
}
],
"by_outcome": [
{
"outcome": "completed",
"sessions": 402
},
{
"outcome": "abandoned",
"sessions": 882
}
],
"top_pages": [
{
"entry_url": "/products/aurora-x20",
"sessions": 486
},
{
"entry_url": "/collections/headphones",
"sessions": 210
}
],
"median_duration_ms": 42150
}Purpose split
GET /api/v1/traffic/purpose-split
user_action is an assistant acting on someone's behalf; search is retrieval for an answer; crawl is indexing. Keep the unknown bucket visible — folding it into another category overstates certainty. The split is a composition: one stacked bar or a share list, not four disconnected KPIs.
Sample response
{
"domain": "yourstore.example",
"site_id": "site_9f2c41",
"period": "7d",
"period_start": "2026-07-23T00:00:00.000Z",
"period_end": "2026-07-30T00:00:00.000Z",
"total_sessions": 1284,
"user_action_sessions": 517,
"search_sessions": 604,
"crawl_sessions": 148,
"unknown_sessions": 15,
"by_purpose": [
{
"purpose": "user_action",
"sessions": 517,
"share_pct": 40.3
},
{
"purpose": "search",
"sessions": 604,
"share_pct": 47
},
{
"purpose": "crawl",
"sessions": 148,
"share_pct": 11.5
}
]
}Verification breakdown
GET /api/v1/traffic/verification
verified, declared and heuristic are tiers of evidence, strongest first. A heuristic session never renders under a "verified" label. If you show one total, keep the tier mix one glance away.
903 verified · 264 declared · 117 heuristic
Sample response
{
"domain": "yourstore.example",
"site_id": "site_9f2c41",
"period": "7d",
"period_start": "2026-07-23T00:00:00.000Z",
"period_end": "2026-07-30T00:00:00.000Z",
"total_sessions": 1284,
"verified_sessions": 903,
"declared_sessions": 264,
"heuristic_sessions": 117,
"by_platform": [
{
"platform": "chatgpt",
"sessions": 812,
"verified": 690,
"declared": 96,
"heuristic": 26,
"tier": "verified"
}
]
}Attribution overview
GET /api/v1/attribution/overview
Never sum value across currencies — render one line per currency (value_minor is in minor units). There is no ROAS field by design: Serge does not read spend yet, and computing your own from partial data produces a false number. Attribution basis matters: a platform click ID is hard evidence, a UTM label is self-declared — keep that distinction reachable. by_source classifies orders from DECLARED evidence only and is narrower than the session taxonomy: paid requires a platform-minted click ID (a typed utm_medium never upgrades an order), assistant/tagged classes come from labels, and search/referral/direct cannot exist because an order carries no referrer — render each row's basis next to its number.
| Platform | Orders | Value (USD) |
|---|---|---|
| openai | 71 | $6,429.00 |
| 25 | $2,352.00 | |
| unattributed | 118 | $10,153.00 |
No cross-currency total — by design.
Sample response
{
"site_id": "site_9f2c41",
"domain": "yourstore.example",
"period": "30d",
"period_start": "2026-06-30T00:00:00.000Z",
"period_end": "2026-07-30T00:00:00.000Z",
"total_conversions": 214,
"attributed_conversions": 96,
"unattributed_conversions": 118,
"by_currency": [
{
"currency": "USD",
"conversions": 214,
"value_minor": 1893400
}
],
"by_platform": [
{
"platform": "openai",
"conversions": 71,
"by_currency": [
{
"currency": "USD",
"conversions": 71,
"value_minor": 642900
}
]
},
{
"platform": "unattributed",
"conversions": 118,
"by_currency": [
{
"currency": "USD",
"conversions": 118,
"value_minor": 1015300
}
]
},
{
"platform": "google",
"conversions": 25,
"by_currency": [
{
"currency": "USD",
"conversions": 25,
"value_minor": 235200
}
]
}
],
"by_basis": [
{
"basis": "none",
"conversions": 118
},
{
"basis": "declared_click_id",
"conversions": 84
},
{
"basis": "declared_utm",
"conversions": 12
}
],
"by_source": [
{
"source": "unattributed",
"sub_source": null,
"basis": "none",
"conversions": 118,
"by_currency": [
{
"currency": "USD",
"conversions": 118,
"value_minor": 1015300
}
]
},
{
"source": "paid",
"sub_source": "openai",
"basis": "declared_click_id",
"conversions": 71,
"by_currency": [
{
"currency": "USD",
"conversions": 71,
"value_minor": 642900
}
]
},
{
"source": "ai_assistant_link",
"sub_source": "chatgpt",
"basis": "declared_utm",
"conversions": 8,
"by_currency": [
{
"currency": "USD",
"conversions": 8,
"value_minor": 71800
}
]
},
{
"source": "tagged",
"sub_source": "newsletter",
"basis": "declared_utm",
"conversions": 4,
"by_currency": [
{
"currency": "USD",
"conversions": 4,
"value_minor": 27600
}
]
}
]
}Shapes, auth, and limits live in the API reference. If a metric you want to display is not covered here, ask — the rule you get will match how Serge itself renders the same data.