Skip to content

API reference

The public API covers event ingestion, revenue ingestion and reading the reports of one website. Every request is authenticated with an API key.

Base URL and authentication

The base URL is https://abackanalytics.com and every endpoint below starts with /api/v1. Send the API key as a bearer token:

headerhttp
Authorization: Bearer abk_xxxxxxxx.secret

The key identifies one website. A website id in the body is checked against the key, and a mismatch is refused. The key is created on the Developers page of the website, the secret is shown exactly once, and it can be revoked at any time.

OpenAPI specification

This reference is also published as an OpenAPI 3.1 document, served as JSON at /api/openapi. It describes every endpoint, with the request bodies, the response schemas, the error codes and the limits. The OpenAPI page lists them in a table.

terminalbash
curl https://abackanalytics.com/api/openapi

Response envelope

A successful call answers with an ok flag and a data object. A failed call answers with an ok flag of false and an error object with a stable code and a message that is safe to show to a user.

successjson
{ "ok": true, "data": { ... } }
failurejson
{
  "ok": false,
  "error": {
    "code": "validation_failed",
    "message": "Event name is required."
  },
  "requestId": "req_7c1f2a9b4d6e8f0a2b3c"
}

Every response carries a request id header, and the request id is repeated in the body of a failure. Quote it when you report a problem.

Scopes

A key carries the scopes it was created with. A request that needs a scope the key does not have is refused with a forbidden error, so a key that can only send events cannot read your reports.

scopestext
events:write   send events from your server
revenue:write  report confirmed payments
reports:read   read the reports of the website
export:read    download the events of the website as a CSV

Rate limits and payload limits

  • Public ingestion is rate limited per website and per client address at the edge. A request over the limit is refused with the code rate_limited and a Retry-After header.
  • The server-to-server endpoints accept a body of at most 32 KB. A larger body is refused with payload_too_large before it is parsed.
  • Accepted events are metered against the monthly limit of the plan. The current usage is visible in the dashboard.

POST /api/v1/events

Sends one event from your server. Scope: events:write. Answers 202 when the event was accepted and 200 when it was a duplicate.

terminalbash
curl -X POST https://abackanalytics.com/api/v1/events \
  -H "Authorization: Bearer abk_xxxxxxxx.secret" \
  -H "content-type: application/json" \
  -d '{
    "websiteId": "abk_xxxxxxxxxxxxxxxx",
    "event": "signup_completed",
    "properties": { "plan": "pro" }
  }'
responsejson
{
  "ok": true,
  "data": {
    "eventId": "evt_9f2c41b7ac0d4e6f8a1b",
    "status": "accepted"
  }
}

Body fields:

bodytext
websiteId   required   the public tracking id of the website
event        required   the event name, 1 to 64 characters
properties  optional   at most 20 properties, see the events page
timestamp   optional   epoch milliseconds, clamped by the server
visitorId   optional   the identifier from the visitor browser
sessionId   optional   links the event to a known session
path        optional   the page path the event happened on

POST /api/v1/revenue

Records one confirmed payment. Scope: revenue:write. Answers 201 when the payment was recorded and 200 when the idempotency key was already used.

terminalbash
curl -X POST https://abackanalytics.com/api/v1/revenue \
  -H "Authorization: Bearer abk_xxxxxxxx.secret" \
  -H "content-type: application/json" \
  -d '{
    "websiteId": "abk_xxxxxxxxxxxxxxxx",
    "idempotencyKey": "order_2026_10_00041",
    "amount": 1999,
    "currency": "USD",
    "customerRef": "cust_8f21"
  }'
responsejson
{
  "ok": true,
  "data": {
    "revenueEventId": "rev_3c9d5a1e7b2f4a6c8d0e",
    "status": "recorded",
    "attributed": true
  }
}

Body fields:

bodytext
websiteId      required   the public tracking id of the website
idempotencyKey required   8 to 128 characters, unique per payment
amount         required   integer minor units, for example 1999
currency       required   three letter ISO code
provider       optional   defaults to "manual"
type           optional   payment, refund, subscription or chargeback
customerRef    optional   an opaque reference, never an email address
productId      optional   the product or plan identifier
visitorId      optional   the identifier from the visitor browser
sessionId      optional   links the payment to a known session
attributionId  optional   an opaque attribution reference
timestamp      optional   epoch milliseconds
properties     optional   at most 20 properties

