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.
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 }.
| Surface | Limit | Discriminator |
|---|---|---|
Management write (POST/PATCH/DELETE on /api/v1/sites[/:id] and /api/v1/sites/:id/goals[/:id]) | 60 / minute | Bearer token |
| Stats / Realtime / Search engines reads | No limit (yet) | - |
Event collector (POST /api/v1/event) | 3,600 / minute per IP · 10,000 / minute & 500,000 / day per site token | IP + 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
| Field | Type | Required | Notes |
|---|---|---|---|
site.domain | string | yes | Bare hostname (no scheme). Used as display label and as the default hostname filter. |
site.name | string | no | Human-friendly label. Defaults to the domain. |
site.alerts_enabled | bool | no | Default true. Controls anomaly alert mailers for this site. |
site.anomaly_z_threshold | float | no | Default 3.0. Per-site z-score that triggers an alert. |
site.gsc_property | string | no | Format: sc-domain:example.com or https://example.com/. |
site.excluded_ips | string | no | Free-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
| Param | Values | Default | Notes |
|---|---|---|---|
range | 24h · 7d · 30d · 90d | 24h | Bucket width tracks the range (hourly under 24h, daily otherwise). |
compare | 1 | off | Adds 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>
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.