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.
La API REST y los webhooks están cerrados por ahora. Puedes preparar claves en Ajustes → Desarrolladores. La disponibilidad de MCP se controla por separado.
URL base: https://qrflick.com/api/v1
Referencia legible por máquinas (OpenAPI 3.1) para Postman, generadores de código y herramientas de IA: https://qrflick.com/developers/openapi.json
Reintentos seguros: envía Idempotency-Key en POST /qr-codes y POST /webhooks. Repetir la misma clave y el mismo cuerpo en 24 horas devuelve la primera respuesta sin crear un duplicado. No reutilices claves entre peticiones. Las subidas multipart de logos y el generador estático no garantizan esta repetición.
¿Usas Claude u otro asistente de IA? Conecta nuestro servidor MCP en https://qrflick.com/mcp para crear y gestionar códigos en lenguaje natural (en Ajustes → Desarrolladores te explicamos cómo).
Guías paso a paso: Crea códigos QR en bloque con la API · Gestiona códigos QR desde Claude
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 /design-options |
Todos los patrones, esquinas, marcos y límites que acepta el objeto design, con sus nombres. |
GET /qr-types |
Tipos actuales, restricciones de campos anidados, ejemplos y disponibilidad para creación mediante API. |
GET /folders |
Nombres de carpeta y cuántos códigos tiene cada una. |
GET /tags |
Etiquetas en uso. |
POST /static-qr-codes |
Genera un código estático a partir de type y fields (url, wifi, vcard, whatsapp…) con cualquier diseño: svg, png o json. No se guarda nada. |
POST /uploads/logos |
Sube un logo (campo multipart file). Usa el id devuelto como design.logo_upload_id. (requiere lectura y escritura) |
GET /qr-codes |
Tus códigos dinámicos, los más nuevos primero, cada uno con su diseño guardado. Filtros: search, type, folder, tag, 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, folder, tags, paused o archived (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 /qr-codes/{id}/poll-results |
Estado de la encuesta, papeletas totales, votos y porcentajes por opción. Sin identidades de votantes. |
GET /qr-codes/{id}/form-responses |
Respuestas paginadas con consentimiento, origen y UTM. Filtros: since, source, page y per_page. |
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) |
POST /static-qr-codes admite claves de solo lectura: genera una imagen sin guardar nada.
Conecta tu aplicación
Las claves pertenecen al espacio de trabajo que estás usando. Crea una clave distinta para cada integración.
- Crea una clave con nombre. Elige Solo lectura para informes o Lectura y escritura para crear y cambiar códigos.
- Copia la clave una vez y guárdala en el entorno de tu servidor o en un gestor de secretos. No la incluyas en código del navegador, aplicaciones móviles ni repositorios públicos.
- Sustituye YOUR_API_KEY en el ejemplo y consulta /me para comprobar la cuenta, el acceso y los límites actuales.
curl "https://qrflick.com/api/v1/me" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json"
Para renovar una clave, crea otra, actualiza la integración y comprueba que funciona. Después revoca la anterior. Las claves revocadas dejan de funcionar al instante.
Encuestas, formularios y páginas alojadas
Crea encuestas y formularios con POST /qr-codes y edítalos con PATCH /qr-codes/{id}. Los eventos admiten organizador, recinto y agenda; los cupones, oferta y condiciones. Consulta GET /qr-types para ver campos, reglas y ejemplos actualizados. Las reglas usan required, nullable, min, max e in; los puntos indican campos anidados y * cada elemento de una lista. El servidor también comprueba los límites del plan y las URL.
design en el nivel superior controla la imagen QR. payload.design controla la página alojada: marca, preajuste, colores, fuente, diseño y botones. Lee el payload guardado antes de editar y conserva los identificadores de campos y opciones. Si envías payload en un PATCH, sustituye el contenido completo: incluye todos los campos que quieras conservar. Si lo omites, no cambia. No se pueden eliminar opciones que ya tienen votos.
Encuesta
POST /api/v1/qr-codes
{
"type": "poll",
"name": "Encuesta",
"payload": {
"title": "Which workshop should we run?",
"options": [
{
"label": "Photography"
},
{
"label": "Design"
}
],
"choice": "single",
"vote_limit": "person",
"results": "after_vote",
"design": {
"preset": "sage",
"layout": "rounded",
"brand_name": "Studio"
}
}
}
Formulario
POST /api/v1/qr-codes
{
"type": "form",
"name": "Formulario",
"payload": {
"title": "Request a quote",
"fields": [
{
"type": "email",
"label": "Your email",
"required": true
},
{
"type": "textarea",
"label": "What do you need?"
}
],
"controller": "Example Studio",
"privacy_url": "https://example.com/privacy",
"retention_days": 365,
"thank_you": "Thank you. We will be in touch.",
"design": {
"preset": "ocean",
"layout": "editorial"
}
}
}
Evento
POST /api/v1/qr-codes
{
"type": "event",
"name": "Evento",
"payload": {
"title": "Studio open day",
"starts_at": "2030-06-15T10:00",
"ends_at": "2030-06-15T17:00",
"timezone": "Europe/Madrid",
"organizer": "Example Studio",
"venue_name": "Main studio",
"agenda": [
{
"time": "10:00",
"title": "Welcome",
"speaker": "Studio team"
}
],
"design": {
"preset": "sage"
}
}
}
Cupón
POST /api/v1/qr-codes
{
"type": "coupon",
"name": "Cupón",
"payload": {
"title": "Welcome offer",
"code": "WELCOME10",
"offer": "10% off",
"terms": "One use per customer.",
"design": {
"preset": "clay"
}
}
}
GET /qr-codes/{id}/poll-results devuelve papeletas totales, votos y porcentajes por opción; los porcentajes se calculan sobre todas las opciones seleccionadas. GET /qr-codes/{id}/form-responses devuelve respuestas, consentimiento, país, origen y atribución UTM. Se ordenan de más reciente a más antigua: usa page, per_page (1–100), since (fecha exclusiva) y source (origen, o - para el código principal). Ambos admiten claves de solo lectura. Las respuestas son datos personales; nunca se devuelven identificadores de votantes ni listas de suscriptores.
Las tarjetas digitales, los perfiles de empresa y el contenido de las cartas de restaurante se crean y editan en el panel. Sus códigos QR se pueden listar y descargar. Los envíos de formularios y los votos no generan webhooks: consulta estos endpoints de informes periódicamente. La API no envía votos ni respuestas.
Referencia del conector MCP
Conecta un cliente MCP a esta URL e inicia sesión para elegir espacio de trabajo y acceso. Los clientes con cabeceras personalizadas también pueden enviar una clave REST con Authorization: Bearer YOUR_API_KEY. Los tokens OAuth del conector solo funcionan en /mcp; REST requiere una clave de API.
https://qrflick.com/mcp
Usa get_qr_types antes de crear una encuesta, un formulario o una página con campos nuevos. get_poll_results devuelve resultados y list_form_responses respuestas con filtros de origen. Los informes admiten conexiones de solo lectura. El conector no tiene herramienta de eliminación; pausa o archiva los códigos.
upload_logo, create_qr_code, create_qr_codes, update_qr_code, set_qr_code_protection y set_qr_code_rules requieren permiso de lectura y escritura. Las demás herramientas solo leen o generan imágenes.
get_qr_typesget_poll_resultslist_form_responsesget_accountlist_qr_codesget_qr_codeget_qr_code_scansget_account_scansexport_scans_csvget_qr_code_imageget_design_optionslist_folders_and_tagslist_logosrender_static_qr_codelist_cardslist_card_leadsupload_logocreate_qr_codecreate_qr_codesupdate_qr_codeset_qr_code_protectionset_qr_code_rules
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 |
review_url,
place_id
|
link_bio Pro |
title*,
description,
links*,
theme,
avatar_upload_id,
design,
design.brand_name,
design.accent,
design.background,
design.surface,
design.text,
design.font,
design.layout,
design.button_style,
design.preset
|
menu Pro |
title*,
description,
menu_url*
|
app_store Pro |
ios_url*,
android_url*,
fallback_url
|
event Pro |
title*,
description,
starts_at*,
ends_at,
location,
url,
organizer,
venue_name,
timezone,
details,
agenda,
agenda.*,
agenda.*.time*,
agenda.*.title*,
agenda.*.speaker,
agenda.*.description,
design,
design.brand_name,
design.accent,
design.background,
design.surface,
design.text,
design.font,
design.layout,
design.button_style,
design.preset
|
coupon Pro |
title*,
description,
code*,
expires_at,
url,
offer,
terms,
design,
design.brand_name,
design.accent,
design.background,
design.surface,
design.text,
design.font,
design.layout,
design.button_style,
design.preset
|
social |
platform*,
handle*
|
file |
title*,
description,
file_url
|
poll |
title*,
description,
options*,
options.*,
options.*.id,
options.*.label*,
options.*.image_upload_id,
choice,
max_choices,
vote_limit,
results,
starts_at,
ends_at,
timezone,
collect_emails,
design,
design.brand_name,
design.accent,
design.background,
design.surface,
design.text,
design.font,
design.layout,
design.button_style,
design.preset
|
form |
title*,
description,
fields*,
fields.*,
fields.*.id,
fields.*.type*,
fields.*.label*,
fields.*.help,
fields.*.required,
fields.*.options,
fields.*.min,
fields.*.max,
submit_label,
thank_you,
redirect_url,
controller,
privacy_url,
retention_days,
notify,
design,
design.brand_name,
design.accent,
design.background,
design.surface,
design.text,
design.font,
design.layout,
design.button_style,
design.preset
|
* 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
&& Buffer.byteLength(expected) === Buffer.byteLength(signatureHeader)
&& 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.