Endpoints
Query reference covers the analytics endpoint. These six are the rest of the read surface: discovering sites, one live number, one plan state, and the two calls that run a saved funnel.
| Method | Path | Purpose |
|---|---|---|
GET | /sites | The sites this key can read |
GET | /current-visitors | Visitors active right now |
GET | /props | Custom property keys a site records |
GET | /funnels | Saved funnel definitions |
POST | /funnels/{id}/report | Run one saved funnel |
GET | /subscription | The plan state of the key's owner |
Where an endpoint takes site_id, it is required on an all-sites key and optional on a single-site key.
List sites
Start here. It turns a key into the site_id values every other call needs, and doubles as a way to see what a key can actually reach.
| Query param | Notes |
|---|---|
date_range | Optional. 7d, 30d, month, realtime, or Nd for 1 to 90 days. Adds a stats block to every site. Explicit date pairs are accepted by /query only |
{
"sites": [
{
"site_id": 3093477,
"name": "example.com",
"timezone": "Europe/Amsterdam",
"stats_start_date": "2026-04-07",
"created_at": "2026-02-21T12:52:54Z"
}
]
}
| Field | Notes |
|---|---|
site_id | What you pass to every other endpoint |
name | The site as its owner entered it, often a full URL rather than a bare domain |
timezone | The reporting timezone every date in the API follows |
stats_start_date | First day with data, or null if it has not been computed |
created_at | RFC 3339 |
An all-sites key lists every site you can read, sorted by name. A single-site key lists that one, or nothing at all if your access to it ended.
Add ?date_range=7d and each site also carries headline numbers:
{
"site_id": 3093477,
"name": "example.com",
"stats": {
"date_range": "7d",
"metrics": {
"visitors": 1234, "pageviews": 5678, "visits": 900,
"visit_duration": 62, "bounce_rate": 41, "views_per_visit": 2.1
}
}
}
These totals are approximate at the edges
The stats block is computed for every site in one pass, so its period boundaries are not resolved in each site's own timezone. It is built for a quick overview across an account. When the exact window matters, ask /query instead.
Current visitors
Unique visitors active in the last five minutes.
One number, nothing to page through. For a live series rather than a single figure, use /query with date_range: "realtime" and dimensions: ["time:minute"], which covers roughly the last half hour.
Property keys
Lists the custom properties a site has actually recorded. This is discovery for the event:props:<key> breakdown, which otherwise requires you to know the key already.
date_range accepts the same forms as /query and defaults to 30d.
{
"props": [
{ "key": "plan", "event": "Signup", "count": 1240, "first_seen": "2026-06-01T10:00:00Z" },
{ "key": "method", "event": "Login", "count": 980, "first_seen": "2026-06-02T09:12:00Z" }
]
}
Each entry is a key and event pair, because a property belongs to the event it was sent with. The same key name under two events is two entries. Take both values into the next query: event as a filter, key as the dimension.
Saved funnels
Funnels sit outside /query on purpose. A funnel result is an ordered sequence with a cumulative rate at each position, and the {dimensions, metrics} row envelope treats rows as an unordered set. Forcing one into the other would misrepresent it.
These endpoints only run funnels. Definitions are built in the dashboard under Custom Analytics.
{
"funnels": [
{
"id": 45,
"site_id": 3093477,
"name": "Signup flow",
"scope": "visitor",
"strict_order": false,
"steps": [
{ "kind": "page", "path": "/pricing", "operator": "e" },
{ "kind": "event", "event": "Signup" },
{ "kind": "goal", "goal_id": 9 }
],
"created_at": "2026-06-01T10:00:00Z",
"updated_at": "2026-06-01T10:00:00Z"
}
]
}
Step kind is one of page, event, goal, scroll, entry_page or exit_page, and the remaining fields on a step depend on its kind.
Run a funnel
{
"site_id": 3093477,
"date_range": "30d",
"filters": [ { "field": "country", "operator": "is", "values": ["DE"] } ]
}
| Field | Required | Notes |
|---|---|---|
site_id | Conditional | As everywhere else |
date_range | Yes | Same forms as /query, including explicit pairs up to 366 days |
filters | No | Session fields only |
date_range decides which sessions are examined. It does not change how long a visitor has to finish: that window is one day, always, whatever range you ask for.
Filters here accept session and segment fields only, such as country, browser, device, source, channel, utm parameters, entry and exit page, hostname. Event-level fields (event, page, code) are rejected with 400 invalid_filter, because the funnel's own steps already decide which events count.
{
"funnel": { "id": 45, "name": "Signup flow", "scope": "visitor", "strict_order": false },
"entering": 4210,
"all_visitors": 51890,
"steps": [
{ "index": 0, "name": "Visited /pricing", "kind": "page", "visitors": 4210, "conversion_rate": 100.0, "dropoff": 0 },
{ "index": 1, "name": "Signup", "kind": "event", "visitors": 2870, "conversion_rate": 68.17, "dropoff": 1340 },
{ "index": 2, "name": "Signup completed", "kind": "goal", "visitors": 1120, "conversion_rate": 26.6, "dropoff": 1750 }
]
}
Three things to read correctly:
stepsis already ordered.indexis its position from zero. Do not sort it.conversion_rateis cumulative, measured againstenteringrather than the step before. Step 2 above kept 26.6% of everyone who started, not 26.6% of step 1.dropoffis the opposite, counted against the previous step alone.
entering is how many visitors reached step 0, which is the denominator for every rate. all_visitors is the site's total for the period and has nothing to do with the funnel; it tells you how large a slice the funnel represents.
An id that does not belong to the resolved site returns 404 unknown_funnel.
Subscription
The plan state of whoever owns the key, in one line's worth of fields. Enough to render "Trial, 12 days left" somewhere in your own tooling; not a billing page.
| Field | Notes |
|---|---|
status | One of trialing, active, past_due, expired, trial_expired, none |
is_trial | True while the account is inside its trial |
ends_at | When the current state stops being true: the trial's end while trialing, the paid period's end otherwise. Absent when unknown |
only_hobby | True when the account holds hobby sites and nothing else |
It takes no site_id: the answer is about the account, not a site. An account with only hobby sites has no subscription and answers status: "none" with only_hobby: true.
Next steps
- Query reference: the analytics endpoint itself
- Errors: the failures these endpoints return
Ready to take control of your web analytics? Try Statable free for 30 days. No credit card required, full feature access, built for GDPR. Start your free trial or view a live demo.