The Stats API is a public, rate-limited query interface that lets you fetch your Zenovay analytics over HTTP. Use it to build custom dashboards, embed live metrics in BI tools, or migrate scripts from Plausible.
What you can query
| Endpoint | What it returns |
|---|---|
GET /stats/aggregate | Totals over a period (visitors, pageviews, bounce rate, average session duration). |
GET /stats/timeseries | One data point per day, hour, or month over the period. |
GET /stats/breakdown | Top pages, top countries, top browsers — any single dimension, sorted by visitors. |
Full reference, parameter tables, and examples: docs.zenovay.com/api/external-api (look for the "Stats API" section).
The live OpenAPI 3.1 spec is available at /api/external/v1/openapi.json — import it into Postman, Insomnia, or use it with a code generator.
Plan requirement
The Stats API is available on Pro, Scale, and Enterprise plans. The Free plan can't create or use API keys at all — calls return a 403 API_PAID_PLAN_REQUIRED error. Upgrade in Settings → Billing.
Per-tier rate limits
| Plan | Rate limit (per minute, target) | Monthly request quota |
|---|---|---|
| Free | 10 | 1,000 |
| Pro | 30 | 10,000 |
| Scale | 60 | 100,000 |
| Enterprise | 120 | 1,000,000 |
When you exceed your tier's rate limit, the API returns 429 Too Many Requests with a Retry-After header telling you how many seconds to wait.
How rate-limiting is enforced. The per-minute limit is a target, not a hard ceiling. We use Cloudflare's edge rate-limit, which is per data center with a small burst allowance — so a sustained client may transiently see 1.5–3× the headline number before being throttled, especially if requests fan out across regions. The hard cap is the monthly request quota, which is enforced atomically against your account no matter where the requests originate. Plan capacity-sensitive workloads against the monthly quota; treat the per-minute number as a smoothing target.
Getting started
- Get an API key (Pro+) — see How do I get an API key?. Keys start with
zv_. - Find your site UUID — open the website in your Zenovay dashboard. The UUID is the path segment in the URL:
app.zenovay.com/domains/<UUID>. - Make your first call:
curl -G "https://api.zenovay.com/api/external/v1/stats/aggregate" \
-H "X-API-Key: zv_YOUR_KEY" \
--data-urlencode "site_id=YOUR_SITE_UUID" \
--data-urlencode "period=7d" \
--data-urlencode "metrics=visitors,pageviews"
You should get back something like:
{
"success": true,
"data": {
"results": {
"visitors": { "value": 12453 },
"pageviews": { "value": 38219 }
},
"meta": { "period": "7d", "period_start": "...", "period_end": "..." }
}
}
Common use cases
- Custom dashboards — pull aggregate metrics into Notion, Coda, Retool, or an internal BI tool every hour.
- Plausible migration — the parameter shape (
site_id,period,date,metrics,filters,property) is intentionally Plausible-compatible. Most migration scripts work after replacing the base URL and remappingsite_idfrom a domain to your Zenovay UUID. - Embeds — show live visitor counts on your status page or marketing site (use a server-side proxy; never expose your API key in client-side code).
- Scheduled reports — fetch breakdown data once a day and post it to Slack or email.
Filters
You can scope any query to a subset of visitors with Plausible-style filters:
# US visitors only
--data-urlencode "filters=country==US"
# US + Canada visitors using Chrome
--data-urlencode "filters=country==US,CA;browser==Chrome"
# Everyone except /admin pages
--data-urlencode "filters=page!=/admin"
Allowed filter keys: country, browser, device, os, source, utm_source, utm_medium, utm_campaign, page.
V1 limitation: When you provide
filters, only thevisitorsmetric is computed in V1; other metrics returnnullwith ameta.noteflag. Full filter support across all metrics ships in V2. The unfiltered queries support all metrics today.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
401 UNAUTHORIZED "Missing API key" | No X-API-Key or Authorization: Bearer header | Add the header. Generate a key in Settings → Account → Security & access if you don't have one. |
401 UNAUTHORIZED "Invalid API key" | Key was revoked, has a typo, or doesn't start with zv_ | Re-copy from Settings → Account → Security & access. |
403 FORBIDDEN "requires a Pro plan" | Your team is on Free | Upgrade in Settings → Billing. |
403 FORBIDDEN "does not have access" | The API key was scoped to a single site, but site_id doesn't match | Create a full-access key, or call with the correct site_id. |
404 NOT_FOUND "Website not found" | Wrong site_id UUID | Verify in dashboard URL. |
400 MISSING_* | Required query parameter missing | Add site_id, period, and metrics (always required). |
400 INVALID_METRIC | Unknown metric name | Use visitors, pageviews, visit_duration, bounce_rate, or events. |
400 INTERVAL_PERIOD_MISMATCH | interval=hour with period other than day | Use period=day with interval=hour, or use interval=day for longer periods. |
429 Too Many Requests | Per-tier rate limit hit | Honour the Retry-After header. Upgrade tier if you regularly hit this. |
Migrating from Plausible
The parameter shape is intentionally close to Plausible's Stats API v1. Differences:
| Plausible | Zenovay | Notes |
|---|---|---|
site_id=mysite.com | site_id=<UUID> | Plausible uses a domain string; Zenovay uses a UUID. Map once via GET /websites. |
period, date | period, date | Same allowlist + custom:YYYY-MM-DD,YYYY-MM-DD. |
metrics | metrics | visitors, pageviews, bounce_rate, visit_duration map 1:1. |
filters (v1 string format) | filters | Same key==value;key!=value shape. |
filters (v2 JSON array) | (V2 of Zenovay Stats API) | Ships later. |
Next steps
- Full Stats API reference (docs) — every parameter, every endpoint, every error code.
- OpenAPI 3.1 spec — for code generation and API testing tools.
- Authentication — Bearer vs X-API-Key headers, key rotation.
- API overview — all the other Zenovay public endpoints (insights, anomalies, retention, LTV, NL query).