DocsDevelopers
Read API
Read stats, goals, attribution, ad spend and short links with a read key. JSON over HTTPS, OpenAPI 3.1, CSV feeds and copyable curl examples.
On this page
The read API gives your own code, a client's dashboard or a spreadsheet the same numbers you see in Datacenter. It only reads. Nothing it does can change a site, a goal or a link.
The read API is on paid plans: Solo, Studio, Agency and Custom. It also powers the MCP server and Google Sheets feeds.
Make a key
- Open Settings, then API.
- Name the key, pick what it reads (All sites, or one site) and press Make a key.
- Copy the key. It is shown once. Datacenter keeps only a hash of it.
A key starts with st_rk_. All sites covers sites you add later too. For a client, a script that runs somewhere else, or a shared sheet, make a key for one site. Revoke stops a key at once.
Read keys are not Events API keys. Events API keys (st_sk_) send events and cannot read. Read keys (st_rk_) read and cannot send.
First request
export DATACENTER_KEY="st_rk_..."
curl https://datacenter.so/api/v1/sites \
-H "Authorization: Bearer $DATACENTER_KEY"{
"data": [
{
"id": "3f9a1c2e7b4d8a06",
"name": "Demo shop",
"domain": "shop.example.com",
"client_label": null,
"timezone": "America/New_York",
"tracking_mode": "attribution",
"created_at": "2026-09-14T16:02:11.000Z"
}
],
"pagination": { "page": 1, "limit": 100, "has_more": false, "next_page": null }
}Every other call takes a site id from this list.
Endpoints
All are GET under https://datacenter.so. The full schema is the OpenAPI 3.1 document.
| Endpoint | What it returns |
|---|---|
/api/v1/sites | Sites the key reads |
/api/v1/sites/{site_id} | One site |
/api/v1/sites/{site_id}/stats | Totals for a range, the span before it, and the change |
/api/v1/sites/{site_id}/timeseries | The same totals per hour, day or month |
/api/v1/sites/{site_id}/breakdown/{dimension} | Top sources, pages, campaigns, countries, devices or goals |
/api/v1/sites/{site_id}/goals | Goals with conversions |
/api/v1/sites/{site_id}/attribution | Conversions and revenue credited by source, campaign, ad, platform or short link |
/api/v1/sites/{site_id}/ads | Ad spend, credited revenue and ROAS |
/api/v1/links | Short links with clicks |
/api/v1/links/{link_id} | One link: clicks per day and where they came from |
Totals
curl "https://datacenter.so/api/v1/sites/3f9a1c2e7b4d8a06/stats?period=7d" \
-H "Authorization: Bearer $DATACENTER_KEY"{
"data": {
"site_id": "3f9a1c2e7b4d8a06",
"range": { "period": "7d", "date_from": "2026-10-17", "date_to": "2026-10-23", "timezone": "America/New_York", "from": "2026-10-17T00:00:00-04:00", "to": "2026-10-23T11:00:00-04:00" },
"previous_range": { "compare": "previous", "match_weekdays": false, "from": "2026-10-10T00:00:00-04:00", "to": "2026-10-16T11:00:00-04:00" },
"filters": {},
"metrics": { "visitors": 1840, "visits": 2210, "pageviews": 5120, "views_per_visit": 2.32, "bounce_rate": 41, "visit_duration": 96, "conversions": 63, "conversion_rate": 2.9 },
"previous": { "visitors": 1702, "visits": 2050, "pageviews": 4790, "views_per_visit": 2.34, "bounce_rate": 43, "visit_duration": 91, "conversions": 55, "conversion_rate": 2.7 },
"change_pct": { "visitors": 8.1, "visits": 7.8, "pageviews": 6.9, "views_per_visit": -0.9, "bounce_rate": -4.7, "visit_duration": 5.5, "conversions": 14.5, "conversion_rate": 7.4 }
}
}The numbers above are an example. bounce_rate and conversion_rate are percents. visit_duration is seconds per visit. conversions counts visits that reached any goal. previous_range is the span the change is measured against. By default it is the same number of days just before, ending at the same time of day, so a part day is compared with a part day. This month, quarter or year compares with the same days of the one before: October 1 to 23 against September 1 to 23. Add compare=year for the same dates a year back, and match_weekdays=true with it to go back 364 days so a Monday lines up with a Monday. Add compare=false to skip it.
Per day
curl "https://datacenter.so/api/v1/sites/3f9a1c2e7b4d8a06/timeseries?period=30d&interval=day" \
-H "Authorization: Bearer $DATACENTER_KEY"interval is day (the default), hour (up to 7 days) or month. Every bucket is in the list, empty ones too. date is the local day (2026-10-01), hour (2026-10-01 14:00) or month (2026-10).
Breakdowns
curl "https://datacenter.so/api/v1/sites/3f9a1c2e7b4d8a06/breakdown/source?period=30d&limit=10" \
-H "Authorization: Bearer $DATACENTER_KEY"Dimensions: source, referrer, utm_source, utm_medium, utm_campaign (or campaign), utm_content, utm_term, page, entry_page, country, device, browser, os and goal. Rows come most visitors first, each with visitors, visits, pageviews, bounce rate, visit duration, conversions and conversion rate. A blank source is Direct / None.
Goals
curl "https://datacenter.so/api/v1/sites/3f9a1c2e7b4d8a06/goals?period=mtd" \
-H "Authorization: Bearer $DATACENTER_KEY"Each goal has conversions (visits that reached it), events (every match) and conversion_rate (percent of visits).
Attribution
curl "https://datacenter.so/api/v1/sites/3f9a1c2e7b4d8a06/attribution?period=30d&by=campaign&model=last" \
-H "Authorization: Bearer $DATACENTER_KEY"This is the attribution report. by is source, campaign, content (the ad), term (the ad set or keyword), platform or link. model is last, first, linear, position, decay or any. Without it, the site's own model from its Attribution tab applies, and the answer's model says which. goal_id counts one goal. Revenue is per currency, such as {"USD": 1240.5}, and never converted.
Ad spend and ROAS
curl "https://datacenter.so/api/v1/sites/3f9a1c2e7b4d8a06/ads?period=30d&level=campaign" \
-H "Authorization: Bearer $DATACENTER_KEY"From connected ad platforms and spend files. level is platform, campaign, adset or ad. revenue is what Datacenter credits to the ads, last touch. platform_reported is what the platform itself counts. roas is null when there is no spend or when spend and revenue are in different currencies. connected is false when the site has no ad spend.
Short links
curl "https://datacenter.so/api/v1/links?period=30d" \
-H "Authorization: Bearer $DATACENTER_KEY"
curl "https://datacenter.so/api/v1/links/LINK_ID?period=30d" \
-H "Authorization: Bearer $DATACENTER_KEY"A key for one site lists the links tied to that site. Add site_id to an all-sites key to do the same. status is active (the default), archived or all.
Dates and time zones
Send a period, or date_from and date_to as YYYY-MM-DD. Both days are included.
period | Covers |
|---|---|
today | Today, up to now |
yesterday | Yesterday |
7d, 30d, 90d | The last 7, 30 or 90 days, today included (default 30d) |
12m | The last 365 days |
mtd | This month, up to now |
lastmonth | Last calendar month |
quarter | This quarter, up to now |
lastquarter | Last calendar quarter |
year | This year, up to now |
Days start at local midnight in one time zone: the timezone you send (an IANA name such as Europe/Berlin), else the site's, else UTC. Every answer repeats the range with ISO 8601 times and their offset, so you never have to guess. Days and months in a time series use the zone's offset at the start of the range.
Short link clicks are kept in whole UTC days, so link calls are always UTC.
A range can be up to 731 days, and only as far back as your plan keeps stats.
Filters
Add filter=dimension:value. Repeat it to combine, up to 8.
curl -G "https://datacenter.so/api/v1/sites/3f9a1c2e7b4d8a06/stats" \
--data-urlencode "period=30d" \
--data-urlencode "filter=source:Instagram" \
--data-urlencode "filter=device:Mobile" \
-H "Authorization: Bearer $DATACENTER_KEY"Filters work on stats, time series, breakdowns and goals. A goal filter takes a goal id from the goals call.
Pages of results
Lists take limit (1 to 1,000, default 100) and page (from 1). The answer says if there is more:
"pagination": { "page": 1, "limit": 100, "has_more": true, "next_page": 2 }Lists go 1,000 rows deep.
CSV feeds
Add format=csv to any list for a CSV file with every row. For tools that cannot send headers, such as Google Sheets, the key can ride in the address, for CSV feeds only:
https://datacenter.so/api/v1/sites/3f9a1c2e7b4d8a06/timeseries?period=90d&format=csv&key=st_rk_...Anyone with that address can read what the key reads. Use a key for one site, and revoke it if the address leaks. See Google Sheets and Looker Studio.
Errors
Errors have a status code and the same shape:
{ "error": { "code": "invalid_parameter", "message": "date_from must be a date as YYYY-MM-DD.", "field": "date_from" } }| Status | Code | Means |
|---|---|---|
| 400 | invalid_parameter, range_too_long, key_in_url | A parameter is wrong. field names it |
| 401 | unauthorized | No key, or the key was revoked |
| 403 | plan | The account's plan does not include the read API |
| 403 | history_limit | The range starts before the plan keeps stats |
| 404 | not_found | No such site, link or endpoint for this key |
| 429 | rate_limited | Over the rate limit. Wait Retry-After seconds |
| 502, 503 | upstream_error, stats_unavailable | Stats could not be read. Try again in a minute |
A site or link another key or account owns reads as not_found, never as forbidden.
Rate limit
Each key can make 120 requests a minute, across the read API, CSV feeds and the MCP server. Every answer has X-RateLimit-Limit and X-RateLimit-Remaining.
Safety
- Keep keys on a server or in a password manager. The API sends no CORS headers, so a web page cannot call it.
- Each key reads only its sites. Revoke it in Settings, API and it stops at once.
- Answers never include visitor emails, names or phone numbers.
Something missing or wrong? Email hello@datacenter.so.