Stats API
Read your stats programmatically: API keys, authentication, rate limits and every /api/v1 endpoint.
The Stats API exposes the same numbers as the dashboard over plain HTTPS + JSON: totals, time series, and breakdowns for any site, plus write endpoints for annotations, publish pings, and server-side bot collection. All endpoints live under https://app.numative.com/api/v1/.
Create an API key
Go to Account > API, give the key a name, and pick a scope. The full key is shown once at creation; only its SHA-256 hash is stored, so copy it immediately. Keys look like nmv_live_... and can be revoked at any time from the same page.
- read (default): the reporting endpoints (stats, timeseries, breakdown) and listing annotations.
- write: everything a read key can do, plus creating and deleting annotations, publish pings, and server-side collection.
A key belongs to your organization and can access every site in it.
Authentication
Send the key in the Authorization header on every request:
Authorization: Bearer nmv_live_...An x-api-key: nmv_live_... header is also accepted. For platforms that can only configure a fixed URL (log drains), a ?key= query parameter works too, but URLs may be logged by the platform, so only use it with a write-only ingest key, never a read key.
Errors are returned as JSON: {"error": "message"} with status 400 (bad input), 401 (missing/invalid key), 403 (scope), 404 (site not found for this key), or 429 (rate limited).
Rate limits
| Endpoint | Limit per key |
|---|---|
stats, timeseries, breakdown, annotations | 600 requests / minute |
collect | 600 requests / minute |
ping | 60 requests / minute |
Common query parameters
The three read endpoints share these parameters. The requested site must belong to the key's organization.
| Parameter | Type | Default | Description |
|---|---|---|---|
site_id | string | required | The site UUID or the public snippet id (the data-site value); both are accepted. |
range | string | 30d | One of: realtime, today, yesterday, 7d, week, month, 30d, 90d, year, ytd, last-year, 12mo, all, custom. Relative ranges resolve in the site's timezone. |
from | date | - | Start date (YYYY-MM-DD), used with range=custom. |
to | date | - | End date (YYYY-MM-DD), used with range=custom. |
filters | JSON | none | URL-encoded JSON array of segment filters (see below). |
limit | number | 100 | Max rows returned by breakdown, clamped to 1-1000. |
The response bucket size (interval) is derived from the range (minute for realtime, hour for today/yesterday, day and up for longer windows) and echoed in every response's range object.
Filters
A filter is an object with dimension, operator, and value keys, where value is an array of strings and operator is one of is, is_not, contains, or matches. Dimensions: path, entry_page, exit_page, source, channel, referrer, utm_source, utm_medium, utm_campaign, utm_term, utm_content, country, region, city, browser, os, device, screen_class, hostname, event, plus prop:<key> for custom properties.
[{"dimension":"country","operator":"is","value":["US","CA"]}]/api/v1 responses send Access-Control-Allow-Origin: *, so the API is callable from anywhere, including browsers. Do not embed API keys in client-side code, though: anyone who can read the page can read the key. Call the API from your server.GET /api/v1/stats
Headline totals for the window.
curl "https://app.numative.com/api/v1/stats?site_id=SITE_ID&range=30d" \
-H "Authorization: Bearer nmv_live_..."{
"range": { "from": "2026-06-02T14:00:00.000Z", "to": "2026-07-02T14:00:00.000Z", "interval": "day" },
"totals": {
"visitors": 4210,
"visits": 5124,
"pageviews": 9876,
"bounceRate": 0.41,
"avgDuration": 74.2,
"viewsPerVisit": 1.93,
"events": 312
}
}bounceRate is a fraction (0 to 1), avgDuration is seconds, and events counts custom (non-pageview) events.
GET /api/v1/timeseries
Visitors, visits, and pageviews bucketed over time.
curl "https://app.numative.com/api/v1/timeseries?site_id=SITE_ID&range=7d" \
-H "Authorization: Bearer nmv_live_..."{
"range": { "from": "2026-06-25T14:00:00.000Z", "to": "2026-07-02T14:00:00.000Z", "interval": "day" },
"series": [
{ "timestamp": "2026-06-25T00:00:00.000Z", "visitors": 512, "visits": 590, "pageviews": 1204 },
{ "timestamp": "2026-06-26T00:00:00.000Z", "visitors": 486, "visits": 553, "pageviews": 1131 }
]
}GET /api/v1/breakdown
Ranked rows for one dimension. Takes the common parameters plus:
| Parameter | Type | Default | Description |
|---|---|---|---|
property | string | required | The dimension to rank by: any dimension from the filters list, or prop:<key>. |
curl "https://app.numative.com/api/v1/breakdown?site_id=SITE_ID&property=source&range=30d&limit=5" \
-H "Authorization: Bearer nmv_live_..."{
"range": { "from": "2026-06-02T14:00:00.000Z", "to": "2026-07-02T14:00:00.000Z", "interval": "day" },
"property": "source",
"results": [
{ "value": "google.com", "visitors": 1810, "visits": 2011, "pageviews": 3902, "bounceRate": 0.38, "avgDuration": 81.5 },
{ "value": "news.ycombinator.com", "visitors": 640, "visits": 655, "pageviews": 1105, "bounceRate": 0.61, "avgDuration": 42.0 }
]
}POST /api/v1/collect
Server-side collection of bot and AI-crawler visits, the requests a JavaScript tracker never sees. Requires a write-scoped key. Only bot traffic is stored; human and junk hits are dropped, so nothing is double-counted against the JS tracker. Stored events count toward your event quota. The in-app setup card (your site's Settings, under AI crawler tracking) has ready-made snippets for WordPress, Cloudflare Workers, Vercel log drains, and Node built on this endpoint.
| Body field | Type | Default | Description |
|---|---|---|---|
site_id | string | required | The site's public snippet id. |
events | array | required | Up to 500 hits per request. A single hit may also be sent as the body itself, without the events wrapper. |
events[].path | string | required | Request path, e.g. /pricing. |
events[].userAgent | string | required | The visitor's User-Agent (user_agent and ua are accepted as aliases). |
events[].ip | string | - | Client IP, used for bot verification (clientIp is an alias). |
events[].referrer | string | - | Referrer URL (referer is an alias). |
events[].hostname | string | - | Request hostname (host is an alias). |
events[].timestamp | string | now | ISO 8601 time of the hit. |
curl -X POST "https://app.numative.com/api/v1/collect" \
-H "Authorization: Bearer nmv_live_..." \
-H "Content-Type: application/json" \
-d '{
"site_id": "YOUR_SITE_ID",
"events": [
{ "path": "/blog/post", "userAgent": "GPTBot/1.0", "ip": "20.15.240.64" }
]
}'{ "ingested": 1, "dropped": 0 }POST /api/v1/ping
Instant single-page recrawl, for publish and deploy hooks. Requires a write-scoped key. The body is a single absolute url whose hostname must match one of your organization's sites (its domain, a subdomain, or an allowed host). See Change detection for where pings fit.
| Body field | Type | Default | Description |
|---|---|---|---|
url | string | required | Absolute http(s) URL of the page that changed. |
curl -X POST "https://app.numative.com/api/v1/ping" \
-H "Authorization: Bearer nmv_live_..." \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/blog/new-post"}'{ "accepted": true, "path": "/blog/new-post" }Returns 503 when the crawler is unavailable, so a hook can retry.
Annotations: /api/v1/annotations
Chart annotations (the emerald markers) can be managed from CI or a deploy pipeline. Listing works with any key; creating and deleting require a write-scoped key.
POST (create)
| Body field | Type | Default | Description |
|---|---|---|---|
siteId | UUID | - | The site UUID. Either siteId or domain is required. |
domain | string | - | The site's domain, as an alternative to siteId. |
title | string | required | Marker title, at most 200 characters. |
at | string | now | ISO 8601 timestamp the marker is placed at. |
kind | string | note | One of: deploy, campaign, email, content, note, spike_source, other. |
body | string | - | Optional longer description shown on hover. |
url | string | - | Optional link. |
curl -X POST "https://app.numative.com/api/v1/annotations" \
-H "Authorization: Bearer nmv_live_..." \
-H "Content-Type: application/json" \
-d '{
"domain": "example.com",
"kind": "deploy",
"title": "v2.4.0 released",
"url": "https://github.com/acme/site/releases/v2.4.0"
}'The created annotation row is returned as JSON.
GET (list)
GET /api/v1/annotations?siteId=<uuid> lists a site's annotations, newest first, up to 500. Optional from and to ISO 8601 timestamps bound the window. Note that this endpoint takes the site UUID, not the public snippet id.
DELETE
DELETE /api/v1/annotations?id=<uuid> removes one annotation and returns {"deleted": true}.