API REST y webhooks
Crea, cambia y consulta tus códigos QR dinámicos desde tu propio software, y recibe un webhook firmado cuando alguien escanea o deja sus datos.
URL base: https://qrflick.com/api/v1
Autenticación
Cada petición lleva una clave de API en la cabecera Authorization. Las claves pertenecen a una cuenta (o espacio de equipo) y las crea su propietario o un administrador en Ajustes → Desarrolladores.
curl https://qrflick.com/api/v1/me \
-H "Authorization: Bearer api_…"
- Las claves de solo lectura pueden listar y obtener códigos, tarjetas, contactos, escaneos e imágenes.
- Las claves de lectura y escritura también pueden crear, cambiar y eliminar códigos y endpoints de webhook.
- Una clave deja de funcionar si quien la creó sale del equipo o deja de ser administrador, o si la cuenta pasa a un plan sin API.
Crea una clave en Ajustes → Desarrolladores.
Peticiones, errores y límites
Envía y recibe JSON. Las fechas van en ISO 8601 y UTC. Los ids son los de 12 caracteres que ves en las URL del panel.
Las listas van paginadas: usa ?page= y ?per_page= (hasta 100). Las respuestas incluyen data, links y meta. Cada clave tiene un límite de peticiones por minuto; la cabecera X-RateLimit-Remaining muestra lo que queda.
| Estado | Significado |
|---|---|
401 | Clave ausente, incorrecta o revocada. |
403 | La clave es de solo lectura, el plan no incluye la API o la acción no está permitida. |
404 | No existe ese registro en esta cuenta. |
422 | Algo de la petición no es válido o supera un límite del plan; errors indica qué campo. |
429 | Demasiadas peticiones. Espera los segundos de Retry-After. |
{
"message": "The name field is required.",
"errors": { "name": ["The name field is required."] }
}
Endpoints
| Petición | Qué hace |
|---|---|
GET /me |
La cuenta, el plan, la clave y los límites. Útil para probar la conexión. |
GET /qr-codes |
Tus códigos dinámicos, los más nuevos primero. Filtros: search, type, archived. |
POST /qr-codes |
Crea un código: type, name, payload y, opcionalmente, design. (requiere lectura y escritura) |
GET /qr-codes/{id} |
Un código, con su enlace corto, destino y número de escaneos. |
PATCH /qr-codes/{id} |
Cambia name, payload, type, design o paused (true/false). El código impreso no cambia. (requiere lectura y escritura) |
DELETE /qr-codes/{id} |
Elimina un código. Su enlace corto mostrará «no encontrado». (requiere lectura y escritura) |
GET /qr-codes/{id}/image |
El código en png, svg, pdf o eps: ?format=, y ?size= (píxeles) o ?mm= y ?dpi=. |
GET /qr-codes/{id}/scans |
Escaneos por día y desgloses (país, ciudad, dispositivo, sistema, navegador, idioma, origen): ?from= y ?to= (AAAA-MM-DD). |
GET /cards |
Tus tarjetas de visita digitales. |
GET /cards/{id} |
Una tarjeta, con su página y su enlace corto. |
GET /cards/{id}/leads |
Contactos compartidos en una tarjeta, los más nuevos primero. ?since= devuelve solo los posteriores. |
GET /webhooks |
Tus endpoints de webhook. |
POST /webhooks |
Suscribe una URL a eventos (url, events). La respuesta incluye el secreto de firma. (requiere lectura y escritura) |
DELETE /webhooks/{id} |
Da de baja un endpoint. (requiere lectura y escritura) |
Ejemplo: crear y cambiar un código
Crea un código de sitio web. La respuesta trae el enlace corto para imprimir o compartir.
curl -X POST https://qrflick.com/api/v1/qr-codes \
-H "Authorization: Bearer api_…" \
-H "Content-Type: application/json" \
-d '{"type": "url", "name": "Summer menu", "payload": {"url": "https://example.com/menu"}}'
{
"data": {
"id": "k3v9x2m7qa1b",
"object": "qr_code",
"name": "Summer menu",
"type": "url",
"payload": { "url": "https://example.com/menu" },
"short_url": "https://qrflick.com/Ab3xY9k",
"domain": "qrflick.com",
"destination": "https://example.com/menu",
"status": "active",
"scans": 0,
"last_scanned_at": null,
"archived": false,
"created_at": "2026-01-31T10:00:00+00:00",
"updated_at": "2026-01-31T10:00:00+00:00"
}
}
"domain" opcional: el dominio de redirección de la plataforma que tu espacio puede usar (los mismos que se ofrecen al crear un código en la app). Omítelo para el dominio corto estándar. La dirección no se puede cambiar por la API una vez creada.
Más adelante, lleva el mismo código impreso a otro sitio:
curl -X PATCH https://qrflick.com/api/v1/qr-codes/k3v9x2m7qa1b \
-H "Authorization: Bearer api_…" \
-H "Content-Type: application/json" \
-d '{"payload": {"url": "https://example.com/autumn-menu"}}'
Descárgalo como SVG listo para imprenta, de 40 mm de ancho:
curl "https://qrflick.com/api/v1/qr-codes/k3v9x2m7qa1b/image?format=svg&mm=40" \
-H "Authorization: Bearer api_…" -o menu.svg
Tipos de código QR y campos del payload
El payload contiene los campos del tipo de código. Los códigos de tarjetas y perfiles se crean con su tarjeta o perfil, no aquí.
| type | Campos del payload |
|---|---|
url |
url*
|
whatsapp |
phone*,
message
|
phone |
phone*
|
email |
email*,
subject,
body
|
sms |
phone*,
message
|
maps |
query*
|
google_review |
place_id*
|
link_bio Pro |
title*,
description,
links*
|
menu Pro |
title*,
description,
menu_url*
|
app_store Pro |
ios_url*,
android_url*,
fallback_url*
|
event Pro |
title*,
description,
starts_at*,
location,
url
|
coupon Pro |
title*,
description,
code*,
expires_at,
url
|
social |
platform*,
handle*
|
file |
title*,
description,
file_url
|
* obligatorio. Los tipos Pro requieren un plan con redirecciones inteligentes.
Webhooks
Añade un endpoint en Ajustes → Desarrolladores (o con POST /webhooks) y elige sus eventos. Cada evento se envía por POST en JSON:
| Evento | Se envía cuando |
|---|---|
qr_code.created | Se creó un código QR. |
qr_code.updated | Un código QR cambió de destino, estado o ajuste. |
qr_code.deleted | Se eliminó un código QR. |
scan.created | Alguien escaneó un código (sin bots ni vistas previas de enlaces). |
lead.created | Alguien compartió su contacto en una tarjeta o escribió desde un perfil. |
POST /your/endpoint
Content-Type: application/json
webhook-id: evt_01jbx6q9d2m8w4k7r3t5y0a1bc
webhook-timestamp: 1767225600
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=
{
"id": "evt_01jbx6q9d2m8w4k7r3t5y0a1bc",
"type": "scan.created",
"created_at": "2026-01-01T00:00:00+00:00",
"data": {
"qr_code": { "id": "k3v9x2m7qa1b", "name": "Summer menu", "type": "url" },
"short_url": "https://qrflick.com/Ab3xY9k",
"scanned_at": "2026-01-01T00:00:00+00:00",
"country": "ES", "city": "Madrid", "device": "mobile", "os": "ios",
"browser": "safari", "language": "es", "referrer": null, "variant": null
}
}
- Responde con cualquier estado 2xx en menos de 10 segundos; haz el trabajo lento después de responder.
- Cualquier otra respuesta se reintenta tras 1 min, 5 min, 30 min, 2 h, 6 h.
- Si 20 eventos seguidos fallan en todos sus intentos, el endpoint se pausa y el propietario recibe un email.
- Los reintentos y «Enviar de nuevo» mantienen el mismo webhook-id: úsalo para descartar duplicados.
- Los escaneos nunca incluyen direcciones IP. Los eventos de contacto incluyen los datos que el visitante decidió compartir.
Comprobar las firmas
Los envíos se firman según Standard Webhooks. Firma «webhook-id.webhook-timestamp.cuerpo sin procesar» con HMAC-SHA256, usando la parte de tu secreto tras whsec_ decodificada en base64, y compárala con la cabecera webhook-signature. Rechaza marcas de tiempo antiguas para evitar repeticiones.
// PHP
$secret = 'whsec_…';
$payload = $id . '.' . $timestamp . '.' . $rawBody;
$key = base64_decode(substr($secret, 6));
$expected = 'v1,' . base64_encode(hash_hmac('sha256', $payload, $key, true));
$valid = abs(time() - (int) $timestamp) < 300
&& hash_equals($expected, $signatureHeader);
// Node.js
import crypto from 'node:crypto';
const key = Buffer.from(secret.slice(6), 'base64');
const expected = 'v1,' + crypto.createHmac('sha256', key)
.update(`${id}.${timestamp}.${rawBody}`).digest('base64');
const valid = Math.abs(Date.now() / 1000 - Number(timestamp)) < 300
&& crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader));
Las librerías de Standard Webhooks (standardwebhooks.com) para la mayoría de lenguajes lo comprueban por ti.