GET /api/v1/reports/overview

Returns the totals, the daily series, the goal statistics and the revenue summary of the website for a date range. Scope: reports:read.

terminalbash
curl "https://abackanalytics.com/api/v1/reports/overview?range=last_30_days" \
  -H "Authorization: Bearer abk_xxxxxxxx.secret"
responsejson
{
  "ok": true,
  "data": {
    "website": { "id": "site_...", "name": "Marketing site", "timezone": "UTC" },
    "range": { "start": "2026-09-23", "end": "2026-10-23", "days": 30, "timezone": "UTC" },
    "totals": {
      "visitors": 4210,
      "pageviews": 11894,
      "sessions": 5031,
      "events": 812,
      "newVisitors": 3104,
      "returningVisitors": 1106,
      "bounceRate": 0.41,
      "averageSessionSeconds": 96,
      "viewsPerVisitor": 2.82
    },
    "series": [
      { "date": "2026-09-23", "visitors": 121, "pageviews": 344, "sessions": 140 }
    ],
    "goals": [
      {
        "id": "goal_...",
        "name": "Signup",
        "eventName": "signup_completed",
        "kind": "event",
        "conversions": 96,
        "visitors": 91,
        "conversionRate": 0.0216
      }
    ],
    "revenue": [
      {
        "currency": "USD",
        "grossMinor": 191904,
        "refundsMinor": 1999,
        "netMinor": 189905,
        "payments": 96,
        "customers": 94
      }
    ]
  }
}

Query parameters:

querytext
range   optional   one of: last_24_hours, today, yesterday, last_7_days, last_30_days, last_90_days, this_month, last_month, last_12_months, year_to_date, custom
                  defaults to last_30_days
start   optional   YYYY-MM-DD, used when range is custom
end     optional   YYYY-MM-DD, used when range is custom, exclusive

GET /api/v1/reports/breakdown

Breaks a dimension down for the same range. Scope: reports:read. The dimension is checked against an allowlist, and an unknown value is refused with an invalid request error.

terminalbash
curl "https://abackanalytics.com/api/v1/reports/breakdown?dimension=source&range=last_30_days&limit=10" \
  -H "Authorization: Bearer abk_xxxxxxxx.secret"
responsejson
{
  "ok": true,
  "data": {
    "website": { "id": "site_...", "name": "Marketing site" },
    "range": { "start": "2026-09-23", "end": "2026-10-23", "days": 30 },
    "dimension": "source",
    "rows": [
      { "value": "google", "visitors": 1904, "pageviews": 5210, "sessions": 2210, "events": 402 }
    ]
  }
}

Query parameters:

querytext
dimension  optional   one of: source, medium, campaign, channel, referrer_host, path, landing_page, country, device, browser, os, screen, event_name, term, content
                     defaults to source
range      optional   one of: last_24_hours, today, yesterday, last_7_days, last_30_days, last_90_days, this_month, last_month, last_12_months, year_to_date, custom
limit      optional   1 to 200, defaults to 25
start      optional   YYYY-MM-DD, used when range is custom
end        optional   YYYY-MM-DD, used when range is custom, exclusive

GET /api/v1/live

The activity of the last few minutes: the most recent events, the number of visitors in the window and the time of the last event. Scope: reports:read.

The window is at most 60 minutes and the number of events is at most 200, because the endpoint reads the event stream rather than an aggregate. It is rate limited.

terminalbash
curl "https://abackanalytics.com/api/v1/live?minutes=5&limit=50" \
  -H "Authorization: Bearer abk_xxxxxxxx.secret"
responsejson
{
  "ok": true,
  "data": {
    "website": { "id": "site_...", "name": "Marketing site" },
    "window": { "minutes": 5, "since": 1790000000000, "until": 1790000300000 },
    "visitors": 12,
    "lastEventAt": 1790000299000,
    "events": [
      {
        "eventId": "evt_...",
        "eventName": "pageview",
        "eventType": "pageview",
        "path": "/pricing",
        "title": "Plans and pricing",
        "source": "twitter",
        "channel": "Social",
        "country": "US",
        "device": "desktop",
        "occurredAt": 1790000299000
      }
    ]
  }
}

Query parameters:

querytext
minutes    optional   1 to 60, defaults to 5
limit      optional   1 to 200, defaults to 50

GET /api/account/sites

