DoryDoryGet an API key
API Reference

The Dory Data API

Every metric on your dashboard — pageviews, events, referrers, breakdowns, and raw rows — is available over a simple, read-only REST API. One API key per site, no SDK required.

Authentication

Every site has its own API key, scoped to that site's data only. Generate or copy it from Settings → API Access in your dashboard. Send it as a bearer token on every request — there is no separate siteId parameter, the key already identifies the site.

curl "https://your-dory-domain.com/api/v1/overview?range=30d" \
  -H "Authorization: Bearer YOUR_API_KEY"

Base URL & versioning

All endpoints live under /api/v1/ on your own Dory deployment (e.g. https://analytics.yourdomain.com). The v1 prefix is a stable contract — breaking changes will ship as v2 rather than mutating this one. All responses are JSON, all endpoints are GET-only, and CORS is open so you can call the API directly from a browser dashboard.

Errors

Errors return a non-2xx status with a consistent shape:

{ "error": { "message": "Invalid API key.", "status": 401 } }
401

Missing or invalid API key

400

Bad or missing query parameter

500

Unexpected server error

Rate limits

There is no hard rate limit today — this is a self-hosted deployment on your own database. Be a good citizen: cache responses you poll frequently, and prefer /timeseries or /overview over paging all of /export when a summary will do.

Endpoints

GET/api/v1/site

Metadata about the site the API key belongs to.

{
  "data": { "id": "65f...", "name": "Marketing Site", "domain": "example.com", "createdAt": "2025-01-01T00:00:00.000Z" }
}
GET/api/v1/overview

Totals for the period: views, events, unique visitors, and top 5 pages.

Query params

range24h | 7d | 30d | 3m | 12m | 24mLookback window. Defaults to 30d.
{
  "range": "30d",
  "data": {
    "totalViews": 12045,
    "totalEvents": 812,
    "uniqueVisitors": 3021,
    "topPages": [{ "route": "/", "views": 4210 }]
  }
}
GET/api/v1/timeseries

Pageviews bucketed over time (hourly for 24h, daily for 7d/30d/3m, monthly for 12m/24m). Missing buckets are zero-filled.

Query params

range24h | 7d | 30d | 3m | 12m | 24mLookback window. Defaults to 30d.
{
  "range": "7d",
  "data": [{ "date": "2026-09-24", "views": 512 }, { "date": "2026-09-25", "views": 488 }]
}
GET/api/v1/pages

Top 20 routes by views, with unique visitor counts.

Query params

range24h | 7d | 30d | 3m | 12m | 24mLookback window. Defaults to 30d.
{
  "range": "30d",
  "data": [{ "route": "/pricing", "views": 900, "unique": 640 }]
}
GET/api/v1/referrers

Top 10 external referrers by views.

Query params

range24h | 7d | 30d | 3m | 12m | 24mLookback window. Defaults to 30d.
{
  "range": "30d",
  "data": [{ "referrer": "https://instagram.com", "views": 300 }]
}
GET/api/v1/events

Custom events, grouped by name, with count, last-seen time, and the 5 most recent samples per event.

Query params

range24h | 7d | 30d | 3m | 12m | 24mLookback window. Defaults to 30d.
{
  "range": "30d",
  "data": [{
    "name": "signup_clicked",
    "count": 44,
    "lastSeen": "2026-09-29T10:00:00.000Z",
    "recentEvents": [{ "url": "https://example.com/pricing", "properties": { "plan": "Pro" }, "timestamp": "2026-09-29T10:00:00.000Z" }]
  }]
}
GET/api/v1/breakdown

Unique visitors grouped by country, device, browser, OS, UTM campaign, and feature flag.

Query params

range24h | 7d | 30d | 3m | 12m | 24mLookback window. Defaults to 30d.
{
  "range": "30d",
  "data": {
    "countries": [{ "name": "IN", "visitors": 900 }],
    "devices": [{ "name": "Mobile", "visitors": 1200 }],
    "browsers": [{ "name": "Chrome", "visitors": 2000 }],
    "os": [{ "name": "Windows", "visitors": 1500 }],
    "utms": [{ "name": "spring_launch", "visitors": 80 }],
    "flags": [{ "name": "new-nav", "visitors": 300 }]
  }
}
GET/api/v1/realtime

Active visitors, live pages, and live countries for the last 5 minutes. No range param — always live.

{
  "data": {
    "activeVisitors": 12,
    "livePages": [{ "route": "/", "unique": 8, "views": 20 }],
    "liveCountries": [{ "name": "US", "visitors": 6 }]
  }
}
GET/api/v1/export

Raw document dump for pageviews or events — the underlying rows behind every other endpoint. Cursor-paginated, newest first.

Query params

typepageviews | eventsWhich collection to export. Defaults to pageviews.
limitinteger, 1–500Rows per page. Defaults to 100.
beforeISO 8601 timestampReturn rows strictly older than this timestamp. Use the previous response's nextCursor to page backwards through history.
{
  "type": "pageviews",
  "count": 100,
  "nextCursor": "2026-09-28T04:12:00.000Z",
  "data": [{ "_id": "...", "url": "https://example.com/", "route": "/", "country": "IN", "device": "Mobile", "timestamp": "2026-09-29T10:00:00.000Z" }]
}

Paging through raw data

/api/v1/export is cursor-paginated: each response includes a nextCursor timestamp. Pass it back as before to fetch the next page, and stop when nextCursor is null.

let cursor;
do {
  const url = new URL("https://your-dory-domain.com/api/v1/export");
  url.searchParams.set("type", "pageviews");
  if (cursor) url.searchParams.set("before", cursor);

  const res = await fetch(url, { headers: { Authorization: "Bearer YOUR_API_KEY" } });
  const { data, nextCursor } = await res.json();
  // ...process data...
  cursor = nextCursor;
} while (cursor);