Reports API guide
Read a saved report from your own software—without giving that software access to your other reports.
Optional, for developers. You do not need an API key to view, save, schedule or download a report in Hitsteps. This API returns JSON to your server-side integration.
English reference · Updated September 12, 2026
1. Save your report and create a key
- Open your website in Hitsteps, go to Reports, and save the report you want to read. Test its metrics, filters and date range in the dashboard first.
- Open that saved report and expand Reports API.
- Choose an expiry and click Create API key. The dashboard offers 1, 7, 30 or 90 days.
- Copy the key now and store it in your server's secret manager. It is shown only once; it cannot be recovered later.
The key identifies one saved report and its revision. It is not your website tracking/API code and cannot be used as a general Hitsteps account key. There is no separate report ID to send with a request.
If the Reports API section is unavailable, check your account's feature access or contact support. Creating a key does not unlock unavailable report types, data or account features.
2. Make a request
POST https://www.hitsteps.com/api/reports-v2.php
Authorization: Bearer YOUR_REPORT_API_KEY
Content-Type: application/json
Send exactly two JSON fields. Both must be integer Unix timestamps in seconds—not milliseconds, quoted strings or calendar-date strings.
| Field | Meaning |
|---|---|
from | Start of the first account-local day, included. |
until | Start of the day after the last included day, excluded. Must be greater than from. |
No sid, report_id, timezone, metrics, filters, SQL or definition JSON may be added. Change the saved report in the dashboard instead, then create a replacement key.
cURL example
This example requests September 6, 2026 for an account at UTC+02:00. Replace the timestamps with complete days within your own retained history. Configure HITSTEPS_REPORT_API_KEY securely in your server environment before running it.
printf 'Authorization: Bearer %s\n' "$HITSTEPS_REPORT_API_KEY" |
curl --silent --show-error --include \
'https://www.hitsteps.com/api/reports-v2.php' \
-H @- \
-H 'Content-Type: application/json' \
--data '{"from":1788645600,"until":1788732000}'
Run with shell tracing disabled. The shell's built-in printf passes the header through standard input, keeping the key out of cURL's process arguments. --include shows the HTTP status and headers, including Retry-After when rate-limited. An HTTP error can still contain a JSON body: always check the status, not just whether cURL connected.
3. Use the account calendar
Report days use your Hitsteps account's fixed UTC offset, not your browser or server timezone. For UTC+02:00, local midnight is 22:00 UTC on the preceding date. The server resolves the account offset; you cannot override it in the API request.
- Use complete local days. Do not include the current unfinished day or send arbitrary hourly boundaries.
- The default maximum is 31 days per period. Account retention, source readiness and query-cost limits may require a shorter range.
- Recent completed days can still be processing. Being yesterday does not guarantee that every data source is ready.
- Comparison behavior comes from the saved report: none, previous period or previous year. Both periods need available retained data. Custom comparison dates are not supported by this date-only API.
- A fixed offset does not automatically follow daylight-saving changes. If your account offset changes, recalculate request boundaries.
The request example inside your saved report's API section uses its selected timestamps and is a useful starting point. The API does not return expired history or bypass your report's normal data checks.
PHP: request the previous complete day
This server-side example requires PHP with cURL. It does not print the key. Set the offset to your account's current fixed offset; the example uses UTC+02:00. Handle data still processing as described below.
<?php
$key = getenv('HITSTEPS_REPORT_API_KEY');
if (!is_string($key) || $key === '') {
throw new RuntimeException('Configure the report API key securely.');
}
$offset = 2 * 3600; // Change to your Hitsteps account offset in seconds.
$until = (int) (floor((time() + $offset) / 86400) * 86400 - $offset);
$from = $until - 86400;
$request = curl_init('https://www.hitsteps.com/api/reports-v2.php');
curl_setopt_array($request, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 20,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $key,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['from' => $from, 'until' => $until]),
]);
$body = curl_exec($request);
$status = (int) curl_getinfo($request, CURLINFO_HTTP_CODE);
curl_close($request);
if ($body === false) {
throw new RuntimeException('Could not reach the Reports API.');
}
$response = json_decode($body, true);
if ($status !== 200 || !is_array($response) || empty($response['ok'])) {
// Consult the error table; do not loop or retry indefinitely.
throw new RuntimeException('Reports API returned HTTP ' . $status);
}
$report = $response['result'];
$rows = $report['rows'];
// Process rows privately. Preserve exact decimal strings and null values.
4. Read the JSON response
A successful response has HTTP 200, outer ok: true, version: 2, report_id, report_revision, and a result object. This illustrative Actions report groups by Action key. Additional report metadata is omitted; the values are examples, not customer data:
{
"ok": true,
"version": 2,
"report_id": 42,
"report_revision": 1,
"result": {
"ok": true,
"version": 2,
"dataset": "actions",
"timezone_offset": 7200,
"metrics": ["action_events", "unique_visitors"],
"rows": [{
"dimension_1": {"id": "action.key", "value": "cart_category", "state": "set"},
"action_events": "10",
"unique_visitors": "4"
}],
"totals": {"action_events": "10", "unique_visitors": "4"},
"comparison": null,
"comparison_rows": [],
"warnings": [],
"has_more": false
}
}
Inside result | How to use it |
|---|---|
definition, dataset, metrics | The saved question and machine-readable metric IDs. Row shape depends on its dataset and breakdowns; do not assume all reports have the same columns. |
rows, totals | Current-period breakdown rows and totals. Empty rows mean a successful query found no matching data, not that the API failed. |
current, comparison, timezone_offset | The effective periods and fixed offset used. Comparison is null when disabled. |
comparison_rows | Rows for the saved comparison, when configured. Match categories rather than assuming row positions are identical. |
time_series_rows | Daily chart data when the saved visualization is a time series; it is not present for every report. |
warnings, has_more, row_cap | Read semantic warnings and truncation metadata. Results follow the saved sort/row limit; this endpoint has no pagination parameters. Do not present a limited table as an exhaustive export. |
Breakdown cells such as dimension_1 carry their field ID, value and state. Keep state: "missing" distinct from state: "empty"; an absent value is not the same as an explicitly empty one.
Preserve numeric precision. Metrics may be exact integer/decimal strings or null. Do not blindly convert all values to JavaScript Number or treat unavailable values as zero. Unique counts and ratios generally cannot be added across date chunks to obtain an exact longer-period total.
5. Errors, limits and retries
Errors use {"ok":false,"error":"code"}. The default rate limit is 20 requests per 60-second window per key. Reuse results where appropriate; do not create additional keys to bypass limits.
| HTTP / error | What to do |
|---|---|
400 invalid_request | Send only integer from and until, each between 1 and 2147483647, with until > from. Check for milliseconds, extra fields or invalid JSON. |
401 unauthorized | Check the Bearer header, expiry and revocation. Saving changes to a report invalidates its keys. Create a replacement key in that report; a website tracking code will not work. |
403 not_available | Open the report in Hitsteps and check access, feature availability, supported settings and report limits. Reduce the range if the report is too expensive. Do not retry repeatedly without a change. |
404 not_found | The Reports API may be disabled on this deployment. Confirm feature availability with support. |
405 method_not_allowed | Use POST, not GET. Opening the endpoint in a browser is not a report request. |
413 request_too_large | The request body must not exceed 4096 bytes. |
415 unsupported_media_type | Set Content-Type: application/json. |
422 report_unavailable | Check complete account-local boundaries, the 31-day default limit, retained history and source readiness. Shorten the range or wait if recent data is processing; unavailable data is not an empty report. |
422 custom_comparison_not_supported | Save the report with no comparison, previous period or previous year, then create a replacement key. |
422 report_calendar_changed | The account offset changed during execution. Recalculate boundaries using its current offset and retry. |
429 rate_limited | Wait at least the Retry-After response-header value in seconds. Spread requests out. |
503 temporarily_unavailable | Retry a few times with increasing delays and jitter, then stop and surface an error. Never treat an unavailable report as zero. |
Requests are bounded by the same report access, retention, privacy, source-coverage and cost controls as the dashboard. An API key does not grant unlimited queries or raw database access.
Keep access safe
- Use HTTPS and send the key only in the Authorization header. Never place it in URLs, public JavaScript, source control, screenshots or support messages.
- Run integrations on your server. This documentation page does not accept keys or make authenticated API requests.
- Revoke a lost or exposed key in the saved report's API section and create a replacement. Choose a short expiry and arrange rotation before it expires.
- Saving changes to the report invalidates existing keys. Report deletion or loss of the owner's access also stops authorized reads.
- Treat downloaded results as private analytics data, including any visitor-supplied custom values. Protect storage and follow your deletion/retention obligations.
The API does not edit reports, create schedules or email results. Dashboard schedules currently prepare CSV downloads separately; they do not require an API key.
Using the older website-level Analytics API? Its credentials and request format are different. See the Analytics API reference or the Grafana integration guide.