Files
pn-new-crm/docs/umami-api-capabilities.md
Matt bac253b360 feat(analytics): Umami website-analytics suite — world map, realtime, sessions, heatmap, pixel tracking, tracked links
Adds the read-side Umami integration queued in last week's
website-analytics plan (Phases 1–6 of `docs/website-analytics-flesh-out-plan.md`):

- Realtime panel polls Umami at 5s intervals; world map renders visitor
  origins via echarts + `public/world-map/echarts-world.json` topo.
- Sessions list + session-detail-sheet drill-down (per-session event
  timeline pulled from `/api/v1/website-analytics`).
- Weekly heatmap (day-of-week × hour-of-day) for engagement timing.
- Metric-detail pages under `/[portSlug]/website-analytics/[metric]`
  for pageviews / referrers / events deep-dives.
- Email-pixel write path: `/api/public/email-pixel/[sendId]` 1×1 GIF
  beacon backed by `email_open_tracking` (migration 0076); resolves
  inline on render in inbox.
- Tracked-link redirect: `/q/[slug]` routes through `tracked_links`
  (migration 0077) and forwards to the canonical destination after
  logging the click.
- Dashboard `website-glance-tile` now reads from the live Umami service
  instead of placeholder data.

Deps: `@umami/node`, `echarts`, `echarts-for-react`, `@types/geojson`,
`@types/topojson-client`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 15:53:41 +02:00

12 KiB

Umami v2 / v3 API capabilities — reference for flesh-out planning

Verified against: analytics.portnimara.com (Umami v3.1.0), 2026-05-19. Auth: username/password → JWT via POST /api/auth/login, Bearer on every request, 1h TTL (we cache 55min). Companion code: src/lib/services/umami.service.ts (currently wraps stats/pageviews/metrics/active).

Endpoints below are listed by topic area, with what we currently use, what's available but unused, and where it could plug into the CRM.


1. Stats & traffic snapshots — /api/websites/:id/stats

Currently used. Returns the flat aggregate over the requested window plus a comparison block for the prior window of equal length.

{
  "pageviews": 2081, "visitors": 726, "visits": 872,
  "bounces": 457, "totaltime": 109519,
  "comparison": { "pageviews": 1935, "visitors": 642, ... }
}

Unused fields we could surface:

  • totaltime — total seconds on site → derive avg session time (totaltime / visits).
  • bounces / visits → bounce-rate KPI.
  • Period-over-period deltas (already wired for trend arrows, but the full comparison object has more we could use for a "what changed since last period" panel).

Filters supported (per Umami docs, mostly untested by us): url, referrer, title, query, event, host, os, browser, device, country, region, city — meaning every stats call can be sliced. Big unlock: show stats for a specific landing-page URL on the berth detail (e.g. /berths/A12 stats), or filter by referrer to see which channels drove signed EOIs.


2. Time-series — /api/websites/:id/pageviews

Currently used for the trend chart. Returns {pageviews: [{x, y}], sessions?: [{x, y}]} (sessions only when compare is requested).

Parameters: startAt, endAt, unit (year|month|day|hour), timezone, compare (untapped), filters (untapped).

Unused: compare=prev gives the same series for the previous period — could power a dual-line "vs last period" overlay on the chart.


3. Top-N metrics — /api/websites/:id/metrics

Currently used for Top Pages / Referrers / Countries (limit 10). Returns [{x, y}].

Available type values (we surface 4, Umami offers 17):

Type What it returns CRM use case
path Top URLs Already shown (we mis-typed as url, now fixed)
referrer Top referring sites Already shown
country Visitors by country Already shown
browser / os / device Tech breakdown Not surfaced — useful for "is mobile traffic converting?"
region / city Geographic drill-down Strong fit for marina marketing
language Visitor browser language Could feed i18n decisions
screen Resolution Low value
event Top custom events Big unlock — see §6 below
tag Event tags Same
query Top URL query strings UTM-debug surface
entry / exit First/last page in session Funnel analysis
title Top page titles (vs paths) Better labels for non-slug URLs
hostname Multi-domain sites Probably N/A
distinctId Custom user identifiers If we ever pipe CRM user IDs into Umami

