Open source Built for the AI agent era

Open-source analytics
for every app you ship.

One panel you can check.

Traffic, users and revenue for your websites, iOS and Android apps — side by side, without storing who your users are.

$ <script defer data-site="SITE_ID" src="https://app.vitrus.dev/v.js"></script>

Every number opens the query that counted it — for you, or for ChatGPT and Claude over MCP.
Free plan, no card · 3.0 KB script · Apache-2.0 core · the demo needs no account

The real globe from the dashboard · demo workspace
01The proof

Every number opens the query that made it.

This is the product, not a drawing of it. Click a figure on any page and the SQL, its parameters, the window and the result are right there — the same evidence our AI summary is checked against.

app.vitrus.dev/app
Vitrus dashboard, demo workspace with sample data: the unique-visitors figure opened to show the SQL query, parameters, result and time window behind it

Screenshots are of the shipped dashboard running on a demo workspace, and say so on the image itself — the figures are sample data, not a customer's. Open that workspace yourself →

02Portfolio

Every app you ship. One panel.

Built five apps with AI this year? Stop opening five admin pages to see who signed up. Add one line where your app knows the user is signed in, and Vitrus counts who is new, who came back, who you lost and who started paying — for every app, side by side, web and mobile.

vitrus.identify(user.id) // hashed in the browser, never sent raw
app.vitrus.dev/app?page=portfolio
Vitrus Portfolio page, demo workspace with sample data: one card per app with its active users, a trend line, Web, iOS and Android badges, active, new and lost users, paying users and revenue, under a strip of highlights

Every user — under a made-up name

Open any app and see each signed-in user: returning, one-time or signed up only, their actions, active days and visits — as “Amber Falcon”, because there is no email or name on our side to show.

Hashed before it leaves the browser

The script sends sha256(site + id); the server hashes that again with a secret. Your users' ids — often emails — never reach our servers.

Separate per app, by design

The same person in two of your apps is two unrelated hashes, so nobody — us included — can join them. Email, phone and name fields are dropped in the browser and again on the server.

app.vitrus.dev/app?page=people
Vitrus Users page, demo workspace with sample data: returning, one-time and signed-up-only users, and a list of users under made-up names with actions, active days, visits and an opened row showing actions by day and recent visits

Plus a Lifecycle page per app — new, came back, lost, net, one-and-done, became paying — and every number on all three pages opens its query. Ask ChatGPT or Claude over MCP and they answer from the same numbers. Two optional calls make it sharper: vitrus("signup") and vitrus("plan", { plan: "pro" }). No auth provider to connect, no admin keys handed over. Open the portfolio in the demo → · How it is counted

03Inside

Twenty views. One file underneath.

Real-time, a 3D globe, sessions, funnels, journeys, goals, revenue, retention and Web Vitals — all in one process and one SQLite file. Click a number, read the SQL that produced it.

Read the changelog →

04How it runs

One process. That is the whole architecture.

No ClickHouse to babysit, no Postgres, no Redis, no Kafka. Ingest, the API, the dashboard and the MCP server are one Bun process over one SQLite file.

  • Backups are a file copy. Everything lives in one SQLite file; VACUUM INTO is consistent while it runs.
  • Runs on the small box you already have. A €4 VPS handles a few million events a month.
  • Zero runtime dependencies. A build gate fails if one is added — no transitive supply chain.
Tracker3.0 KB · cookie-free
Ingest/api/d · server-side for crawlers
Query APIevery metric = {sql, params, window, value}
Dashboardno third-party scripts
MCP server/mcp · read-only
Numeric guardan AI sentence with an unproven number is dropped
SQLite · one file
VITRUS, A SINGLE BUN PROCESS

What changed

Two things broke at once.

The audience stopped being only human, and the reporting stopped being only written by people. Most analytics tools were designed before either happened.

01

Measurement broke

ChatGPT, Perplexity and Claude send real visitors — and roughly 70% of them land in GA4's “direct” bucket. Meanwhile GPTBot and ClaudeBot read your pages every day and most tools either ignore them or count them as people. Worse, those crawlers do not run JavaScript, so a browser script cannot see them at all — ours included, until you forward them from your server.

02

Trust broke

