API reference
Read your analytics from your own code: the same figures as the dashboard, as JSON. To ask an AI assistant instead, see Connect an AI assistant (MCP).
What it is for
Your own panel
Show visitors, sales or a funnel inside your admin, intranet or product.
Daily summary in Slack or by email
A script that runs every morning, reads yesterday's summary and posts it where your team reads.
Spreadsheets and BI
Copy the figures to Google Sheets, Metabase or your data warehouse to cross them with sales, CRM or costs.
Reports for clients
Agencies and freelancers: one key per client site and a monthly report built automatically.
Your own alerts
Pro already emails you about drops, spikes, 404s, goals with no conversions and new AI agents (Email alerts). For anything else, such as a day with no sales, a scheduled job that reads the figures and warns you.
A counter on your site
Show "X readers this month" from your server. Never put the key in the page.
Access
- Part of Pro. Create a key in Settings → API and MCP, for all your sites or for one.
- Send it in every request: Authorization: Bearer sd_…
- Read-only: GET requests only. 60 requests per minute per key.
- Use it from a server or a script, never from a web page: anyone could read the key in the page.
Example
curl -H "Authorization: Bearer sd_..." \
"https://api.simpledata.dev/v1/sites/SITE_ID/summary?period=30d&compare=true"{
"site_id": "5b1c…",
"range": { "start": "2026-10-01T09:00:00.000Z", "end": "2026-10-31T09:00:00.000Z", "period": "30d", "timezone": "Europe/Madrid" },
"filters": {},
"pageviews": 18420,
"visitors": 6115,
"bounce_rate": 41.2,
"avg_visit_duration_seconds": 94,
"views_per_visit": 2.4,
"top_page": "/",
"previous": {
"range": { "start": "2026-08-31T08:59:59.999Z", "end": "2026-09-30T08:59:59.999Z" },
"pageviews": 15002, "visitors": 5230, "bounce_rate": 43.8,
"avg_visit_duration_seconds": 88, "views_per_visit": 2.3, "top_page": "/"
}
}Endpoints
All under https://api.simpledata.dev/v1. site_id is the id GET /sites returns.
| GET /sites | The sites the key can read: id, name, domain, type, currency and timezone. |
| GET /sites/{site_id}/summary | Pageviews, visitors, bounce rate (%), visit duration (s), pages per visit and top page. compare=true adds previous: the same span before, or null if the site did not record all of it. With days imported from another tool in the range, imported says how many (days, first_day, last_day, sources, pageviews) and whether they are in the figures (included; not with a filter, excluded_reason "filters"). comparable is "pageviews" when only pageviews can be compared, because the two spans mix imported days and SimpleData's own differently (see /docs/import). |
| GET /sites/{site_id}/timeseries | points: pageviews and visitors per bucket, empty ones included. interval=hour, day, week or month, on the site's clock (time is the instant each bucket starts, in UTC); by default hour up to 2 days, day up to 400 days, month beyond. Hourly covers 31 days at most. |
| GET /sites/{site_id}/breakdown/{dimension} | rows of { value, count, visitors } for one dimension (table below). limit from 1 to 100, default 10. |
| GET /sites/{site_id}/realtime | visitors_last_5_minutes. |
| GET /sites/{site_id}/goals | Each goal with conversions, unique_conversions and conversion_rate (% of visitors). |
| GET /sites/{site_id}/goals/{goal_id}/properties | A custom-event goal broken down by its properties: each key with its top values (limit, default 10 per key), events and visitors. |
| GET /sites/{site_id}/ecommerce | revenue, orders, average_order_value, conversion_rate, add_to_cart, cart_conversion_rate, currency, top_products (limit, default 10) and revenue_by_channel. |
| GET /sites/{site_id}/payments | Orders, newest first, with revenue, currency and the source, medium, channel and landing page of the buyer's first visit. total, limit (default 25, up to 100) and offset to page. |
| GET /sites/{site_id}/funnels | The funnels defined on the site, with their steps. |
| GET /sites/{site_id}/funnels/{funnel_id} | Visitors per step, conversion from the first step and from the previous one, completed and overall conversion. |
| GET /sites/{site_id}/paths | The most frequent sequences of the first three pages of a session: pages, sessions, share (%). limit up to 50. |
| GET /sites/{site_id}/ai-traffic | Visits from AI agents and crawlers: agent, purpose (user, retrieval, training or search) and visits. search is a search engine crawler, not AI traffic. |
| GET /sites/{site_id}/engagement | Scroll depth and time on page: measured (page views that sent a measurement), scroll_depth (average %), time_on_page_seconds (average), reached (measurements that saw at least 25, 50, 75 and 100 %) and pages, the top pages by measurements (limit up to 100). Each measurement is matched to a page view by visitor, page and UTC day, at most one per page view; filters apply through that match. |
Parameters
| period | today, yesterday, 7d (default), 30d, month (this calendar month), last_month, 6m, 12m or all. Calendar periods (today, yesterday, month, last_month) begin at midnight in the site's time zone (UTC unless changed in the site settings). |
| start, end | A custom range instead of period, in ISO 8601: start=2026-10-01&end=2026-11-01. end is excluded. |
| filter | key:value, repeatable, all must match: filter=channel:Organic Search&filter=country:ES. Keys: pathname, referrer, channel, country, device, browser, os, utm_source, utm_medium, utm_campaign, utm_term, utm_content. key:value is exact; !key:value is "is not", ~key:value "contains" and !~key:value "does not contain" (contains ignores case), comparing the value the dashboard shows (Direct, None, Unknown for missing ones): filter=~pathname:/blog&filter=!country:ES. prop.<name>:value, with the same prefixes, keeps the sessions in which a custom event had that property. Up to 20 filters. channel is where the visit came from: every page of a visit (one visitor on one UTC day) keeps the channel of its first page. Summary, timeseries, breakdown, funnel results, paths and engagement accept filters; the MCP tools take the same text in filter, a list. With any filter, days imported from another tool are left out (summary says so in imported). |
Breakdown dimensions
| pages, referrers, channels, countries, devices, browsers, operating_systems | count is pageviews. |
| utm_sources, utm_mediums, utm_campaigns, utm_terms, utm_contents | count is pageviews. Visits without the tag show as Direct (source) or None. |
| entry_pages, exit_pages | count is entries or exits. Top 10 only. |
| outbound_links, downloads | count is clicks or downloads; value is the URL. |
| not_found | count is occurrences of 404 pages; value is the path. |
| Days imported from another tool | Without filters they are in pages, entry and exit pages, referrers, channels, UTM tags, countries, devices, browsers and systems. Each imported day keeps its 100 biggest values per dimension and adds the rest up in a row whose value is "(other)" (see /docs/import). |
Responses
- JSON with snake_case keys. Each response repeats site_id, the range used (ISO 8601, UTC, with the site's timezone) and the filters applied.
- Percentages go from 0 to 100. Money is in the site's currency.
- A visitor is the weekly anonymous identifier (no cookies); a session is one visitor on one UTC day. There is nothing about individual people.
- Fields may be added; existing ones are not renamed or removed within /v1.
Errors
An error answers with its HTTP status and a body like this:
{ "error": { "code": "site_not_found", "message": "No site with this id for this key. …" } }| 400 invalid_request | A parameter is wrong. The message says what to send. |
| 401 invalid_api_key | No key, a key that does not exist, or one that was revoked. |
| 403 pro_required | The account is on Free. The key works again after upgrading. |
| 404 site_not_found, funnel_not_found, not_found | Not in this account, outside the key's site, or no such endpoint. |
| 429 rate_limited | More than 60 requests in a minute with one key. Retry-After says how many seconds to wait; RateLimit-Remaining and RateLimit-Reset come with every response. |
| 500 internal_error | Something failed on our side. Try again. |
Related
- Connect an AI assistant (MCP): the same data from Claude, Cursor or VS Code
- Dashboard metrics: what each figure means