REST API
Read your analytics and manage your account programmatically. The read API is the HTTP twin of the MCP server — same data, plain HTTP, JSON responses — and the management API gives you full CRUD over sites, keys, team, and workspace settings.
Authentication
All requests use a bearer token. There are two kinds of key, each with its own scope:
| Key | Prefix | Scope | Created from |
|---|---|---|---|
| Site key | abner_ | Read-only analytics for one site | Site → Settings → API key |
| Management key | abner_ws_ | Full CRUD across one workspace | Account → Settings → Management API Keys |
Authorization: Bearer abner_xxxx… # read endpoints
Authorization: Bearer abner_ws_xxxx… # management endpoints
The full key is shown once at creation; only a hash is stored, so save it somewhere safe. Treat keys like passwords; never embed them in client-side code (responses are not CORS-enabled, so the API is intended for server-side use). A missing or wrong-type key returns 401 Unauthorized — read endpoints require a site key, management endpoints require a management key.
Base URL
https://www.abner.app/api/v1
Vocabulary
The query endpoints share one vocabulary. GET /api/v1/meta returns the live, authoritative lists; the current values are:
- Periods:
24h,7d,14d,30d,month,year,all - Metrics:
visitors,pageviews,sessions,pageviews_per_visitor,events - Dimensions:
pathname,referrer_host,country,region,city,browser,device_type,utm_source,utm_medium,utm_campaign,event_name(plustime_day/time_hourfor time series) - Filters (as
filter.<key>=value):pathname,referrer_host,country,browser,device_type,utm_source,utm_medium,utm_campaign,event_name
Read API
Authenticated with a site key (abner_…). All read endpoints are GET and scoped to that key's site.
GET /site
Returns the site the key is scoped to. Useful as a "whoami" / key-check.
curl https://www.abner.app/api/v1/site \
-H "Authorization: Bearer $ABNER_API_KEY"
{
"site_id": "9f3c…",
"name": "Example",
"domain": "example.com",
"timezone": "UTC",
"workspace_id": "1a2b…"
}
GET /stats
A single overview row of aggregate metrics for the period. Query params: period (default 7d), any filter.<key>.
curl "https://www.abner.app/api/v1/stats?period=30d&filter.country=US" \
-H "Authorization: Bearer $ABNER_API_KEY"
{
"site_id": "9f3c…",
"period": "30d",
"stats": { "visitors": 1240, "pageviews": 3877, "sessions": 1502, "pageviews_per_visitor": 3.12 }
}
GET /timeseries
Metrics bucketed over time. Query params: period (default 30d), interval = day | hour (default day), metrics (comma-separated, default visitors,pageviews), filters.
curl "https://www.abner.app/api/v1/timeseries?period=7d&interval=day&metrics=visitors" \
-H "Authorization: Bearer $ABNER_API_KEY"
{
"site_id": "9f3c…",
"period": "7d",
"interval": "day",
"points": [
{ "time": "2026-05-19T00:00:00Z", "metrics": { "visitors": 180 } },
{ "time": "2026-05-20T00:00:00Z", "metrics": { "visitors": 205 } }
]
}
GET /breakdown/{dimension}
Top values for a single dimension (e.g. top pages, referrers, countries). Path: any dimension except the time buckets. Query params: period (default 7d), metrics (default visitors,pageviews), limit (default 50, max 1000), filters.
curl "https://www.abner.app/api/v1/breakdown/pathname?period=30d&limit=5" \
-H "Authorization: Bearer $ABNER_API_KEY"
{
"site_id": "9f3c…",
"period": "30d",
"dimension": "pathname",
"results": [
{ "value": "/", "metrics": { "visitors": 820, "pageviews": 1610 } },
{ "value": "/pricing", "metrics": { "visitors": 210, "pageviews": 340 } }
]
}
GET /metrics
The swiss-army query — combine any metrics, dimensions, filters, and period. This is the direct mirror of the MCP query_metrics tool. Query params: metrics (required, comma-separated), dimensions (optional, comma-separated), period, limit, filter.<key>.
curl "https://www.abner.app/api/v1/metrics?metrics=visitors,pageviews&dimensions=country,browser&period=7d&limit=10" \
-H "Authorization: Bearer $ABNER_API_KEY"
{
"site_id": "9f3c…",
"period": "7d",
"rows": [
{ "dimensions": { "country": "US", "browser": "Chrome" }, "metrics": { "visitors": 412, "pageviews": 980 } }
]
}
With no dimensions you get a single overview row (same as /stats). Add time_day or time_hour as a dimension to get a time series, where each row carries a time field.
GET /realtime
Unique visitors active in the last 5 minutes plus the most recent events. Query param: limit (default 50, max 200).
curl "https://www.abner.app/api/v1/realtime?limit=20" \
-H "Authorization: Bearer $ABNER_API_KEY"
{
"site_id": "9f3c…",
"active_visitors": 7,
"recent_events": [
{ "event_type": "pageview", "url": "https://example.com/x", "pathname": "/x", "country": "US", "browser": "Chrome", "timestamp": "2026-05-25T11:34:00Z" }
]
}
GET /vitals
p75 Core Web Vitals over the period. Query param: period (default 30d).
curl "https://www.abner.app/api/v1/vitals?period=30d" \
-H "Authorization: Bearer $ABNER_API_KEY"
{
"site_id": "9f3c…",
"period": "30d",
"vitals": { "lcp_p75": 1.8, "fid_p75": 12, "cls_p75": 0.04, "fcp_p75": 1.1, "ttfb_p75": 0.6, "inp_p75": 180 }
}
GET /funnel
A stateless, ordered per-session conversion funnel over inline steps — nothing is stored server-side; pass the steps you want each time. This is the direct mirror of the MCP query_funnel tool. Query params: steps (required, repeatable, 2-8 ordered step specs), period (default 7d), window_seconds (default 86400, max 604800).
Each step is path:<pathname> (e.g. path:/pricing) or event:<name> (e.g. event:signed_up). Steps must occur in order within one session, within window_seconds of the first step.
curl "https://www.abner.app/api/v1/funnel?steps=path:/pricing&steps=event:signed_up&period=30d" \
-H "Authorization: Bearer $ABNER_API_KEY"
{
"site_id": "9f3c…",
"period": "30d",
"steps": [
{ "step": 1, "label": "path:/pricing", "sessions": 820, "conversion_rate": 100, "dropoff_rate": 0 },
{ "step": 2, "label": "event:signed_up", "sessions": 164, "conversion_rate": 20, "dropoff_rate": 80 }
]
}
Management API
Authenticated with a management key (abner_ws_…), these endpoints manage everything in the workspace the key belongs to. Reads return 200, creates 201, deletes 204. PATCH updates only the fields you send.
Sites
| Method & path | Description |
|---|---|
| GET /api/v1/sites | List all sites in the workspace |
| POST /api/v1/sites | Create a site — body { "name", "domain", "timezone"? } |
| GET /api/v1/sites/{site_id} | Fetch one site |
| PATCH /api/v1/sites/{site_id} | Update — body any of { "name", "domain", "timezone" } |
| DELETE /api/v1/sites/{site_id} | Delete a site and its related Postgres plus ClickHouse data |
curl -X POST https://www.abner.app/api/v1/sites \
-H "Authorization: Bearer $ABNER_WS_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Marketing Site","domain":"example.com"}'
{ "site_id": "…", "name": "Marketing Site", "domain": "example.com", "public_id": "…", "timezone": "UTC", "workspace_id": "…", "created_at": "…" }
Site read keys
Mint and revoke the per-site read keys used by the read API. The raw key is returned once on creation.
| GET /api/v1/sites/{site_id}/keys | List a site's read keys (metadata only) |
| POST /api/v1/sites/{site_id}/keys | Create — body { "label"? }; response includes key once |
| DELETE /api/v1/sites/{site_id}/keys/{key_id} | Revoke a read key |
Workspace
| GET /api/v1/workspace | Fetch the current workspace |
| PATCH /api/v1/workspace | Update — body { "name" } |
Team members
The invitee must already have an Abner account; they're added to the workspace by email.
| GET /api/v1/workspace/members | List members (membership_id, user_id, email, name, role) |
| POST /api/v1/workspace/members | Add — body { "email", "role"? } (role: owner, admin, member) |
| DELETE /api/v1/workspace/members/{membership_id} | Remove a member |
Account profile and billing are managed in the web UI and the Stripe billing portal, not the API.
Sending events
To record events server-side, use the public ingest endpoint, which is authenticated by your site's public ID (not an API key). See Installation for details.
POST /api/ingest/
Content-Type: application/json
{ "site_id": "your_public_id", "url": "https://example.com/page", "referrer": "https://google.com" }
Errors
Errors return a JSON envelope with a stable machine-readable code and a human-readable message:
{
"error": { "code": "invalid_metric", "message": "Unknown metric 'foo'. See GET /api/v1/meta for the supported list." }
}
Status codes:
200/201/204— Success (read / created / deleted)400— Invalid parameter or body (invalid_period,invalid_metric,invalid_dimension,invalid_filter,invalid_interval,missing_metrics,missing_steps,invalid_steps,invalid_body,invalid_role)401— Missing or wrong-type API key (unauthorized)404— Resource not found in this workspace (not_found,user_not_found)409— Conflict, e.g. user already a member (already_member)503— Analytics backend temporarily unavailable (analytics_unavailable)