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 characters
  • properties — 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.