Guide
Scan analytics
Read how often a card or a QR code was scanned. The API returns totals and breakdowns for a period; it does not return individual scans.
What data is available
- What is it?
- Scan statistics for one card, one QR code, or the whole account, over a chosen period.
- Why would I use it?
- To show scan counts in your own product, or to report on a print campaign.
- What do I need first?
- A key with
analytics:read. - Which endpoint do I use?
GET /analytics/:idfor one item,GET /analytics/overviewfor the account.- What result should I expect?
- A scan_stats object: totals, a comparison with the previous period, and a day-by-day timeline.
| Field | What it is | Always present? |
|---|---|---|
totalScans | Scans in the period. | Yes |
uniqueVisitors | Distinct visitors in the period. | Yes |
previousPeriodScans | Scans in the period of the same length just before. | Yes |
trend | The change between the two, as text such as "+20%". | Yes |
timeline | One entry per day: { date, scans }. | Yes |
period | The start, end and number of days actually reported. | Yes |
clamped, historyDays | Whether the plan shortened the period you asked for. See plan limits. | Yes |
topCountries | Up to ten countries: { country, code, scans }. | Only with advanced analytics |
devices | Device types: { device, scans, percent }. | Only with advanced analytics |
topQrCodes | The five busiest items: { id, name, type, scans }. | Overview only |
Statistics for one card or code
/analytics/:id:id is a card id or a QR code id; the endpoint accepts either. This asks for the last seven days of the example card:
curl "https://api.qrbold.com/api/public/v1/analytics/cmf3k2a9x0001?range=7d" \
-H "Authorization: Bearer $QRBOLD_API_KEY"{
"object": "scan_stats",
"qrCodeId": "cmf3k2a9x0001",
"range": "7d",
"period": {
"start": "2026-10-02T00:00:00.000Z",
"end": "2026-10-09T09:40:00.000Z",
"days": 7
},
"clamped": false,
"historyDays": -1,
"totalScans": 120,
"uniqueVisitors": 80,
"previousPeriodScans": 100,
"trend": "+20%",
"timeline": [
{
"date": "2026-10-07",
"scans": 6
},
{
"date": "2026-10-08",
"scans": 9
}
],
"topCountries": [
{
"country": "United Kingdom",
"code": "GB",
"scans": 74
}
],
"devices": [
{
"device": "Mobile",
"scans": 108,
"percent": 90
}
]
}The field is called qrCodeId even when the id is a card’s. An id that does not exist in the account answers 404.
Account overview
/analytics/overviewcurl "https://api.qrbold.com/api/public/v1/analytics/overview?range=30d&timeZone=Europe%2FLondon" \
-H "Authorization: Bearer $QRBOLD_API_KEY"{
"object": "scan_stats",
"qrCodeId": null,
"range": "30d",
"period": {
"start": "2026-09-09T00:00:00.000Z",
"end": "2026-10-09T09:40:00.000Z",
"days": 30
},
"clamped": false,
"historyDays": -1,
"totalScans": 120,
"uniqueVisitors": 80,
"previousPeriodScans": 100,
"trend": "+20%",
"timeline": [
{
"date": "2026-10-07",
"scans": 6
},
{
"date": "2026-10-08",
"scans": 9
}
],
"topCountries": [
{
"country": "United Kingdom",
"code": "GB",
"scans": 74
}
],
"devices": [
{
"device": "Mobile",
"scans": 108,
"percent": 90
}
],
"topQrCodes": [
{
"id": "cmf3k2a9x0001",
"name": "Ada Lovelace",
"type": "card",
"scans": 74
},
{
"id": "cmf3k4c2e0003",
"name": "Spring menu",
"type": "url",
"scans": 46
}
]
}The same object, for every card and code in the account together. qrCodeId is null, and topQrCodes lists the five busiest items, where type tells a card from a url code.
Ranges and time zones
| Name | Type | Required | Description |
|---|---|---|---|
range | string | Optional | The period to report. One of 7d, 30d, 90d, 12m. Default: 30d. |
timeZone | string | Optional | IANA time zone the daily buckets are cut in. Default: UTC. |
- The period is one of four fixed ranges. Custom start and end dates are not supported.
timeZonedecides where one day ends and the next begins intimeline. Without it, days are cut in UTC, so a scan at 11pm in New York lands on the next day’s row. Send the IANA name of your audience’s zone, such asAmerica/New_York.- A range outside those four answers
422. A time zone name is not checked in the same way, so make sure you send a real IANA name.
Plan limits on analytics
Two things depend on the account’s plan, and the response tells you about both.
| Limit | How it shows in the response |
|---|---|
| How far back history goes | historyDays is the number of days the plan keeps, or -1 for no limit. If you ask for a longer range, you get the shorter one and clamped is true. period shows what was really covered. |
| Country and device breakdowns | topCountries and devices are absent, not empty, on a plan without advanced analytics. |
Treat a missing topCountries as “not available on this plan”, and an empty array as “no scans with a known country yet”. They are different.
Privacy and what is not returned
The analytics endpoints return aggregates only. They never return:
- IP addresses.
- Precise locations. Country is the finest level of geography.
- Individual scan records, visitor identifiers or timestamps of single scans.
- Contact details that visitors submitted on a card. Those are leads, and are not in the API.
There are also no webhooks for scans. To keep your own numbers current, call these endpoints on a schedule, for example once an hour. See webhooks.