Skip to content

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.

MethodPathPurpose
GET/sitesThe sites this key can read
GET/current-visitorsVisitors active right now
GET/propsCustom property keys a site records
GET/funnelsSaved funnel definitions
POST/funnels/{id}/reportRun one saved funnel
GET/subscriptionThe 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

GET /api/v1/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 paramNotes
date_rangeOptional. 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
curl -s https://statable.com/api/v1/sites \
  -H "Authorization: Bearer stbl_YOUR_KEY"
{
  "sites": [
    {
      "site_id": 3093477,
      "name": "example.com",
      "timezone": "Europe/Amsterdam",
      "stats_start_date": "2026-04-07",
      "created_at": "2026-02-21T12:52:54Z"
    }
  ]
}
FieldNotes
site_idWhat you pass to every other endpoint
nameThe site as its owner entered it, often a full URL rather than a bare domain
timezoneThe reporting timezone every date in the API follows
stats_start_dateFirst day with data, or null if it has not been computed
created_atRFC 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

GET /api/v1/current-visitors?site_id=3093477

Unique visitors active in the last five minutes.

{ "site_id": 3093477, "visitors": 42 }

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

GET /api/v1/props?site_id=3093477&date_range=30d

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

GET /api/v1/funnels?site_id=3093477

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

POST /api/v1/funnels/{id}/report
{
  "site_id": 3093477,
  "date_range": "30d",
  "filters": [ { "field": "country", "operator": "is", "values": ["DE"] } ]
}
FieldRequiredNotes
site_idConditionalAs everywhere else
date_rangeYesSame forms as /query, including explicit pairs up to 366 days
filtersNoSession 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:

  • steps is already ordered. index is its position from zero. Do not sort it.
  • conversion_rate is cumulative, measured against entering rather than the step before. Step 2 above kept 26.6% of everyone who started, not 26.6% of step 1.
  • dropoff is 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

GET /api/v1/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.

{ "status": "trialing", "is_trial": true, "ends_at": "2026-10-14T08:10:48Z", "only_hobby": false }
FieldNotes
statusOne of trialing, active, past_due, expired, trial_expired, none
is_trialTrue while the account is inside its trial
ends_atWhen the current state stops being true: the trial's end while trialing, the paid period's end otherwise. Absent when unknown
only_hobbyTrue 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


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.