API de agregación
GET /v1/codes/:id/scans — Series temporales + principales países/dispositivos/sistemas operativos para un único objeto de código.
Cada escaneo de un código QR dinámico genera un registro de escaneo (Scan-Record) en tu espacio de trabajo. La recopilación se realiza por completo en el Edge de Cloudflare, sin servicios de seguimiento externos, sin cookies y sin que la IP original llegue nunca a tu base de datos.
Tres formas de consumir los datos de escaneo:
API de agregación
GET /v1/codes/:id/scans — Series temporales + principales países/dispositivos/sistemas operativos para un único objeto de código.
Webhooks (tiempo real)
Evento qr.scanned por escaneo, firmado con HMAC-SHA256. Consulta Webhooks.
Panel de control
Visualización en el panel de control de qr3.app, sin necesidad de llamadas a la API.
Por cada escaneo se crea un registro en la tabla scans:
| Campo | Origen | Ejemplo |
|---|---|---|
id | UUID generado por el servidor | scn_d1f8… |
code_id | ID del código QR escaneado | qr_a1b2c3d4 |
workspace_id | Espacio de trabajo del código | ws_xxx |
country | cf.country de Cloudflare | AT |
region | cf.region de Cloudflare | Vienna |
city | cf.city de Cloudflare | Wien |
device_type | Análisis del User-Agent | mobile, tablet, desktop |
os | Análisis del User-Agent | iOS, Android, macOS, Windows |
browser | Análisis del User-Agent | Safari, Chrome, Firefox |
referer | Cabecera HTTP Referer | https://example.com/landing |
language | Cabecera Accept-Language (primer idioma) | de |
redirected_to | Destino real de la redirección | https://example.com |
ip_hash | SHA-256(IP + sal diaria) — no reversible | 8f3a… |
scanned_at | Marca de tiempo ISO-8601 | 2026-05-12T14:32:11.000Z |
Todos los datos geográficos (country, region, city) provienen del objeto cf de Cloudflare y se basan en la base de datos Geo-IP de Cloudflare. No se realizan consultas a servicios externos de Geo-IP; la resolución se realiza en el mismo Worker que ejecuta la redirección.
Esto tiene tres consecuencias:
city) no siempre está disponible (por ejemplo, con VPNs, operadores de telefonía móvil o regiones pequeñas). Cuenta con valores null y considéralos en tus paneles de control.GET /v1/codes/:id/scansDevuelve analíticas agregadas para un único código QR durante un período de tiempo seleccionable.
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 30Parámetros de consulta (Query Parameters):
| Parámetro | Tipo | Por defecto | Descripción |
|---|---|---|---|
days | integer | 30 | Período en días (1–365) |
Response (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" }}Campos de respuesta:
| Campo | Descripción |
|---|---|
total_scans | Total acumulado de escaneos del código (independientemente del intervalo de días days) |
period_scans | Suma de escaneos en el intervalo de tiempo seleccionado |
scans_by_day | Agrupaciones diarias, en orden ascendente por fecha; se omiten los días sin actividad |
top_countries | Los 8 principales países en el intervalo de tiempo, en orden descendente |
top_devices | Los 5 principales tipos de dispositivos (mobile, tablet, desktop) |
top_os | Los 5 principales sistemas operativos |
Los totales a nivel de espacio de trabajo (por ejemplo, escaneos al mes de todos los códigos) están disponibles a través de GET /v1/workspaces/:id en el campo scans_this_month. Para análisis cruzados entre códigos, recomendamos utilizar el panel de control o realizar una exportación periódica.
Si deseas procesar los eventos de escaneo en el momento en que ocurren (por ejemplo, para paneles de control en vivo, seguimiento de clientes potenciales o integraciones con CRM), suscríbete al evento 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" }}Documentación completa sobre verificación de firmas, lógica de reintentos e historial de entregas en API → Webhooks.
Los registros de escaneo no contienen direcciones IP de carácter personal. La IP original se reemplaza en el Edge Worker mediante una función de hash criptográfica (SHA-256) con una sal (salt) que rota diariamente. Esto significa:
La retención depende del plan:
| Plan | Retención |
|---|---|
| Free | 7 días |
| Pro | 90 días |
| Business / Agency | 1 año |
| Enterprise | Personalizado (SLA) |
Una tarea programada (cron job) diaria (purgeOldScans) elimina automáticamente los registros más antiguos; el valor total_scans del objeto de código no se ve afectado por esto.
days=365), la carga útil puede llegar a pesar varios KB. Almacena en caché en el lado del cliente con un TTL corto (por ejemplo, 60 s).top_countries, top_devices y top_os están limitados en el servidor a 8 o 5 entradas respectivamente. Para análisis más profundos, exporta los datos en bruto.null: country, region, city, referer y language pueden ser null. Trata estos casos como “Desconocido” en tus análisis.