Developer reference

Trafnova API

HTTP / JSON API for managing sites and pulling analytics data. Read-only Stats endpoints let you mirror the dashboard in your own tooling; the Sites management endpoints let CI and IaC scripts provision new tracked sites with one round-trip.

On this page

Base URL

https://trafnova.com/api/v1

All endpoints described below are relative to this base. Every request must be HTTPS; responses are application/json unless noted (Stats endpoints also support .csv).

Authentication

Mint an API token at /settings (one-time cleartext display - save it the moment you create it). Send it on every request as Authorization: Bearer <token>.

curl https://trafnova.com/api/v1/sites \
  -H "Authorization: Bearer sk_xxx_your_token_here"

Missing or malformed header returns 401 with a JSON body:

{ "error": "missing Authorization header" }

Errors

Validation failures on writes return 422 with a structured payload so client code can route per-field messages back to the user without parsing prose:

{
  "error": "validation_failed",
  "details": {
    "domain": ["can't be blank"],
    "gsc_property": ["must be 'sc-domain:example.com' or 'https://example.com/'"]
  }
}

Cross-user access (any endpoint with :id on a site you don't own) returns 404 { "error": "site not found" } rather than 403, so a probing client can't tell whether the id exists at all.

Rate limits

Soft caps designed to stop runaway scripts, not throttle real usage. Hitting one returns 429 with a Retry-After header and a JSON body { "error": "rate_limited", "retry_after": N }.

SurfaceLimitDiscriminator
Management write (POST/PATCH/DELETE on /api/v1/sites[/:id] and /api/v1/sites/:id/goals[/:id])60 / minuteBearer token
Stats / Realtime / Search engines readsNo limit (yet)-
Event collector (POST /api/v1/event)3,600 / minute per IP · 10,000 / minute & 500,000 / day per site tokenIP + site token

Sites - list & read

GET /api/v1/sites

Returns every site owned by the authenticated user, sorted by domain.

curl https://trafnova.com/api/v1/sites \
  -H "Authorization: Bearer $TRAFNOVA_TOKEN"
{
  "sites": [
    {
      "id": 12,
      "domain": "example.com",
      "name": "Example",
      "installed_at": "2026-05-01T10:14:22Z",
      "last_seen_at": "2026-05-28T03:22:11Z",
      "alerts_enabled": true,
      "anomaly_z_threshold": 3.0
    }
  ]
}

GET /api/v1/sites/:id

Single site, including token + install_snippet so you can paste straight into the page.

{
  "id": 12, "domain": "example.com", "name": "Example",
  "installed_at": "2026-05-01T10:14:22Z", "last_seen_at": "2026-05-28T03:22:11Z",
  "alerts_enabled": true, "anomaly_z_threshold": 3.0,
  "token": "sfW1JvxBJDg7VQaT3HPY",
  "install_snippet": "<script>window.tn=window.tn||function(){(window.tn.q=window.tn.q||[]).push(arguments)}</script>\n<script defer data-token=\"sfW1JvxBJDg7VQaT3HPY\" src=\"https://cdn.trafnova.com/t.js\"></script>"
}

Sites - create

POST /api/v1/sites

Creates a site under the authenticated user. Returns 201 with the same shape as the single-site GET (token + install snippet included).

Request body

FieldTypeRequiredNotes
site.domainstringyesBare hostname (no scheme). Used as display label and as the default hostname filter.
site.namestringnoHuman-friendly label. Defaults to the domain.
site.alerts_enabledboolnoDefault true. Controls anomaly alert mailers for this site.
site.anomaly_z_thresholdfloatnoDefault 3.0. Per-site z-score that triggers an alert.
site.gsc_propertystringnoFormat: sc-domain:example.com or https://example.com/.
site.excluded_ipsstringnoFree-form comma/space/newline list of IPv4/IPv6 + CIDR ranges.

Unknown attributes (e.g. token, user_id, installed_at) are silently ignored.

curl -X POST https://trafnova.com/api/v1/sites \
  -H "Authorization: Bearer $TRAFNOVA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"site":{"domain":"shop.example.com","name":"Shop"}}'

Sites - update

PATCH /api/v1/sites/:id

Partial update. Same permitted fields as create. 200 with the updated site_json on success, 422 on validation failure, 404 on cross-user access.

curl -X PATCH https://trafnova.com/api/v1/sites/12 \
  -H "Authorization: Bearer $TRAFNOVA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"site":{"alerts_enabled":false,"name":"Shop (paused)"}}'

Sites - delete

DELETE /api/v1/sites/:id

Hard delete. Cascades through the site's events, anomalies, goals, and GSC daily stats. 204 No Content on success, 404 on cross-user access.

curl -X DELETE https://trafnova.com/api/v1/sites/12 \
  -H "Authorization: Bearer $TRAFNOVA_TOKEN"

Goals

Conversion goals attach to a site and match either a page path (kind: "page", target a path like /checkout) or a custom event name (kind: "event", target the event name, case-sensitive). Page targets are normalized (leading slash added, trailing slashes stripped), so checkout, /checkout, and /checkout/ all collide on the same goal.

GET /api/v1/sites/:site_id/goals

Lists every goal on a site you own.

curl https://trafnova.com/api/v1/sites/12/goals \
  -H "Authorization: Bearer $TRAFNOVA_TOKEN"
{
  "goals": [
    { "id": 1, "site_id": 12, "name": "Checkout", "kind": "page", "target": "/checkout", "created_at": "2026-06-14T10:00:00Z" },
    { "id": 2, "site_id": 12, "name": "Signup", "kind": "event", "target": "Signup", "created_at": "2026-06-14T10:01:00Z" }
  ]
}

POST /api/v1/sites/:site_id/goals

201 Created with the goal JSON. 422 with field-level details on validation failure (blank name, bad kind, or a target already tracked for that site+kind).

curl -X POST https://trafnova.com/api/v1/sites/12/goals \
  -H "Authorization: Bearer $TRAFNOVA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"goal": {"name": "Checkout", "kind": "page", "target": "/checkout"}}'

DELETE /api/v1/sites/:site_id/goals/:id

204 No Content on success, 404 on cross-user access.

curl -X DELETE https://trafnova.com/api/v1/sites/12/goals/1 \
  -H "Authorization: Bearer $TRAFNOVA_TOKEN"

Stats

GET /api/v1/sites/:id/stats

The full per-site analytics payload that powers the dashboard - timeseries plus 14 top-N tables, in one shot.

Query parameters

ParamValuesDefaultNotes
range24h · 7d · 30d · 90d24hBucket width tracks the range (hourly under 24h, daily otherwise).
compare1offAdds prev_count + delta_pct to every top-N row and an overlay series.
filter[dim][op] + filter[dim][values][]see below-Click-to-filter dimensions. Multi-value within a dim is OR; across dims is AND.

Whitelisted dimensions: source, country, region, city, browser, os, device, page, hostname. Operators: is, is_not, contains, does_not_contain (enum-ish dims like country only support is / is_not).

# Last 30 days, only google.com referrals, with prev-period overlay
curl "https://trafnova.com/api/v1/sites/12/stats?range=30d&compare=1\
&filter[source][op]=is&filter[source][values][]=google.com" \
  -H "Authorization: Bearer $TRAFNOVA_TOKEN"

Response keys (abridged):

{
  "range": "30d",
  "labels": ["Apr 29", "Apr 30", ...],
  "pageviews": [...],
  "visits": [...],
  "total_pv": 12450, "total_uv": 7821,
  "top_pages":     [{ "label": "/", "count": 4521, "title": "Home - Acme" }, ...],
  "entry_pages":   [...], "exit_pages": [...],
  "top_sources":   [...], "top_channels": [...],
  "top_countries": [...], "top_regions":  [...], "top_cities": [...],
  "top_browsers":  [...], "top_browser_versions": [...],
  "top_os":        [...], "top_os_versions":      [...],
  "top_devices":   [...], "top_hostnames":        [...],
  "top_campaigns": [...],
  "top_screen_sizes": [{ "label": "Mobile", "count": 1203 }, ...], "top_languages": [{ "label": "en-US", "count": 880 }, ...],
  "goals": [...],
  "event_properties": [...],
  "page_properties": [{ "event_name": "Pageviews", "total": 312, "keys": [{ "key": "author", "values": [...] }] }],
  "engagement": { "bounce_rate": 0.41, "avg_duration_seconds": 84, "views_per_visit": 1.9 }
}

CSV export

Append .csv + a dataset param to download a single table. Datasets: timeseries (default), top_pages, entry_pages, exit_pages, top_sources, top_channels, top_countries, top_regions, top_cities, top_browsers, top_browser_versions, top_os, top_os_versions, top_devices, top_hostnames, top_campaigns, top_screen_sizes, top_languages.

curl "https://trafnova.com/api/v1/sites/12/stats.csv?dataset=top_pages&range=7d" \
  -H "Authorization: Bearer $TRAFNOVA_TOKEN" \
  -o top_pages.csv

Realtime

GET /api/v1/sites/:id/realtime

Last-5-minute distinct sessions + 30-bucket per-minute pageview histogram + top countries. Poll on a ~5s cadence. Filter params accepted (same shape as Stats).

{
  "visitors_now": 47,
  "page_views_now": 132,
  "labels":    ["18:34", "18:35", ..., "19:03"],
  "histogram": [3, 5, 4, 8, ...],
  "histogram_pv": [7, 9, 6, 14, ...],
  "top_countries": [{ "country_code": "US", "count": 18 }, ...],
  "top_sites": [{ "site_id": 1, "count": 12 }, ...],
  "top_countries_pv": [{ "country_code": "US", "count": 41 }, ...],
  "top_sites_pv": [{ "site_id": 1, "count": 30 }, ...]
}

The visitor widgets (histogram, top_sites, top_countries) count distinct sessions; the _pv variants count raw pageviews. The dashboard card's visitors/page-views toggle swaps between the two sets.

Search engines

GET /api/v1/sites/:id/search_engines

Same payload shape as /stats, but pre-filtered to events where the referrer host matches a known search engine. Two extra keys: top_engines and search_landing_pages.

Tracker snippet

The tracker is a static file served from the CDN; you embed it in your site's <head>:

<script defer data-token="YOUR_SITE_TOKEN" src="https://cdn.trafnova.com/t.js"></script>
Don't call POST /api/v1/event yourself. The collector endpoint is an implementation detail of the tracker (cookieless session id, dwell beacons, batched queue, bot signals - all handled in t.js). Hand-rolling pageview calls means you'll lose every one of those signals. Use the snippet.

Custom events, declarative data-tn-event tagging, <meta name="tn:*"> pageview dimensions, hostname / path restrictions, consent hooks and more are covered in the full tracker reference (/docs/tracking).

Found a bug, missing an endpoint, or want SDK examples in your language? Tell us.