Skip to main content
Pro Plan10 minutesIntermediate

How do I query my analytics programmatically (Stats API)?

Query Zenovay's analytics data over HTTP — totals, time series, and breakdowns by page, country, browser, and more. Plausible-compatible parameter shape for easy migration.

statsapireportingplausibleintegration
Last updated:

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

EndpointWhat it returns
GET /stats/aggregateTotals over a period (visitors, pageviews, bounce rate, average session duration).
GET /stats/timeseriesOne data point per day, hour, or month over the period.
GET /stats/breakdownTop 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

PlanRate limit (per minute, target)Monthly request quota
Free101,000
Pro3010,000
Scale60100,000
Enterprise1201,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

  1. Get an API key (Pro+) — see How do I get an API key?. Keys start with zv_.
  2. 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>.
  3. 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 remapping site_id from 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 the visitors metric is computed in V1; other metrics return null with a meta.note flag. Full filter support across all metrics ships in V2. The unfiltered queries support all metrics today.

Troubleshooting

SymptomCauseFix
401 UNAUTHORIZED "Missing API key"No X-API-Key or Authorization: Bearer headerAdd 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 FreeUpgrade in Settings → Billing.
403 FORBIDDEN "does not have access"The API key was scoped to a single site, but site_id doesn't matchCreate a full-access key, or call with the correct site_id.
404 NOT_FOUND "Website not found"Wrong site_id UUIDVerify in dashboard URL.
400 MISSING_*Required query parameter missingAdd site_id, period, and metrics (always required).
400 INVALID_METRICUnknown metric nameUse visitors, pageviews, visit_duration, bounce_rate, or events.
400 INTERVAL_PERIOD_MISMATCHinterval=hour with period other than dayUse period=day with interval=hour, or use interval=day for longer periods.
429 Too Many RequestsPer-tier rate limit hitHonour 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:

PlausibleZenovayNotes
site_id=mysite.comsite_id=<UUID>Plausible uses a domain string; Zenovay uses a UUID. Map once via GET /websites.
period, dateperiod, dateSame allowlist + custom:YYYY-MM-DD,YYYY-MM-DD.
metricsmetricsvisitors, pageviews, bounce_rate, visit_duration map 1:1.
filters (v1 string format)filtersSame key==value;key!=value shape.
filters (v2 JSON array)(V2 of Zenovay Stats API)Ships later.

Next steps

Was this article helpful?