REST API overview
Base URL, bearer authentication, the JSON envelope, error codes, rate limits, date ranges, the filter DSL and cursor paging.
The API is JSON over HTTPS. Every path below is relative to https://statsy.co/api/v1. The hosted API also answers on https://api.statsy.co, which is what the CLI uses by default.
Authentication
Send an API token as a bearer token. There is no other header to set.
curl -H "Authorization: Bearer sta_your_token" \
"https://statsy.co/api/v1/sites/ws_yoursitekey/overview?from=30d"In a path, :site accepts either the site’s public id or its ws_ public key. A missing or revoked token gives 401.
Response envelope
Every JSON response uses one of two shapes.
{ "status": "success", "data": { } }
{ "status": "error", "error": { "code": "bad_dimension", "message": "..." } }The CSV export is the one exception: it streams text/csv with a Content-Disposition filename instead.
Errors
| Status | Typical codes | Meaning |
|---|---|---|
| 400 | validation_error, bad_range, bad_dimension, bad_filters, bad_cursor | The request was malformed. The message says which part. |
| 401 | unauthorized, invalid_token | No bearer token, or the token is unknown or revoked. |
| 402 | trial_expired, plan_cancelled, plan_limit | Reads still work; writes are paused until you pick a plan, or a plan limit was reached. |
| 403 | forbidden, session_required | Your role or token kind is not allowed to do this. |
| 404 | site_not_found, goal_not_found, not_found | No such site, resource or route. |
| 409 | already_member, widget_limit, last_owner | The request conflicts with the current state. |
| 429 | rate_limited, export_busy | Too many requests. Wait and retry. |
| 500 | internal_error | Something went wrong on our side. |
Rate limits
Token-authenticated requests are limited to 60 per minute; requests from a signed-in dashboard session get 600. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset, and a 429 adds Retry-After in seconds. A few endpoints have their own, tighter limits, noted on their pages.
Dates, presets and time zones
Analytics endpoints share the same range parameters. from takes either a preset or a date; to takes a date and defaults to now.
| Preset | Means |
|---|---|
now | The last 30 minutes, always from raw events. |
24h | A rolling 24 hours. |
7d | Seven calendar days in the site’s time zone, up to now. |
30d | Thirty calendar days. This is the default when from is omitted. |
90d | Ninety calendar days. |
12mo | The start of the month eleven months ago, up to now. |
For exact windows pass an ISO timestamp or YYYY-MM-DD. A bare date is read as UTC midnight. from must be before to, and a range may not exceed 400 days, or you get 400 bad_range.
tz sets the IANA time zone for day and month boundaries. It defaults to the site’s time zone, and an unrecognised value falls back to UTC. Add compare=prev to get the equally long window immediately before yours alongside the current one.
Note. Responses include
sourceandclamped. Raw events are kept 90 days, so a long range that needs raw events is clamped to the last 90 days andclampedbecomestrue. Rolled-up totals go back as far as the site does.
Filters
filters is a URL-encoded JSON array of {k, op, v} objects, up to 20, all combined with AND.
curl -G -H "Authorization: Bearer sta_your_token" \
--data-urlencode 'filters=[{"k":"country","op":"is","v":"US"},{"k":"path","op":"contains","v":"/blog"}]' \
"https://statsy.co/api/v1/sites/ws_yoursitekey/breakdown?dimension=channel"Keys: path, entry_path, referrer_host, channel, utm_source, utm_medium, utm_campaign, country, region, city, device, browser, os, lang, goal and post. Operators: is (the default), not, contains and not_contains; the two contains forms ignore case.
segment=<id> loads a saved filter set and adds its filters to yours. Filtering on entry_path, region, city, lang, goal or post forces the query onto raw events, so the range is clamped to 90 days.
Paging
List endpoints take limit and cursor. A cursor is an opaque string; pass back the one the previous response returned, and stop when it is null. The field name differs by endpoint: breakdowns and visitors return cursor, replays return next_cursor, and payments return nextCursor.
Last updated · Markdown version