Lists the websites of the account the signed in user belongs to. This endpoint is authenticated with the session cookie of the dashboard, not with an API key, because it is scoped to the whole account rather than to one website.

The account is resolved from the session on the server. There is no account parameter in the query string, so there is no way to ask for the websites of another account. The answer carries the public tracking id of each website and never a secret: no visitor salt, no API key and no integration credential.

terminalbash
curl https://abackanalytics.com/api/account/sites \
  -H "Cookie: abk_session=..."
responsejson
{
  "ok": true,
  "data": {
    "websites": [
      {
        "id": "site_...",
        "name": "Marketing site",
        "domain": "example.com",
        "timezone": "UTC",
        "status": "active",
        "trackingId": "abk_xxxxxxxxxxxxxxxx",
        "retentionDays": 30
      }
    ]
  }
}

Fields of each website:

websitetext
id             the identifier of the website
name           the name of the website
domain         the canonical domain, empty when there is none yet
timezone       the timezone the days are counted in
status         active, paused or archived
trackingId     the public tracking id, safe to put in a browser snippet
retentionDays  the number of days the raw events are kept

Error codes

StatusCodeMeaning
400invalid_requestThe request itself is wrong, for example an unknown date range or an unknown dimension.
400validation_failedThe body did not match the schema. The error details list the field and the reason for each issue.
401unauthorizedThe Authorization header is missing, or the key is not valid, expired or revoked.
402plan_restrictedThe plan of the account does not include the feature the endpoint needs.
402quota_exceededThe monthly limit of the plan has been reached.
403forbiddenThe key is valid but the scope is missing, or the website in the body does not belong to the key.
404not_foundThe resource does not exist.
409conflictThe request conflicts with an existing record, for example an account that already exists.
413payload_too_largeThe body is larger than the endpoint accepts.
429rate_limitedToo many requests. The response carries a Retry-After header.
500internal_errorSomething failed on the server. The message is generic on purpose. Quote the request id when you report it.

The remote MCP server

AI assistants that speak the Model Context Protocol can read your analytics at https://abackanalytics.com/api/mcp. Add it as a remote MCP server; a client that supports MCP authorization signs in by itself. It opens a page where you sign in, pick the one website it may read and approve. The assistant gets the read tools only: the overview, a breakdown and the live visitors.

Sign in follows OAuth 2.1 as the MCP authorization spec describes. A request without a token gets a 401 whose WWW-Authenticate header names the protected resource metadata. A client identifies itself with the https address of its client metadata document, or registers itself. There are no client secrets, PKCE with S256 is required, and the tokens are only valid at the MCP server. An access token lasts an hour and a refresh token 30 days; each refresh replaces both.

You can see and revoke the assistants you connected on the Developers page of the website. Access also ends when you leave the account.

endpointstext
MCP server                https://abackanalytics.com/api/mcp
Protected resource        https://abackanalytics.com/.well-known/oauth-protected-resource/api/mcp
Authorization server      https://abackanalytics.com/.well-known/oauth-authorization-server
Authorization             https://abackanalytics.com/oauth/authorize
Token                     https://abackanalytics.com/api/oauth/token
Dynamic registration      https://abackanalytics.com/api/oauth/register
Revocation                https://abackanalytics.com/api/oauth/revoke

A client that cannot sign in can send an API key with the reports:read scope instead.

terminalbash
curl -X POST https://abackanalytics.com/api/mcp   -H "Authorization: Bearer abk_xxxxxxxx.secret"   -H "Content-Type: application/json"   -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

The command line interface

A small command line tool reads the same endpoints. It takes the key from the environment, so the key never ends up in a shell history.

terminalbash
export ABACK_API_KEY=abk_xxxxxxxx.secret
aback health

The commands are:

commandstext
aback health                            the state of the installation
aback report overview                   the totals, the goals and the revenue
aback report breakdown --dimension source   one dimension broken down
aback live                              the activity of the last few minutes
aback send signup_completed             record one event
aback snippet                           the tracking snippet

The full list is in the cli/README.md file of the repository.

Not documented here

This page documents the endpoints you call with an API key, and the account scoped read endpoint above. The CSV export uses a session cookie and answers with a file instead of the JSON envelope, so it is described in the OpenAPI document rather than here. There is no endpoint to manage members, roles or subscriptions over HTTP. If you need one of those, ask: the events page has the event contract and the revenue page has the payment contract.