Analytics
When they come
Every hour of every weekday, as a grid.
curl --request GET \
--url 'https://truestat.io/api/v1/sites/{siteId}/heatmap' \
--header 'Authorization: Bearer <token>'{
"data": [
{
"day": 3,
"hour": 14,
"views": 144,
"visitors": 45
}
],
"meta": {
"range": {
"to": "2026-09-16T00:00:00.000Z",
"tz": "Europe/Istanbul",
"from": "2026-09-09T00:00:00.000Z",
"clamped": false
},
"site_id": "6fed8980-a119-4a9c-a4d4-53e448ef9f9e"
}
}Your traffic folded onto a single week: 7 days × 24 hours.
This answers "when", which a line chart cannot
On a thirty-day line every day collapses to one point, so the difference between Tuesday mornings and Friday evenings is invisible. This sums the same traffic into weekday-and-hour cells, which is where that pattern shows up.
The rows are not a time series. day is a weekday, not a date, and one
cell holds every Tuesday 09:00 in the window added together. Asking for a
longer window makes each cell larger, not the grid wider.
day is 1 = Monday
Not 0 = Sunday, which several date libraries use.
The numbering is published exactly as the query produces it rather than shifted to match any particular library. A silent renumbering is what puts Sunday's traffic on Monday's row in somebody's chart, and it is invisible once it has happened.
day | |
| 1 | Monday |
| 2 | Tuesday |
| 3 | Wednesday |
| 4 | Thursday |
| 5 | Friday |
| 6 | Saturday |
| 7 | Sunday |
hour is in your timezone
Unlike the other bucketed endpoints, this one takes tz into the query
itself. It has to: "when do people visit" asked in UTC about European readers
answers a question nobody has.
Pass tz and the grid is the reader's own clock:
GET /sites/{siteId}/heatmap?period=last30d&tz=Europe/IstanbulVerified: a burst of 432 views sitting at hour 10 in UTC appears at hour 13
with tz=Europe/Istanbul. The parameter moves the grid, it does not just
label it.
The grid is sparse
An hour with no traffic has no row. At most 168 cells come back, usually far fewer.
Fill the missing ones with zero when you draw it. An absent cell is an empty hour, not a gap in the data — and a chart that skips them silently compresses the quiet parts of the week.
Its own channel filter
channel narrows the grid to one source, separate from the shared filter_*
set so the grid can refresh on its own while the rest of a dashboard stays
put:
GET /sites/{siteId}/heatmap?period=last30d&channel=Organic%20SearchUseful for the question the grid is best at: whether your paid traffic arrives at a different time of day than your organic traffic.
Authorizations
AuthorizationstringheaderrequiredAn API key from Settings → API keys. Scopes available: analytics:read, sites:read, sites:write, settings:read, settings:write, goals:read, goals:write, funnels:read, funnels:write, notes:read, notes:write, alerts:read, alerts:write, team:read, team:write, revenue:read, revenue:write.
Bearer authentication header of the form Bearer <token>, where <token> is your auth token.
Path parameters
siteIdstring (uuid)pathrequiredQuery parameters
channelstringqueryperiodenum<string>querytodayyesterdaylast24hlast7dlast30dlast12mmonthyearallfromstring (date-time)querytostring (date-time)querytzstringquerydefault: UTCbotsenum<string>querydefault: excludedexcludedincludedResponse
The answer.
dataunknownmetaobjecterrorobjecterrorobjecterrorobjecterrorobject