Analytics Interpretation
The xQR analytics API gives you rich data about how your links and QR codes are performing. This guide helps you interpret that data and act on it.
Analytics Overview
xQR collects analytics every time a short link or QR code is scanned. Data includes:
- Timestamp — When the scan happened
- Country — Derived from the visitor’s IP via GeoIP
- Device type — Mobile, tablet, or desktop
- Browser — Chrome, Safari, Firefox, etc.
- OS — iOS, Android, Windows, macOS, etc.
- Referrer — Where the visitor came from (if available)
Key Metrics
Summary Endpoint
GET /v1/analytics/summary?period=30d
Returns workspace-wide KPIs:
{ "data": { "total_scans": 15420, "unique_visitors": 12893, "scans_change_pct": 12.5, "top_countries": [ { "country": "US", "country_code": "US", "scans": 5200 }, { "country": "Germany", "country_code": "DE", "scans": 2100 } ], "top_devices": [ { "device": "mobile", "scans": 9800, "percentage": 63.5 }, { "device": "desktop", "scans": 4800, "percentage": 31.1 } ] }}Key insights:
scans_change_pct— Percentage change vs the previous period. Positive = growthunique_visitorsvstotal_scans— The ratio tells you about repeat scanning behavior. A high ratio of total to unique suggests engaged users scanning multiple timestop_countries— Identify your strongest markets for geo-targeted campaigns
Timeseries Endpoint
GET /v1/analytics/timeseries?period=30d&granularity=day
Returns scan counts over time:
{ "data": [ { "timestamp": "2026-03-01T00:00:00Z", "scans": 489 }, { "timestamp": "2026-03-02T00:00:00Z", "scans": 512 }, { "timestamp": "2026-03-03T00:00:00Z", "scans": 623 } ]}How to read this:
- Look for trends — Is traffic growing, flat, or declining?
- Identify spikes — Correlate with marketing campaigns or social media mentions
- Spot patterns — Day-of-week patterns reveal when your audience is most active
- Use hourly granularity for recent data to find peak hours
Geography Endpoint
GET /v1/analytics/geography?period=30d
{ "data": [ { "country": "United States", "country_code": "US", "scans": 5200, "percentage": 33.7 }, { "country": "Germany", "country_code": "DE", "scans": 2100, "percentage": 13.6 }, { "country": "United Kingdom", "country_code": "GB", "scans": 1800, "percentage": 11.7 } ]}How to act on this:
- Set up geo-routing — If significant traffic comes from specific countries, create routing rules to redirect to localized pages
- Time your campaigns — Publish during peak hours for your top countries
- Identify new markets — Unexpected traffic from a country might indicate an opportunity
Devices Endpoint
GET /v1/analytics/devices?period=30d
{ "data": { "devices": [ { "device": "mobile", "scans": 9800, "percentage": 63.5 }, { "device": "desktop", "scans": 4800, "percentage": 31.1 }, { "device": "tablet", "scans": 820, "percentage": 5.3 } ], "browsers": [ { "browser": "Chrome", "scans": 7200 }, { "browser": "Safari", "scans": 5100 } ], "os": [ { "os": "iOS", "scans": 6200 }, { "os": "Android", "scans": 4800 } ] }}How to act on this:
- Mobile-first — If most scans are mobile (common for QR codes), ensure your destination pages are mobile-optimized
- Device routing — Use smart routing to send mobile users to app download pages
- Browser testing — Prioritize testing on your most popular browsers
Comparing Link Performance
Top Links
GET /v1/analytics/top-links?period=30d&limit=10
Use this to identify your highest and lowest performing links.
What to look for:
- High scan count + low engagement — Destination page may not be relevant
- Low scan count on promoted links — QR code may be poorly placed or too small to scan
- Sudden drops — Check if the destination URL is still working
Per-Link Stats
GET /v1/analytics/links/{link_id}?period=30d
Deep-dive into a specific link’s performance:
{ "data": { "link_id": "d47f2e1a-...", "short_code": "summer24", "total_scans": 2341, "unique_visitors": 1987, "timeseries": [ ... ], "geography": [ ... ], "devices": { ... } }}Common Analysis Patterns
Campaign Comparison
Compare two campaigns by pulling their link stats:
const campaigns = ["campaign-a-link-id", "campaign-b-link-id"];
const results = await Promise.all( campaigns.map(async (id) => { const res = await fetch( `https://xqr.co/api/v1/analytics/links/${id}?period=30d`, { headers: { Authorization: `Bearer ${apiKey}` } } ); return res.json(); }));
results.forEach(({ data }) => { console.log(`${data.short_code}: ${data.total_scans} scans`);});import httpx
campaigns = ["campaign-a-link-id", "campaign-b-link-id"]
for link_id in campaigns: res = httpx.get( f"https://xqr.co/api/v1/analytics/links/{link_id}", params={"period": "30d"}, headers={"Authorization": f"Bearer {api_key}"}, ) data = res.json()["data"] print(f"{data['short_code']}: {data['total_scans']} scans")Monitoring with Webhooks
Set up a link.threshold webhook to get notified when links hit scan milestones:
{ "url": "https://your-app.com/webhooks/xqr", "events": ["link.threshold", "link.scanned"]}This is useful for:
- Celebrating milestones — Notify your team when a campaign hits 1,000 scans
- Alerting on anomalies — Detect unusual traffic spikes that could indicate abuse
Export to BI Tools
Pull timeseries data and export to your BI stack:
# Export 90 days of daily scan data as JSONcurl "https://xqr.co/api/v1/analytics/timeseries?period=90d&granularity=day" \ -H "Authorization: Bearer $XQR_API_KEY" \ | jq '.data' > scans_90d.jsonBest Practices
- Check analytics regularly — Set up a weekly review cadence
- Use labels — Label your links by campaign, channel, or team for easier filtering
- Compare periods — Use
7dvs30dto spot trends - Act on geography — Set up routing rules for your top traffic sources
- Monitor outliers — Both unusually high and low scan counts deserve investigation
- Combine with UTM — Add UTM parameters to destination URLs for end-to-end tracking in Google Analytics
Was this page helpful?
Thanks for your feedback!