Every analytics product is bolting an AI on top that writes your summaries. Those sentences look authoritative and cannot be checked. When a model writes the report — and increasingly when an agent reads it — “can I verify this number?” stops being pedantic and becomes the whole question.

Vitrus is built for both halves: it separates AI referrals from AI crawlers, and it refuses to publish a number it cannot prove — to you, or to your agent.

Platform

Everything you need to understand your website.

Most teams bolt a traffic tool, a funnel tool and an AI plugin together, then try to reconcile the numbers. Vitrus brings acquisition, behavior and conversion into one place — and ties every one of them to evidence.

Acquisition

Where traffic actually comes from. Channels, referrers, campaigns — and AI assistants as a channel of their own.

Behavior

What people do once they land. CTAs, scroll depth, field-level form abandonment, rage clicks.

Conversion

Where you lose them. Ordered funnels show the drop at every step; retention shows who comes back.

Only here

Proof

Every number carries the query that produced it, and you can click it. Unprovable sentences never ship.

Browse all features →

The whole surface

Nothing important is missing.

If you are moving from Google Analytics, Plausible or Umami, this is the checklist. Every item is in every plan, including the free one; how many sites and teammates grows with the plan.

Traffic

  • Pageviews, sessions, unique visitors
  • Live visitors, right now
  • Top pages, entry and exit pages
  • Referrers, channels, UTM campaigns
  • Devices, browsers, OS, screens, languages
  • Country, region and city (from your proxy's headers)
  • iOS and Android apps, with the Swift and Flutter SDKs

Behaviour

  • Signed-in users, new, came back, lost and paying
  • Custom events, from code or an HTML attribute
  • Outbound clicks and form submissions
  • Field-level form abandonment
  • Rage clicks and scroll depth
  • Ordered funnels
  • Retention cohorts

Health

  • Core Web Vitals at p75, from real visits
  • Slowest pages, ranked
  • JavaScript errors, grouped
  • Errors by browser and OS
  • Release tagging for deploy correlation

Control

  • Many sites and apps, one Portfolio, teams and roles
  • Exclude your own visits in one click
  • Skip or mask private URLs in the browser
  • Standard and strict privacy modes
  • Slack, email and webhook digests
  • REST API and a read-only MCP server

Deliberately absent: ad-platform attribution. Session replay exists, but it is off by default and masked by default. Here is why.

Proof

There is a query behind every sentence

Below is a digest in the shape the product actually produces. Click an e# badge: what opens is not an explanation — it is the query that produced the number. The queries are the real ones; the figures are an example, and saying so is the least a product with this claim can do.

weekly digest · last 7 daysdeterministic · works without an LLM
summary 40 unique visitors and 39 pageviews in the last 7 days.
ai 16 sessions came from AI assistants — about 40% of all sessions. Mostly perplexity (9 sessions).
funnel Most form abandonment happened on the “phone” field — 6 people stopped there.

Query that ran

SELECT COUNT(DISTINCT visitor_id) AS value
  FROM events
 WHERE site_id = ? AND ts >= ? AND ts < ? AND bot_kind = ''

Parameters

["demo", 1789003421000, 1789608221000]

Result

40   (previous period: 44)

Query that ran

SELECT source, COUNT(DISTINCT session_id) AS sessions
  FROM events
 WHERE site_id = ? AND ts >= ? AND ts < ? AND bot_kind = '' AND channel = 'ai'
 GROUP BY source ORDER BY sessions DESC

Raw rows

[{"source":"perplexity","sessions":9},{"source":"chatgpt","sessions":7}]

Query that ran

SELECT json_extract(props,'$.field') AS field, COUNT(*) AS abandons
  FROM events
 WHERE site_id = ? AND ts >= ? AND ts < ? AND bot_kind = '' AND name = 'form_abandon'
 GROUP BY field ORDER BY abandons DESC

Raw rows

[{"field":"phone","abandons":6},{"field":"company","abandons":2}]

Numbers never come from the model

Fixed queries run first and produce an evidence bundle. The model only sees that bundle, and it is forbidden from doing arithmetic.

Unprovable sentences are dropped

Every number in the output is matched against the evidence set. No match, no sentence — it is discarded, not corrected.

At worst, it adds nothing

If the model is unreachable or invents something, we fall back to the deterministic text. An LLM can enrich this product; it cannot break it.

a sentence the guard threw away

“Conversion rate jumped 41.7% to an all-time high.”
↳ 41.7 is not in the evidence set — sentence dropped, digest marked incomplete.

Acquisition

Where your traffic really comes from

The first tool that actually measures AI traffic

Roughly 70% of AI referrals land in GA4's “direct” bucket: ChatGPT appends utm_source to outbound links, but Gemini and Perplexity mostly do not. On top of that, most tools lump the bot that reads you together with the human it sends.

  • AI referral — a chatgpt.com referrer is a real visitor
  • AI crawler — GPTBot, ClaudeBot, PerplexityBot read your pages, they bring nobody
  • Which of your pages get read — the only real feedback loop for GEO/AEO work
  • A 41-case golden set verifies the classifier at 100% on every CI run
ChannelSessions
AI assistant ●16
Search54
Social31
Direct22

Separately: GPTBot made 148 requests — not counted as visitors.

What you get out of the box

One line of script. Pageviews, sources and channels, UTM campaigns, entry and exit pages, country, device, browser, language. None of it needs extra code.

  • Time-series chart and a live visitor counter
  • Visit duration, pages per session, bounce rate
  • Custom events — with data-vitrus-event-*, no code required
<script defer data-site="SITE_ID" src="https://app.vitrus.dev/v.js"></script>

Behavior & conversion

Where you are losing people

Ordered funnels

Steps are counted in order: a step must happen after the one before it. Unordered counting treats someone who signs up and then reads the pricing page as a conversion, and inflates the funnel. You define the steps; your first funnel works with no configuration at all.

Home142 · 100%
Clicked CTA85 · 60%
Signed up28 · 20%

Biggest loss at Clicked CTA: 57 sessions.

Session replay is opt-in — and most questions don't need it

Recording someone's screen can mean recording the ID number they typed into your form, so replay is off until you switch it on, every text node and input value is masked in the browser, and password and card fields are never captured. Most of the time the cheaper, privacy-preserving signals answer the question first.

  • Field-level form abandonment — the field name is sent, never its value
  • Rage clicks — a sign that an element isn't responding
  • Scroll depth — aggregate, never per person
Form fieldAbandons
phone61
company18
email4

Retention that admits when it can't

Our visitor id rotates daily (we use no cookies), so cross-day tracking of anonymous traffic is mathematically impossible. Most tools solve this with a persistent identifier or quietly count it wrong. We took a third route: we say we can't, and we open the route where we can.

  • Call vitrus.identify() and the cohort matrix is computed for real
  • The id is hashed one-way; the raw value never touches disk
  • A day that hasn't happened yet is never shown as “0% returned”
CohortDay 0+1+2+3
Sep 08100%42%31%28%
Sep 09100%38%29%·
Sep 10100%45%··

· hasn't happened yet — an unmeasured day is never rendered as zero.

Proactive

The summary comes to you

Slack, email, or a plain webhook. Weekly or daily. Three rules hold it together.

Never sent twice

Getting the same weekly summary twice is worse than not getting it. One ledger entry per period.

A failed send is never lost

Network errors are not written to the ledger, so the next tick retries. You don't lose that week.

Empty periods stay silent

No “0 visitors” email for a week with no traffic.

05Agents

Ask your AI. It cites the query.

Connect ChatGPT, Claude, Cursor or Claude Code in a minute. Your assistant reads your analytics — read-only — and every number it gets back comes with the SQL behind it, so it can cite instead of assert, and you can check what it told you.

  1. Add the server. https://app.vitrus.dev/mcp as a connector — or one click in Cursor.
  2. Sign in and pick a workspace. OAuth: the app reads that one workspace and nothing else.
  3. Ask. “Which AI assistants sent us people this month, and did they convert?”
ChatGPTClaudeClaude CodeCursorGemini CLIAny MCP client
  • 15 read-only tools. Any metric by any of 20 dimensions, real-time, AI traffic, revenue, goals, funnels, journeys, retention, vitals, errors.
  • Claude Code plugin with four skills. Weekly report, traffic-drop check, measurement plan, AI-search audit — claude plugin marketplace add Vitrus-Dev/vitrus
  • Yours to cut off. Every connection is listed under Account → Connected apps. Removing someone from the team also ends their apps' access.
Claude · connected to Vitrus
Which AI assistants sent us people in the last 30 days?
tool vitrus · query_stats { "by": "source", "days": 30, "filters": [{ "field": "channel", "value": "ai" }] }

ChatGPT sent the most: 188 visitors, then Claude 97, Perplexity 92 and Gemini 43.

Source: SELECT source, COUNT(DISTINCT visitor_id) … WHERE channel = 'ai' AND bot_kind = '' …

AI crawlers (GPTBot, ClaudeBot) are not in these numbers — they read pages but bring nobody.

Real tool output from the demo workspace (sample data). The query is returned with the rows.

Verified identity

“GPTBot visited” means a request said it was GPTBot

Every analytics tool in this category identifies a bot by reading its user-agent — a sentence the client wrote about itself. We report what a request proved separately from what it claimed, because that distinction is the whole product applied to bots.

A signature is evidence. A user-agent is a claim.

Web Bot Auth — an IETF draft built on RFC 9421 HTTP Message Signatures — lets an agent sign its requests. Anthropic, OpenAI, Perplexity and Common Crawl have begun. When a signed request arrives we check it against the Ed25519 key its operator publishes, and we can name the signer.

  • verified — a signature we checked, with the directory that vouched for it
  • claimed — the user-agent matches a known bot, nothing was signed
  • human — neither

What this deliberately does not say: an unsigned request is not evidence of anything. Almost nothing signs yet, and calling unsigned traffic suspicious would replace one unprovable claim with another.

LabelRequests
verified ●1,284
claimed9,610

Example figures. The verified row is the one nobody else can show you — how it works.

Privacy

No cookies, no consent banner, no persistent id.

The visitor id is a one-way hash that changes every day: hash(secret_salt + day + site + ip_prefix + browser). The IP is never stored — it is only an input to the hash.

Works without cookies

No persistent identifier, no retroactive identity stitching. Most sites can drop the banner entirely.

Respects Do Not Track

On by default. A privacy stance that isn't a marketing line should show up in the defaults.

Your data stays yours

Run the open-source core on your own infrastructure, or use the cloud. Either way it is never sold or shared.

Optional

Strict mode

One switch per site drops country, screen size, referrer query strings and cross-day identity — enforced on the server, where the browser script cannot bypass it.

Same for the AI layer: no model is enabled by default. If you turn one on, it's either Ollama on your own server or your own API key — and what reaches the model is an aggregated evidence bundle, never raw visitor data.

Compare

How we're different

Most individual features exist somewhere. The bold rows exist nowhere else — and where a rival is ahead of us, we say so below the table.

Checked 24 September 2026 against each product's own documentation (mobile SDKs: 18 October 2026). These products ship quickly; a comparison table with no date on it is a claim about the present that quietly rots. If a cell is wrong, tell us and we will fix it.

CapabilityUmamiPlausibleRybbitDatabuddyVitrus
Cookie-free, no consent banner✓✓✓✓✓
Self-hostable✓✓✓✓✓
Services to installPostgresPG + ClickHousePG + ClickHousePG + CH + Redisnone · one SQLite file
Daily salt on the visitor id✓✓optional, off by default—always on
Funnels, journeys and goals✓✓ (funnels on paid plans)✓✓✓
Proactive digest (arrives unasked)—alerts✓✓✓
AI usage metered separatelynononoyes, creditsnever
Local LLM / bring your own key————✓
Session replay✓—✓—opt-in, masked by default
Official Swift (iOS) and Flutter SDKscommunity onlyEvents APIReact Native onlyHTTP API✓
All your apps in one panel: users, plans, revenue————✓
User ids hashed in the browser, never stored raw—no user idssalting off by default—✓
Clickable query behind every number————✓
Unprovable AI sentences auto-dropped————✓

Where others are ahead of us today. Umami has attribution reports and custom boards; Plausible imports Google Analytics history, and Rybbit imports Plausible exports (we import from Umami only); and both run on databases built for far larger volumes. If one of them is what you need, pick that tool.

Full comparisons →

Pricing

Pay for volume, not for questions

Pageviews and custom events count the same. AI messages are not metered — the digest is deterministic anyway, so rationing it would be artificial scarcity.

Free

Free
10K events/mo
  • Evidence panel — the query behind every number
  • AI referrals separated from AI crawlers
  • Funnels, retention, Web Vitals, error tracking
  • Users, lifecycle and portfolio
  • Weekly deterministic digest
  • Strict privacy mode and share links
  • Cookie-free, no consent banner required
Get started

Starter

$12 /mo
100K events/mo
  • Everything in Free
  • Up to 10 sites or apps, 3 team members
  • 2 years of data retention
  • Slack and email digests
  • MCP server for AI agents
  • Bring your own LLM key, or local Ollama
Start now

Pro

$29 /mo
1M events/mo
  • Everything in Starter
  • Up to 50 sites or apps, 10 team members
  • 20,000 session recordings a month
  • Click and scroll heatmaps
  • 5 years of data retention
Start now

Business

$99 /mo
10M events/mo
  • Everything in Pro
  • 10M events a month
  • Unlimited sites and apps
  • 25 team members
  • Priority email support
Start now

Enterprise

Custom
Unlimited events
  • Everything in Pro
  • On-premise or a dedicated cloud instance
  • SSO, audit log, DPA
  • Unlimited retention
  • Priority support
Contact us

Going over the limit does not cut you off: data keeps flowing inside a 20% grace band with a warning in the dashboard. Analytics you failed to collect never comes back.

Full plan comparison →

FAQ

Frequently asked

What does “provable” mean, concretely?

Every metric is defined as a fixed SQL query. When a digest is produced those queries run and their results are stored as evidence records. Click a number in the dashboard and you see the query that ran, its parameters and the raw rows. An LLM may rephrase that bundle but cannot produce a new number — the moment it does, the output gate drops the sentence.

Do you store my users' emails or names?

No — and not because of a setting: the data never reaches us. When your app calls vitrus.identify(user.id), the script hashes the id in your user's browser before anything is sent; the server hashes it again with a secret and stores only that. Email, phone, name, address and similar fields are dropped in the browser and again on the server. The Users page shows each person under a made-up name such as “Amber Falcon”, so you can follow someone's activity without anyone learning who they are — and the same person in two of your apps cannot be linked, by us or by anyone.

Can my AI assistant tell me how all my apps are doing?

Yes. Connect ChatGPT, Claude, Cursor or Claude Code over MCP and ask “which of my apps lost the most users this week?”. get_portfolio and get_lifecycle answer with counts only — no list of people — and the query behind each number, so the assistant cites instead of guessing.

Does my data go to an LLM provider?

Not unless you want it to. No model is enabled by default; the digest is generated deterministically. If you turn one on you have two options: Ollama on your own server (data never leaves the box) or your own API key. In both cases what reaches the model is an aggregated evidence bundle, not raw visitor data.

How do you count unique visitors without cookies?

A one-way hash built with a secret salt that rotates daily: hash(salt + day + site + ip_prefix + browser). The IP is never stored, it is only an input. The next day the same person hashes to a different value; there is no persistent identifier.

Is there session replay?

Yes, and it ships the other way round from most tools. Recording a visitor's screen can mean recording the ID number they typed into your form — if masking misses one field, you created your customer's compliance problem. So replay is off by default, a page has to opt in, every text node and input value is masked unless you unmask an element on purpose, password and card fields are never captured, and Do Not Track always stops it. Field-level form abandonment, rage clicks and scroll depth still answer most of the same questions without recording anyone.

Can I run it on my own server?

Yes — the core is Apache-2.0. One process, no second service to install (the database is embedded). The cloud version takes on team management, quotas and hosting; the engine is identical and no feature is held hostage.

Can it replace GA4?

For most sites, yes. Not for ad-platform attribution (Google/Meta Ads ROAS) — we deliberately don't do that. The question Vitrus answers is “where is my traffic coming from, where do people drop off, and is this number correct?”

Don't trust the dashboard. Check it.

One line to install. First digest a week later. Or look around first — the demo needs no account.

Get started free → Try the live demo