4. Live visitors — /api/websites/:id/active

Currently used for the green-dot "N active right now" indicator. Returns {visitors: number} (last-5-min count).

Alternative for richer realtime: /api/realtime/:websiteId (live realtime feed) returns far more — current top URLs being viewed, current top countries, recent event stream, a 30-minute time-series, totals, plus a timestamp you can poll against. We could surface a "live" panel on the dashboard showing the most-viewed pages right now.


5. Sessions API — /api/websites/:id/sessions/*

Not currently used. Multiple endpoints worth integrating:

  • GET /sessions — list every session in a range with full device/geo/visits/views columns. Pageable. Could power a "recent visitors" surface — see who's browsing the berth detail pages right now.
  • GET /sessions/stats — summary aggregate (pageviews, visitors, visits, countries, events) keyed by session.
  • GET /sessions/:sessionId — drill into a single session: device, OS, browser, country, subdivision, city, screen, language, firstAt, lastAt, visits, views, events, totaltime.
  • GET /sessions/:sessionId/activity — full event timeline for one session (urlPath, eventName, referrerDomain, timestamps).
  • GET /sessions/:sessionId/properties — custom session properties (email, name, etc. — if Umami's identify() is called from the marketing site).
  • GET /session-data/properties + /session-data/values — aggregate custom session properties.
  • GET /sessions/weekly — heatmap of session count by hour-of-week. Direct fit for an "engagement heatmap" widget.

Big unlock: if marketing site calls umami.identify({email}) after EOI form submit, sessions can be linked back to a specific client. We could then show "this client's website journey" on their CRM detail page.


6. Events API — /api/websites/:id/events/*

Not currently used. Umami auto-tracks pageviews; custom events are fired explicitly (e.g. button clicks, form submits, video plays). Endpoints:

  • GET /events — list custom events in a range.
  • GET /events/stats — totals.
  • GET /events/series — time-series per event.
  • GET /event-data/* — aggregate over event payload properties.

High-leverage CRM use cases:

  • Fire an event on the marketing site when someone clicks "Inquire about berth A12" → CRM Activity feed shows it in real-time on the inquiry record.
  • Fire an event when someone downloads a brochure → see which brochures convert.
  • Fire an event on EOI form-step completions → drop-off funnel analysis.

We'd need to add umami.track('event-name', {payload}) calls on the marketing site (~1-2h work there) and a new admin surface to define/view these events.


7. Reports API — /api/reports/*

Not currently used. Umami's "saved reports" system. Endpoints:

  • GET /reports + GET /reports/:id — list / retrieve saved reports.
  • POST /reports/insights — slice-and-dice with arbitrary filters/dimensions.
  • POST /reports/funnel — multi-step conversion analysis.
  • POST /reports/retention — cohort retention over time.
  • POST /reports/utm — UTM-tagged campaign performance.
  • POST /reports/journey — most common navigation paths.
  • POST /reports/goals — pageview/event-goal completion tracking.
  • POST /reports/revenue — revenue attribution (if we fire purchase events with amount).
  • POST /reports/attribution — first/last-click attribution modelling.

Best fits for the CRM:

  • Funnel report for the EOI flow: /berths → /berths/A12 → /inquire?berth=A12 → form submit → CRM EOI signed. Surface drop-off percentages on the Pulse-style dashboard.
  • Journey report to see "what paths do visitors take before signing an EOI?" — informs marketing-site IA.
  • UTM report to plumb campaign attribution into the lead-source breakdown (currently CRM-side; could be cross-validated against marketing's UTM-tagged traffic).
  • Attribution report to give Pipeline-by-Source a "first-click vs last-click" toggle.

8. Send events from CRM → Umami — /api/send

Not currently used. The collect endpoint accepts page hits + custom events from any client. CRM doesn't currently push events, but we could:

  • Fire umami.track('signed-eoi', {berth: 'A12', deal_value: 50000}) from the CRM after EOI completion — closes the loop between marketing-site funnel and CRM outcome.
  • Fire umami.track('contract-signed'), umami.track('deposit-received') — full funnel visible in Umami without leaving it.

9. Multi-website + team admin — /api/websites, /api/teams, /api/users

Not currently used. We hard-code a single umami_website_id per port. Useful if a port runs multiple sites (e.g. main marina + residential subdomain): admin UI could list-and-pick from the configured Umami instance's websites instead of requiring manual ID copy-paste. Same for team membership.


Prioritized opportunity list

Ranked by leverage-vs-effort, assuming the v3.1.0 fix in this commit is the baseline:

  1. Avg session time + bounce rate KPI tiles (~20 min) — already in the /stats response, just need new tiles.
  2. compare=prev overlay on the pageviews trend chart (~30 min) — dual-line "vs last period" surface.
  3. Country choropleth heatmap (~4-6h) — already queued in Bucket 3 of the UAT findings doc as "World-map heatmap of Umami visitor origins."
  4. Surface top browsers / OS / devices (~30 min) — additional TopList columns; pure UI work.
  5. Fire CRM-side events back into Umami (~2-3h marketing-site + CRM hook) — closes the funnel between marketing and outcomes.
  6. EOI funnel via /api/reports/funnel (~3-4h) — drop-off analysis from berth view → inquiry → signed EOI.
  7. Identify visitors → link sessions to clients (~4-6h spread across marketing site + CRM detail surfaces) — biggest unlock but needs marketing-site changes.
  8. Sessions-list "recent visitors" panel (~2-3h) — see who's browsing right now, drill into individual sessions.
  9. Saved-reports admin surface (~6-10h) — let admins create + share Umami reports without leaving the CRM. Bigger product surface; defer until #1-#5 land.

Service-layer additions needed to support the above

src/lib/services/umami.service.ts currently exports: getStats, getPageviewsSeries, getMetric, getActiveVisitors, testConnection. To unlock the opportunities above, add:

  • getSessions(portId, range, opts)/sessions (paged)
  • getSession(portId, sessionId) → single-session drill-in
  • getSessionActivity(portId, sessionId, range) → event timeline
  • getSessionsWeekly(portId, range) → heatmap source
  • getEvents(portId, range) + getEventsStats(portId, range) + getEventsSeries(portId, range, eventName, unit) → custom events
  • getRealtime(portId, range)/api/realtime/:id for the live panel
  • getReport(portId, reportType, body) → POST wrappers for funnel/retention/journey/utm/goals/revenue/attribution
  • trackEvent(portId, name, payload) → POST to /api/send for CRM → Umami event emission

Each is a thin wrapper around the existing umamiFetch (or a new umamiPost variant for the reports endpoints). The auth + JWT cache + retry logic already in place handles them all.


Known gotchas (verified against v3.1.0)

  • Metric type=url returns 400 — use type=path (handled in our code via back-compat alias).
  • /api/websites/:id/pageviews returns sessions only when compare is in the query string — keep .sessions optional in TS types.
  • Stats response is flat (pageviews: number), not nested (pageviews: {value, prev}). The v1 nested shape isn't in v2/v3.
  • /api/auth/login returns a JWT with no expires_in field — we assume 1h and refresh proactively at 55min.
  • Visiting /api in a browser returns nothing — base path has no GET handler. Use /api/heartbeat to check liveness.
  • Filters are passed as query params (e.g. &country=DE), NOT as a JSON filters body, per actual API behaviour (docs occasionally show JSON which doesn't work for GET endpoints).