Lodd Documentation
Last updated: 2026-06-04
Quick start
One prompt to set up: tell your coding agent "Add lodd.dev analytics to this project." It handles authentication, script embedding, and deployment. See /llms.txt for the full agent setup flow.
For a tool-specific walkthrough, see the guides for Claude Code, Cursor, and Codex.
Lodd is deliberately read-only. It does not trigger A/B tests, roll back deployments, or mutate site behaviour. Coding agents already have those capabilities — they read analytics, decide what to change, then write the code. Lodd provides the signal; the agent closes the loop.
Tracking script
The tracking script is a single <script> tag added to your HTML <head>:
<script defer src="https://lodd.dev/tracking/v1.js"
data-site-id="YOUR_SITE_ID"
data-tracking-secret="YOUR_SECRET"></script>
This automatically tracks page views, session duration, SPA navigations, referrers, UTM parameters, device info, and page load time. No cookies. Country-only geo. Bots are detected and tagged.
Custom events
Track any user action with window.ca.track():
window.ca.track("event_name", { key: "value" })
event_name— string, max 100 charactersproperties— optional object, max 10KB when serialised
Events count towards your monthly event limit alongside page views.
Revenue tracking
Attach revenue to any event by including revenue and currency in properties:
window.ca.track("purchase", {
revenue: 49.99,
currency: "EUR",
plan: "pro"
})
revenue— number (extracted from properties, stored separately for aggregation)currency— 3-letter ISO 4217 code (e.g. "EUR", "USD", "GBP")
Revenue fields are automatically extracted from properties and stored in dedicated columns. They appear in get_event_counts as total_revenue, avg_revenue, and revenue_currency.
Other properties (like plan in the example above) are kept in the properties object as usual.
Common patterns
E-commerce funnel:
window.ca.track("add_to_cart", { product: "Widget", revenue: 29.99, currency: "USD" })
window.ca.track("checkout_start")
window.ca.track("purchase", { revenue: 29.99, currency: "USD", order_id: "ORD-123" })
Then query the funnel:
"Show me the conversion funnel from add_to_cart to checkout_start to purchase"
Scroll depth:
const tracked = new Set();
const observer = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (e.isIntersecting && !tracked.has(e.target.id)) {
tracked.add(e.target.id);
window.ca.track("scroll_depth", { depth: e.target.id });
}
});
});
["25", "50", "75", "100"].forEach(pct => {
const el = document.getElementById(`scroll-${pct}`);
if (el) observer.observe(el);
});
Place invisible markers in your content:
<div id="scroll-25" style="height:0"></div>
<!-- ... content ... -->
<div id="scroll-50" style="height:0"></div>
Site search:
function onSearch(query) {
window.ca.track("site_search", { query: query.slice(0, 200) })
// ... your search logic
}
Then ask your agent: "What are the most common search terms on my site?"
Form submissions:
form.addEventListener("submit", () => {
window.ca.track("form_submit", { form: "contact", page: location.pathname })
})
Signup / login:
window.ca.track("signup_click", { method: "google" })
window.ca.track("signup_complete", { plan: "free" })
Server-side tracking
For API products, background jobs, and webhook handlers where there's no browser, send events directly via HTTP:
async function trackEvent(event, props) {
try {
await fetch("https://lodd.dev/v1/track", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Tracking-Secret": process.env.TRACKING_SECRET,
},
body: JSON.stringify({
site_id: process.env.SITE_ID,
type: "event",
event_name: event,
properties: props || {},
session_id: crypto.randomUUID(),
url: "https://your-api.com/internal",
timestamp: new Date().toISOString(),
browser: "server", os: "node", device_type: "server",
}),
});
} catch { /* tracking is best-effort */ }
}
No SDK required. 15 lines. Fire and forget. If it fails, your app doesn't notice.
Actor-based analytics
Pass an actor — a hashed identifier — to enable per-actor analytics:
// Browser — use a server-rendered hash (Web Crypto is async)
// Your server renders: window.__ACTOR = "{{ sha256(userId) }}"
window.ca.track("login", { method: "google" }, window.__ACTOR);
// Server (fetch snippet)
{ ...payload, actor: sha256(userId) }
// Node SDK
lodd.track("api_call", { endpoint: "/v1/query" }, { actor: sha256(userId) });
The actor is an opaque string. Lodd never sees the original identifier. Once events have actors, three new tools become available:
- get_active_actors — distinct actors with event counts and revenue
- get_actor_activity — event timeline for one actor
- get_actor_retention — cohort retention by actor
Important: never send raw emails, user IDs, or API keys as the actor. Always hash first. Lodd cannot and will not bridge actors to CRM records or external databases — this is a GDPR design choice, not a missing feature. The actor is a one-way hash; Lodd has no way to reverse it.
Note on retention accuracy: actor identity is only as stable as the identifier you hash. For best results, hash a stable backend ID rather than a client-side token.
MCP tools reference
42 tools total: 2 authentication + 35 authenticated.
Note: get_timeseries returns { buckets: [...], annotations?: [...] } — annotations from the same period are included automatically. get_snapshot includes last_annotation when one exists from the past 7 days.
Authentication (unauthenticated)
| Tool | Description |
|---|---|
authenticate(email) |
Send verification code to email |
verify_code(email, code) |
Exchange code for API key |
Sites
| Tool | Description |
|---|---|
list_sites() |
All sites you own |
create_site(name, domain) |
Returns site ID, tracking secret, script tag |
exclude_my_ip(site) |
Stop tracking your own visits (per site) |
Analytics
| Tool | Description |
|---|---|
get_snapshot(site) |
Today vs yesterday quick comparison |
get_analytics(site, period, filters?) |
Aggregate stats with period comparison |
get_timeseries(site, period, interval?, filters?) |
Hourly or daily time buckets |
get_funnel(site, period, steps, filters?) |
Multi-step conversion funnel (pageviews + events) |
get_realtime(site) |
Active visitors in last 5 minutes |
get_performance(site, period, group_by?) |
Page load time avg/median/p95 |
Breakdowns
| Tool | Description |
|---|---|
get_pages(site, period, url_contains?, filters?) |
Top pages with bounce rate + avg duration |
get_traffic_sources(site, period, filters?) |
Referrers, UTM, trackable links with engagement |
get_countries(site, period, filters?) |
Visitors by country with bounce rate |
get_tech_breakdown(site, period, filters?) |
Browser, OS, device distribution |
get_entry_exit_pages(site, period, filters?) |
Where sessions start and end |
get_bot_report(site, period) |
Bot/crawler traffic by user agent |
Conversion attribution
| Tool | Description |
|---|---|
get_conversion_pages(site, event_name, period) |
Pages viewed before a conversion, with rate + time |
get_source_conversions(site, event_name, period) |
Traffic sources ranked by conversion rate |
Custom events
| Tool | Description |
|---|---|
get_event_counts(site, period, filters?) |
Event totals, sessions, revenue |
get_events(site, period, event_name?, limit?) |
Individual event records with properties |
get_event_timeseries(site, event_name, period, filters?) |
One event over time |
Usage
| Tool | Description |
|---|---|
get_usage() |
Plan, events used, monthly limit |
Key management
| Tool | Description |
|---|---|
create_api_key(name?) |
Generate a new API key (shown once) |
list_api_keys() |
All keys with status and last used |
revoke_api_key(key_id) |
Permanently deactivate a key |
Actor analytics
| Tool | Description |
|---|---|
get_active_actors(site, period, limit?) |
Distinct actors with event counts, first/last seen, revenue |
get_actor_activity(site, actor, period, limit?) |
Event timeline for one actor hash |
get_actor_retention(site, period) |
Cohort retention by actor |
Annotations
| Tool | Description |
|---|---|
create_annotation(site, content, timestamp?) |
Record a user-facing change (deploy, redesign, campaign) |
list_annotations(site, period) |
List annotations within a time period |
Trackable links
| Tool | Description |
|---|---|
create_trackable_link(site, destination_url, source_type, label?) |
Short URL with source attribution |
list_trackable_links(site, status?) |
All links with click stats |
get_link_clicks(link, period) |
Click data for one link |
Team access
| Tool | Description |
|---|---|
share_site(site, email) |
Give another user access to a site (owner only) |
list_members(site) |
List users with access and their roles |
remove_member(site, email) |
Remove a user's access (owner only) |
Filters
Most analytics and breakdown tools accept optional filters:
| Filter | Format | Example |
|---|---|---|
filter_country |
2-letter ISO code | "US", "DE", "NO" |
filter_browser |
Substring match | "Chrome", "Safari" |
filter_os |
Substring match | "iOS", "Windows" |
filter_device_type |
Exact match | "desktop", "mobile", "tablet" |
filter_utm_source |
Exact match | "twitter", "newsletter" |
filter_referrer_contains |
Substring match | "google", "reddit" |
Breakdown tools exclude the dimension they group by (e.g. get_countries doesn't accept filter_country).
Period formats
| Format | Meaning |
|---|---|
"today" |
Since midnight |
"yesterday" |
Previous full day |
"7d" |
Last 7 days |
"30d" |
Last 30 days |
"90d" |
Last 90 days |
"YYYY-MM-DD..YYYY-MM-DD" |
Custom range (up to 365 days) |
Privacy
- No cookies
- No fingerprinting
- Country-only geolocation (no city, no coordinates)
- IPs are hashed with a daily-rotating salt, deleted after 24 hours
- GDPR compliant without consent banners
- Bot traffic is tagged and excluded from analytics
How events are counted
Every page view and every custom event counts as one event toward your monthly limit. A visitor loading a page is one event. A window.ca.track('signup_click') call is one event. Server-side tracking via the Node SDK or HTTP API also counts one event per call. Bot traffic is detected and excluded automatically — bots do not count toward your limit.
Pricing
Free up to 2,500 events/month (page views + custom events combined). €9.99/month for 100,000 events. All features on both tiers. See /pricing.md for details.