How we count
Every metric with its definition and denominator, plus the rules that sort visits into channels.
These are definitions version 2026-10-05.1. When a definition changes, the version changes with it, so an old report can always be explained.
Headline metrics
| Metric | Definition |
|---|---|
| Views | Rendered page navigations reported by the SDK. Not prefetches, not HTTP requests |
| Sessions | Journey mode only. Consented tab sessions, 30 min idle, 24 h cap. Not people |
| Conversion rate | Journey: converted sessions divided by sessions, so it never exceeds 100%. Aggregate: goal completions divided by views. The UI always names the denominator |
| Engaged rate | Engaged sessions divided by sessions (journey), or engaged views divided by views (aggregate). A view counts once, however many $engage events it sends |
| Active time | Median of $engage durations. Foreground plus recent interaction, not attention |
| Automated requests | Edge SDK and log drain observations not classified browser. Separate denominator from views, never summed with them |
The Humans filter means actor_class = 'browser': clients that look like browsers. An automated client that passes as a browser is counted here too.
A number that can't be computed, like sessions in aggregate mode, shows as unavailable with the reason, never as zero.
Channels
Evaluated in order, first match wins. The rules follow the default channel group of Google Analytics 4, mapped onto Little Friend's eight channels. utm_medium and utm_source are compared trimmed and lowercased. A referrer on the project's domain or any subdomain of it is internal: the view is not a landing, carries no source and counts no entry, and UTM tags on such a link are ignored. Channel and source are decided at ingest, so these rules apply to data received after they shipped, and older rows keep the channel they were given.
- Paid
utm_mediummatches^(.*cp.*|ppc|retargeting|paid.*)$(cpc,cpm,paid_social), or isdisplay,banner,expandableorinterstitial - Email
utm_mediumorutm_sourcecontainsemailornewsletter, or ise-mail,e_mailore mail; or the referrer is a webmail host (below) - AI assistantsreferrer is
chatgpt.com,chat.openai.com,perplexity.ai,claude.ai,gemini.google.com,copilot.microsoft.com,you.com,phind.com(or a subdomain), or there is no referrer andutm_sourcenames one of those hosts (assistants tag the links they show, for exampleutm_source=chatgpt.com) - Searchreferrer is a known search engine; or
utm_mediumisorganic; orutm_sourceisgoogle,bing,duckduckgo,yahoo,ecosia,brave,yandex,baiduorkagi, or a search engine's host (google.com). Search engine hosts:google.<tld>andgoogle.<sld>.<cc>such asgoogle.co.uk(other Google hosts are not search),bing.com,duckduckgo.comandecosia.orgwith their subdomains,yahoo.com,yahoo.co.jp,search.yahoo.comandsearch.yahoo.co.jpwith an optional country prefix (uk.search.yahoo.com),search.brave.com,yandex.ru,yandex.com,yandex.com.tr,yandex.kz,yandex.by,yandex.ua,ya.ru,baidu.com,m.baidu.com,kagi.com - Socialreferrer is a known social site; or
utm_sourceisfacebook,fb,instagram,ig,linkedin,x,twitter,reddit,youtube,tiktok,threads,bluesky,bsky,mastodonorpinterest, or a social site's host (facebook.com); orutm_mediumissocial,social-network,social-media,sm,social networkorsocial media. Social hosts, each with its subdomains:x.com,twitter.com,t.co,facebook.com,fb.com,fb.me,instagram.com,linkedin.com,lnkd.in,reddit.com,redd.it,youtube.com,youtu.be,tiktok.com,pinterest.com,pin.it,threads.net,threads.com,bsky.app,news.ycombinator.com, the Mastodon hostsmastodon.social,mastodon.online,mas.to,mstdn.social,fosstodon.org,hachyderm.io,infosec.exchange,techhub.social, and any host starting withmastodon. - Referral
utm_mediumisreferral,apporlink - Campaignany other
utm_source - Referralany other external referrer
- Direct or unknownno referrer and no campaign. Labeled "Direct or unknown", because a browser often hides the referrer, so a visit with none may have come from a link
Webmail and email apps
Webmail hosts, each with its subdomains: mail.google.com, outlook.live.com, outlook.office.com, outlook.office365.com, mail.yahoo.com, mail.aol.com, mail.proton.me, app.fastmail.com, and Zoho Mail's regional hosts mail.zoho.com, mail.zoho.eu, mail.zoho.in, mail.zoho.com.au, mail.zoho.jp, mail.zoho.com.cn, mail.zoho.sa, mail.zoho.uk and mail.zohocloud.ca. Most email apps send no referrer, so a link in an email lands in Email when it carries a UTM tag such as utm_medium=email.
Chrome on Android reports a link opened from an app by the app's package name. A known app is stored as the web host its links come from, so the rules above apply.
| Package | Stored referrer | Channel |
|---|---|---|
com.google.android.gm | mail.google.com | |
com.microsoft.office.outlook | outlook.live.com | |
com.yahoo.mobile.client.android.mail | mail.yahoo.com | |
ch.protonmail.android | mail.proton.me | |
com.openai.chatgpt | chatgpt.com | AI assistants |
ai.perplexity.app.android | perplexity.ai | AI assistants |
com.anthropic.claude | claude.ai | AI assistants |
com.google.android.apps.bard | gemini.google.com | AI assistants |
com.microsoft.copilot | copilot.microsoft.com | AI assistants |
com.google.android.googlequicksearchbox | google.com | Search |
com.linkedin.android | linkedin.com | Social |
com.facebook.katana, com.facebook.orca, com.facebook.lite | facebook.com | Social |
com.instagram.android | instagram.com | Social |
com.instagram.barcelona | threads.net | Social |
com.twitter.android | x.com | Social |
com.reddit.frontpage | reddit.com | Social |
com.google.android.youtube | youtube.com | Social |
com.zhiliaoapp.musically | tiktok.com | Social |
xyz.blueskyweb.app | bsky.app | Social |
com.pinterest | pinterest.com | Social |
com.slack | slack.com | Referral |
com.discord | discord.com | Referral |
org.telegram.messenger | t.me | Referral |
com.whatsapp | whatsapp.com | Referral |
Known limits
- UTM tags in the URL fragment (
#utm_source=x) are not read. The script reads the query string only. - Apps on iOS send no referrer. A link opened from an iOS app is Direct or unknown unless it carries UTM tags.
- An unknown Android app is stored under its package name, such as
com.example.readerorcom.example_reader, in Referral. - The Swift SDK sends no referrer: iOS and macOS do not tell an app which app opened its link, so a link without UTM tags that opens an app lands as Direct or unknown.
- App events carry the device's time. On a device whose clock runs more than 10 minutes ahead, or more than 24 hours behind, the collector refuses them as
clock_skew, and they are lost. - An app sends its queued events with the app block of the version that sends them, so events queued before an app update are stored under the new version.
- A server goal that arrives before the app event carrying its correlation id counts as web, since no session holds that id yet. A flush right after the app event sends it at once.
- An app event keeps at most 8 properties: the SDKs read its keys in ascending order, since a native map has no order of its own, and keep the first 8 valid ones. A page keeps the first 8 in the order it wrote them.
Apps
An iOS, macOS or Android app sends to POST /v1/e with the same WireBatch as lf.js, plus one block, a, that names the app. A batch with that block is an app batch. A batch without it is a web batch, and nothing in this section changes for it.
Devices
An app's user agent is its HTTP library, so the collector reads the device from the app block and the families from its platform, never from the user agent. The actor class is always browser: a person using the app, counted under Humans.
| Platform | Device | Device class | OS family | Browser family |
|---|---|---|---|---|
ios | mobile | mobile | iOS | iOS app |
ios | tablet | tablet | iPadOS | iOS app |
ios | desktop | desktop | iOS | iOS app |
macos | any | the device | macOS | macOS app |
android | any | the device | Android | Android app |
Refused app batches
An app batch passes the same rate limits, quotas, clock window and event rules as any /v1/e batch. Allowed origins do not apply to it. These checks take their place, in this order. Each refusal is counted in rejects_daily under the browser source and shows on the data health strip.
| Reason | Status | Check |
|---|---|---|
bad_app | 400 | The app block is well formed: an app id that passes its rule, a platform of ios, macos or android, a device of mobile, tablet or desktop, and a version that passes its rule with no part that looks like personal data. Otherwise the whole batch is refused, and nothing in it is stored |
app_from_browser | 403 | The request carries no Origin header. Browsers send one and app SDKs do not, so an app batch with one is a web page posing as an app. It is refused before Allowed apps is read, so a listed app id does not let it through |
app_not_allowed | 403 | The app id is on the project's Allowed apps, compared without regard to case. An empty list admits no app. Each refusal is also counted per app id, and the data health strip names the app until it is listed |
Endpoints
| Endpoint | Auth | Body |
|---|---|---|
POST /v1/e | public site key inside the body | WireBatch, sent as text/plain JSON (no CORS preflight, beacon compatible) |
POST /v1/server | Authorization: Bearer lfs_... | ServerBatch JSON |
POST /v1/edge | Authorization: Bearer lfe_... | EdgeBatch JSON |
POST /v1/r | public site key inside the body | WireReplayChunk, gzip or plain JSON, sent as text/plain. See Session replay below |
GET /v1/r/config?k=lf_... | public site key in the query | Answers ReplayClientConfig, public, cacheable for 15 s, Vary: Origin. Off for a page on an origin the project does not allow |