API d'agrégation
GET /v1/codes/:id/scans — Séries temporelles + Top pays/appareils/OS pour un seul objet de code.
Chaque scan d’un code QR dynamique génère un enregistrement de scan (Scan-Record) dans votre espace de travail. La collecte s’effectue entièrement à l’Edge Cloudflare — sans services de suivi externes, sans cookies, et sans que l’adresse IP d’origine n’atteigne jamais votre base de données.
Trois façons de consommer les données de scan :
API d'agrégation
GET /v1/codes/:id/scans — Séries temporelles + Top pays/appareils/OS pour un seul objet de code.
Webhooks (temps réel)
Événement qr.scanned par scan, signé avec HMAC-SHA256. Voir Webhooks.
Tableau de bord
Visualisation dans le tableau de bord qr3.app — aucun appel API requis.
Pour chaque scan, un enregistrement est créé dans la table scans :
| Champ | Source | Exemple |
|---|---|---|
id | UUID généré par le serveur | scn_d1f8… |
code_id | ID du code QR scanné | qr_a1b2c3d4 |
workspace_id | Espace de travail du code | ws_xxx |
country | Cloudflare cf.country | AT |
region | Cloudflare cf.region | Vienna |
city | Cloudflare cf.city | Wien |
device_type | Analyse du User-Agent | mobile, tablet, desktop |
os | Analyse du User-Agent | iOS, Android, macOS, Windows |
browser | Analyse du User-Agent | Safari, Chrome, Firefox |
referer | En-tête HTTP Referer | https://example.com/landing |
language | En-tête Accept-Language (première langue) | de |
redirected_to | Destination réelle de la redirection | https://example.com |
ip_hash | SHA-256(IP + sel quotidien) — non réversible | 8f3a… |
scanned_at | Horodatage ISO-8601 | 2026-05-12T14:32:11.000Z |
Toutes les données géographiques (country, region, city) proviennent de l’objet cf de Cloudflare et sont basées sur la base de données Geo-IP de Cloudflare. Aucun service Geo-IP externe n’est interrogé — la résolution s’effectue dans le même Worker qui exécute la redirection.
Cela a trois conséquences :
city) n’est pas toujours disponible (par exemple avec les VPN, les opérateurs mobiles ou les petites régions). Attendez-vous à des valeurs null et prévoyez-les dans vos tableaux de bord.GET /v1/codes/:id/scansFournit des analyses agrégées pour un seul code QR sur une période sélectionnable.
curl "https://qr3.app/v1/codes/qr_a1b2c3d4/scans?days=30" \ -H "Authorization: Bearer qr3_sk_..."const analytics = await qr3.scans.get('qr_a1b2c3d4', { days: 30 });console.log(analytics.period_scans, analytics.top_countries);qr3 scans qr_a1b2c3d4 --days 30Paramètres de requête :
| Paramètre | Type | Par défaut | Description |
|---|---|---|---|
days | integer | 30 | Période en jours (1–365) |
Réponse (HTTP 200) :
{ "data": { "code_id": "qr_a1b2c3d4", "short_code": "r7f3Kx", "total_scans": 1842, "period_days": 30, "period_scans": 367, "scans_by_day": [ { "date": "2026-04-13", "count": 12 }, { "date": "2026-04-14", "count": 18 } ], "top_countries": [ { "value": "AT", "count": 142 }, { "value": "DE", "count": 98 }, { "value": "CH", "count": 41 } ], "top_devices": [ { "value": "mobile", "count": 281 }, { "value": "desktop", "count": 72 }, { "value": "tablet", "count": 14 } ], "top_os": [ { "value": "iOS", "count": 158 }, { "value": "Android", "count": 123 }, { "value": "macOS", "count": 48 } ] }, "meta": { "request_id": "req_xyz123" }}Champs de réponse :
| Champ | Description |
|---|---|
total_scans | Nombre total de scans cumulés du code (indépendamment de la période days) |
period_scans | Somme des scans dans la période sélectionnée |
scans_by_day | Groupements par jour, par ordre chronologique, les jours vides sont omis |
top_countries | Top 8 des pays sur la période, par ordre décroissant |
top_devices | Top 5 des types d’appareils (mobile, tablet, desktop) |
top_os | Top 5 des systèmes d’exploitation |
Les totaux à l’échelle de l’espace de travail (par exemple, les scans par mois sur tous les codes) sont disponibles via GET /v1/workspaces/:id dans le champ scans_this_month. Pour les analyses multi-codes, nous recommandons le tableau de bord ou un export périodique.
Si vous souhaitez traiter les événements de scan dès qu’ils se produisent — par exemple pour des tableaux de bord en direct, le suivi des leads ou des intégrations CRM — abonnez-vous à l’événement qr.scanned :
curl -X POST https://qr3.app/v1/webhooks \ -H "Authorization: Bearer qr3_sk_..." \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/webhooks/qr3", "events": ["qr.scanned"], "secret": "my-secret-key-min-16-chars" }'Payload :
{ "id": "evt_abc123xyz", "type": "qr.scanned", "created": "2026-05-12T14:32:11.000Z", "data": { "code_id": "qr_a1b2c3d4", "short_code": "r7f3Kx", "scan_id": "scn_d1f8...", "country": "AT", "device_type": "mobile", "os": "iOS" }}Documentation complète sur la vérification des signatures, la logique de tentative et les journaux de livraison dans API → Webhooks.
Les enregistrements de scan ne contiennent aucune adresse IP personnelle. L’IP d’origine est remplacée dans le Worker Edge par une fonction de hachage cryptographique (SHA-256) avec un sel rotatif quotidien (salt). Cela signifie :
La rétention dépend de l’abonnement :
| Abonnement | Rétention |
|---|---|
| Free | 7 jours |
| Pro | 90 jours |
| Business / Agency | 1 an |
| Enterprise | Personnalisé (SLA) |
Une tâche cron quotidienne (purgeOldScans) supprime automatiquement les enregistrements plus anciens — le champ total_scans de l’objet de code n’est pas affecté.
days=365), la charge utile (payload) peut atteindre plusieurs Ko. Mettez en cache côté client avec une courte durée de vie (TTL, par exemple 60 s).top_countries, top_devices et top_os sont limités côté serveur à respectivement 8 et 5 entrées. Pour des analyses plus approfondies, exportez les données brutes.null : country, region, city, referer et language peuvent être null. Traisez cela comme « Inconnu » dans vos analyses.