API
Read your stats as JSON: totals, a daily series and your dashboard’s top lists. With a key, also manage sites, settings and funnels.
Using an AI assistant? Connect it with the MCP server.
Authentication
Public sites need no key. For a private site, send an API key in the Authorization header.
Authorization: Bearer sim_key_YOUR_KEY
Make keys on your account page. Header only, from your server. Never put a key in a URL or a web page.
Endpoint
GET https://simplytics.dev/api/v1/stats/{domain}
| Parameter | Default | Accepts |
|---|---|---|
start, end | The last 30 days | Dates as YYYY-MM-DD, in your site’s timezone. Up to 400 days. |
include | None | Any of the lists below, comma-separated. |
limit | 10 | Rows per list, 1 to 1000. |
metric | visitors | visitors or views: what the lists count and rank by. |
With metric=views, lists count views instead, as in {page, views}. The lists table notes the exceptions.
Lists
Any include also adds bounce_rate, a percent, and avg_duration_seconds to the totals.
| include | Each row |
|---|---|
daily | {date, visitors, views, sessions} One per day, oldest first. |
pages | {page, visitors} Top pages. |
sources | {source, visitors} Referring sites. |
channels | {channel, visitors} Direct, Search, Social, AI, Email and Referral. Always visitors. |
countries | {code, name, visitors} Two-letter country code and its English name. |
devices | {device, visitors} Desktop, Mobile and Tablet. |
browsers | {browser, visitors} |
os | {os, visitors} |
campaigns | {campaign, visitors} UTM source and campaign. |
events | {name, count, visitors} Custom events, most fired first, with either metric. Up to 50. |
clicks | {outbound: [...], downloads: [...]} Rows are {target, visitors}, or {target, clicks} with metric=views. Up to 10 of each. |
Examples
Totals for a public site
curl https://simplytics.dev/api/v1/stats/example.com
{
"domain": "example.com",
"period": {"start": "2026-10-02", "end": "2026-10-31"},
"visitors": 1204,
"views": 3310,
"sessions": 1587
}
Top pages and countries
curl -H "Authorization: Bearer sim_key_YOUR_KEY" \
"https://simplytics.dev/api/v1/stats/example.com?include=pages,countries&limit=3"
{
"domain": "example.com",
"period": {"start": "2026-10-02", "end": "2026-10-31"},
"visitors": 1204,
"views": 3310,
"sessions": 1587,
"bounce_rate": 46,
"avg_duration_seconds": 71,
"pages": [
{"page": "/", "visitors": 702},
{"page": "/pricing", "visitors": 311},
{"page": "/blog/hello", "visitors": 148}
],
"countries": [
{"code": "US", "name": "United States", "visitors": 380},
{"code": "IN", "name": "India", "visitors": 233},
{"code": "DE", "name": "Germany", "visitors": 121}
]
}
Three days, day by day
curl -H "Authorization: Bearer sim_key_YOUR_KEY" \
"https://simplytics.dev/api/v1/stats/example.com?start=2026-10-01&end=2026-10-03&include=daily"
{
"domain": "example.com",
"period": {"start": "2026-10-01", "end": "2026-10-03"},
"visitors": 124,
"views": 329,
"sessions": 157,
"bounce_rate": 44,
"avg_duration_seconds": 68,
"daily": [
{"date": "2026-10-01", "visitors": 41, "views": 112, "sessions": 52},
{"date": "2026-10-02", "visitors": 38, "views": 97, "sessions": 47},
{"date": "2026-10-03", "visitors": 45, "views": 120, "sessions": 58}
]
}
Public sites
Without a key, a public site’s API shows what its public dashboard shows.
- With
include, the range snaps to the dashboard’s presets: the last 1, 7, 10, 30, 90, 180 or 365 days. - Campaigns stop at 10.
- Answers carry
Access-Control-Allow-Origin: *, so a web page can fetch them. Answers to a key do not.
Keys
Make as many keys as you need, of two kinds:
- Access key: reads or changes your data, within its scope.
- Master key: makes, lists, rolls and deletes keys, and can’t see your data. Keys it makes can have any access. One per account.
An access key has a name and a scope:
| Scope | Choices |
|---|---|
| Sites | All, including sites you add later, or a list of your domains. |
| Data | Everything, or totals plus the lists you pick. |
| Permissions | analytics:read reads stats. The others are under Manage sites and funnels. |
A key outside its scope gets a 403 naming what it can’t do, like {"error": "This key can't read example.com"}.
To rotate a key, make a new one, switch your code to it, then delete the old one. To rotate the master key, roll it.
An access key can expire, if you pick a time as you make it. Then it gets a 401. A master key never expires.
Make a key with the master key
POST https://simplytics.dev/api/v1/keys
Send JSON with a name and permissions. Sites and data default to all. ["keys:create"] alone makes a master key, if the account has none.
To make a key expire, add "expires_at": "2026-12-31T23:59:00Z". Leave it out for never.
curl -X POST -H "Authorization: Bearer sim_key_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "Weekly report", "permissions": ["analytics:read"], "sites": ["example.com"], "data": ["pages"]}' \
https://simplytics.dev/api/v1/keys
{
"id": 12,
"name": "Weekly report",
"last4": "Qx3w",
"kind": "access",
"permissions": [
"analytics:read"
],
"sites": [
"example.com"
],
"data": [
"pages"
],
"created_at": "2026-10-31T09:30:00.000Z",
"created_by": "key:3",
"last_used_at": null,
"expires_at": null,
"key": "sim_key_4dQj…Qx3w"
}
The new key is in this answer only, so store it then.
List, roll and delete keys
GET https://simplytics.dev/api/v1/keys
POST https://simplytics.dev/api/v1/keys/{id}/roll
DELETE https://simplytics.dev/api/v1/keys/{id}
The master key lists every key on the account, never a secret.
Roll gives a key a new secret, in its answer only. The old one stops at once.
Manage sites and funnels
An access key with the right permissions can add, change and delete your sites and funnels, and set each site’s logo and custom domain.
Nothing in the API adds views or visitors or changes a count.
| Permission | Lets a key |
|---|---|
analytics:read | Read stats. |
sites:read | List sites. |
sites:edit | Add sites. |
sites:delete | Delete sites. |
funnels:read | Read funnels and their counts. |
funnels:edit | Make and rename funnels. |
funnels:delete | Delete funnels. |
settings:read | Read a site’s settings and custom domain. |
settings:edit | Change a site’s settings and logo and custom domain. |
history:export | Download the export ZIP. |
keys:create | The master key: make, list, roll and delete keys. Nothing else. |
Delete is its own permission: edit never includes it. history:import isn’t available yet.
Paths are under https://simplytics.dev/api/v1.
| Endpoint | Needs | Does |
|---|---|---|
GET /sites | sites:read | Your sites, in dashboard order. |
POST /sites | sites:edit | Adds a site, as the dashboard does, and returns its tracking tag. Body: {"domain": …, "timezone": …}. |
GET /sites/{domain} | settings:read | Its visibility, badge, timezone, when it was added, and its custom_domain and logo_url. |
PATCH /sites/{domain} | settings:edit | Any of visibility (private, public, badge_only), badge (metric, period) and timezone. |
DELETE /sites/{domain} | sites:delete | Deletes it and its stats for good. |
GET /sites/{domain}/custom_domain | settings:read | Its custom domain: hostname, status, the CNAME record to add, and the snippet once it is active. Null if it has none. |
POST /sites/{domain}/custom_domain | settings:edit | Adds one: {"hostname": "stats"} for stats.{domain}, or the full name. |
DELETE /sites/{domain}/custom_domain | settings:edit | Removes it. |
PUT /sites/{domain}/logo | settings:edit | Sets its logo: the image itself as the body. PNG, JPEG or WebP, up to 100 KB. |
DELETE /sites/{domain}/logo | settings:edit | Removes it. |
GET /sites/{domain}/funnels | funnels:read | Each funnel’s steps and counts for start to end (UTC days), 25 at a time. For the next 25, pass after: the response’s next_after (null on the last page). |
POST /sites/{domain}/funnels | funnels:edit | Makes a funnel: name (up to 100 characters) and 2 to 10 steps (up to 512 each). Unlimited funnels on every paid plan; up to 100 during the free trial. |
PATCH /sites/{domain}/funnels/{id} | funnels:edit | Renames it: {"name": …}. |
DELETE /sites/{domain}/funnels/{id} | funnels:delete | Deletes it and its counts for good. |
GET /sites/{domain}/export | history:export | The dashboard’s Everything (ZIP) export, for start to end. |
Add a site
curl -X POST -H "Authorization: Bearer sim_key_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"domain": "shop.example.com", "timezone": "Europe/Berlin"}' \
https://simplytics.dev/api/v1/sites
{
"domain": "shop.example.com",
"verified": false,
"visibility": "private",
"badge": {"metric": "visitors", "period": "month"},
"timezone": "Europe/Berlin",
"created_at": "2026-11-02T09:30:00.000Z",
"custom_domain": null,
"logo_url": null,
"snippet": "<script async src=\"https://simplytics.dev/track.js\" data-key=\"a1b2c3d4\"></script>"
}
Make a dashboard public
curl -X PATCH -H "Authorization: Bearer sim_key_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"visibility": "public"}' \
https://simplytics.dev/api/v1/sites/example.com
{
"domain": "example.com",
"verified": true,
"visibility": "public",
"badge": {"metric": "visitors", "period": "month"},
"timezone": "Europe/Berlin",
"created_at": "2026-06-14T08:12:44.000Z",
"custom_domain": null,
"logo_url": null
}
Make a funnel
curl -X POST -H "Authorization: Bearer sim_key_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "Signup", "steps": ["/", "/pricing", "/signup"]}' \
https://simplytics.dev/api/v1/sites/example.com/funnels
{
"id": 41,
"name": "Signup",
"steps": [
"/",
"/pricing",
"/signup"
]
}
Delete a site
curl -X DELETE -H "Authorization: Bearer sim_key_YOUR_KEY" \
https://simplytics.dev/api/v1/sites/example.com
{
"domain": "example.com",
"deleted": true
}
Export everything
curl -H "Authorization: Bearer sim_key_YOUR_KEY" -o example.com.zip \
"https://simplytics.dev/api/v1/sites/example.com/export?start=2026-10-01&end=2026-10-31"
The same ZIP as the dashboard’s export. It holds every list, so the key’s data must be Everything.
Logo and custom domain
Each needs the same check as Public: our tag on your homepage, or a DNS record. It runs on its own.
Removing an active custom domain emails you first, naming the key. A snippet loading from it stops counting.
If the email can’t go, nothing is removed. Logo changes and other custom domain changes don’t email you.
Send the logo as the request body. We read its type from the file itself, so an SVG is refused even when named .png.
A custom domain is pending until its CNAME to proxy.simplytics.dev is live, then active, with its own snippet.
The rules are on Custom domains.
Add a custom domain
curl -X POST -H "Authorization: Bearer sim_key_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"hostname": "stats"}' \
https://simplytics.dev/api/v1/sites/example.com/custom_domain
{
"domain": "example.com",
"custom_domain": {"hostname": "stats.example.com", "status": "pending", "detail": null, "record": {"type": "CNAME", "name": "stats.example.com", "target": "proxy.simplytics.dev"}, "snippet": null}
}
Upload a logo
curl -X PUT -H "Authorization: Bearer sim_key_YOUR_KEY" \
-H "Content-Type: image/png" \
--data-binary @logo.png \
https://simplytics.dev/api/v1/sites/example.com/logo
{
"domain": "example.com",
"logo_url": "https://simplytics.dev/logo/3f9c2e7a51b84d06c1e9a8f27d4b6035e81c9f4a72d6b0e3958a1c4f7e2d6b90.png"
}
Public and badge-only
Turning either on checks that you control the domain, as the dashboard does, then emails you. If the email can’t go, nothing changes.
Deletes
A delete is permanent, as on the dashboard. We email you first, naming the key. If the email can’t go, nothing is deleted.
Good to know
- The timezone is your account’s, for every site, so only a key for all sites can change it.
- To change a funnel’s steps, make a new funnel and delete the old one.
- A key limited to some sites can only add those domains.
Errors
Errors come as JSON with a message, like {"error": "Subscription required"}.
| Status | When |
|---|---|
| 400 | A parameter or field is wrong. The message names it. |
| 401 | The key is wrong, was deleted or expired. |
| 403 | No key and the site is not public, the subscription has ended, the key’s scope doesn’t cover the request, or we couldn’t confirm you control the domain. The message says which. |
| 404 | No verified site with that name on the key’s account, or no custom domain or logo to remove. |
| 409 | The site is already added, has had no visits yet, changed while saving or deleting, already has a custom domain, the hostname is already in use, waits for Check now beside Public on its dashboard, or a free trial already has 100 funnels. |
| 413, 415 | The logo is over 100 KB, or isn’t a PNG, JPEG or WebP. |
| 502, 503 | We couldn’t email you about a delete, a public change or removing an active custom domain, so nothing changed. Or Cloudflare refused a custom domain. |
| 429 | Too many requests from your address, with your key or across your account’s keys, 100 new keys or 10 emailed changes in a day, or a second check that you control the domain within a minute. Wait the seconds in Retry-After. |
Limits
- 60 requests a minute per IP address.
- 60 requests a minute per key, from any number of addresses.
- 600 requests a minute across all of an account’s keys.
- Without a key, 30 a minute with
include. - A range longer than 400 days keeps its latest 400.
- 100 new keys a day per account. No limit on how many you keep.
- 10 emailed changes (deletes, public, removing an active custom domain) a day per account. Past that, they wait until tomorrow.