API de Qubo

v1

Crea y descarga códigos QR desde tu propio sistema: tu tienda, tu POS o tu CRM. Con el mismo diseño del Studio, en SVG para imprenta o en PNG, estáticos o dinámicos para cambiar su destino sin reimprimirlos.

Introducción

La API usa JSON y una sola URL base: https://qubo.click/api/v1. Está incluida en los planes con API (hoy Pro y Lifetime) y cada QR que creas cuenta dentro del cupo de tu plan, igual que en el panel.

Con ella creas códigos QR estáticos o dinámicos de cualquier tipo del Studio, con tu diseño; descargas su imagen en SVG o PNG; cambias el destino de un dinámico, lo pausas o lo borras, y consultas sus escaneos. Todo lo que haces por API aparece también en tu panel, y al revés.

Autenticación

Envía tu clave en el encabezado X-API-KEY en cada petición (también sirve Authorization: Bearer). La encuentras en tu panel, en la tarjeta API Token, junto al botón para regenerarla.

Encabezado
X-API-KEY: tu_clave_de_api

Tu clave da acceso a tu cuenta: úsala solo desde tu servidor, nunca en código que corra en el navegador o en una app. Qubo la guarda cifrada y te la muestra una sola vez, al crearla o regenerarla; después solo ves su comienzo. Si la pierdes o se filtra, regénérala: la anterior deja de funcionar al instante. La clave no se acepta en la URL (?api_token=), porque quedaría escrita en los registros.

Crear un QR

POST/api/v1/qr
CampoTipoDescripción
titletexto, obligatorioNombre del QR. Lo ves en tu panel.
typestatic (por defecto) o dynamicUn dinámico apunta a un enlace corto y puedes cambiar su destino sin reimprimirlo. Solo para URL.
content_typeurl (por defecto), wifi, vcard, whatsapp, sms, email, text, cryptoQué guarda el QR.
payloadobjetoLos datos de ese tipo (tabla siguiente).
designobjeto, opcionalColores, formas y logo (más abajo).

Tipos de contenido

content_typepayloadPara qué
urlpayload.urlUna página web. Atajo: envía solo "url" sin payload.
wifipayload.ssid, password, encryption (WPA, WEP o nopass)Conecta al Wi-Fi sin escribir la clave.
vcardpayload.vcardName, vcardPhone, vcardEmail, vcardOrgGuarda un contacto en el celular.
whatsapppayload.smsPhone, smsMessageAbre un chat de WhatsApp con el mensaje escrito.
smspayload.smsPhone, smsMessageAbre un SMS listo para enviar.
emailpayload.emailTo, emailSubjectAbre un correo con el asunto escrito.
textpayload.textTexto libre.
cryptopayload.cryptoCoin, cryptoAddressDirección de una billetera (BTC, ETH…).

Diseño

Son las mismas opciones del Studio, y el servidor las dibuja con el mismo algoritmo. Lo que no envíes toma el diseño por defecto del Studio.

design.*ValoresQué cambia
color#RRGGBBColor de los puntos y las esquinas. Por defecto #0F172A.
color2 · gradient#RRGGBB · true/falseSegundo color y degradado en diagonal, como en el Studio.
background#RRGGBBFondo. Por defecto blanco.
dots_stylerounded, square, dots, extra-rounded, classy, classy-roundedForma de los puntos.
corners_styleleaf, extra-rounded, square, dot, classy, classy-roundedForma del marco de las tres esquinas. Por defecto leaf (gota, con la punta hacia el centro).
corner_dot_styleleaf, dot, squareForma del punto dentro de cada esquina. Por defecto leaf.
logo_url · logo_size · logo_clearhttps · 0.1 a 0.4 · true/falseLogo al centro (PNG, JPG, WebP o SVG, máx. 500 KB), su tamaño y si se quitan los puntos detrás.
Cuerpo de ejemplo
{
  "title": "Mesa 4 - Menú",
  "type": "dynamic",
  "url": "https://turestaurante.com/menu",
  "design": { "color": "#0F172A", "dots_style": "rounded", "corners_style": "leaf" }
}
cURL
# 1. Crear el QR
curl -X POST https://qubo.click/api/v1/qr \
  -H "X-API-KEY: tu_clave_de_api" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"title": "Mesa 4 - Menú", "type": "dynamic", "url": "https://turestaurante.com/menu"}'

# 2. Descargar su imagen (usa el id de la respuesta)
curl "https://qubo.click/api/v1/qr/1284/image?format=png&size=1024" \
  -H "X-API-KEY: tu_clave_de_api" -o mesa-4.png

Responde 201 Created. content es lo que lleva el QR e images trae las direcciones para descargarlo.

201 Created
{
  "success": true,
  "data": {
    "id": 1284,
    "title": "Mesa 4 - Menú",
    "type": "dynamic",
    "content_type": "url",
    "content": "https://qubo.click/r/AB12CD",
    "short_code": "AB12CD",
    "short_url": "https://qubo.click/r/AB12CD",
    "redirect_url": "https://turestaurante.com/menu",
    "is_active": true,
    "images": {
      "svg": "https://qubo.click/api/v1/qr/1284/image?format=svg",
      "png": "https://qubo.click/api/v1/qr/1284/image?format=png"
    },
    "created_at": "2026-10-25T20:42:10+00:00"
  }
}

La versión anterior, POST /api/v1/qr/generate con title y redirect_url, sigue funcionando igual: crea un QR dinámico de URL.

Descargar la imagen

GET/api/v1/qr/{id}/image?format=png&size=1024
ParámetroValoresDescripción
formatpng (por defecto) o svgSVG es vectorial: ideal para imprenta y empaques.
size128 a 4096, por defecto 1024Ancho y alto en píxeles.

Devuelve la imagen directamente (no JSON), con el diseño guardado del QR, también si lo editaste en el Studio. Solo descarga QR de tu cuenta.

Generar sin guardar

POST/api/v1/qr/render

Mismos campos de Crear un QR (sin title ni type), más format y size. Devuelve la imagen y no guarda nada en tu cuenta ni gasta cupo de QR. Sirve para etiquetas, tickets o vCards que imprimes una sola vez.

cURL
curl -X POST https://qubo.click/api/v1/qr/render \
  -H "X-API-KEY: tu_clave_de_api" \
  -H "Content-Type: application/json" \
  -d '{"content_type": "wifi", "payload": {"ssid": "Cafe_Invitados", "password": "cafe2026"}, "format": "svg"}' \
  -o wifi.svg

Listar, editar y borrar

ServicioAcciónDetalle
GET /api/v1/qrListarTus QR, del más nuevo al más viejo. Filtros: type, content_type, tag, q (busca en el nombre), is_active; per_page hasta 100.
GET /api/v1/qr/{id}ConsultarUn QR con su contenido, etiquetas y total de escaneos.
PATCH /api/v1/qr/{id}EditarCambia title, is_active (pausar o activar), tags, design y el destino: url si es dinámico, payload si es estático.
DELETE /api/v1/qr/{id}BorrarLo borra. Un QR dinámico impreso deja de funcionar.

La lista responde data con los QR, meta con la página y el total, y links.next con la siguiente página. En el diseño solo se cambia lo que envías: el resto se queda igual. Si editas el contenido de un QR estático, los que ya imprimiste siguen llevando al contenido viejo; para poder cambiarlo después, usa uno dinámico.

cURL
# Tus QR dinámicos con la etiqueta "menu", 50 por página
curl "https://qubo.click/api/v1/qr?type=dynamic&tag=menu&per_page=50" -H "X-API-KEY: tu_clave_de_api"

# Cambiar el destino de un QR dinámico (el QR impreso sigue sirviendo)
curl -X PATCH https://qubo.click/api/v1/qr/1284 \
  -H "X-API-KEY: tu_clave_de_api" -H "Content-Type: application/json" \
  -d '{"url": "https://turestaurante.com/menu-navidad"}'

# Pausarlo: quien lo escanee verá un aviso de "no disponible"
curl -X PATCH https://qubo.click/api/v1/qr/1284 \
  -H "X-API-KEY: tu_clave_de_api" -H "Content-Type: application/json" \
  -d '{"is_active": false}'

# Escaneos de los últimos 30 días
curl "https://qubo.click/api/v1/qr/1284/stats?days=30" -H "X-API-KEY: tu_clave_de_api"

Estadísticas

GET/api/v1/qr/{id}/stats?days=30

Escaneos totales, visitantes únicos, escaneos por día y de dónde vienen: dispositivo y navegador en todos los planes; país, sistema y sitio de origen con analítica avanzada. days se ajusta solo al historial de tu plan (Pro: 365 días). Nunca incluye la IP de nadie.

200 OK
{
  "success": true,
  "data": {
    "id": 1284,
    "days": 30,
    "from": "2026-09-27",
    "to": "2026-10-26",
    "total_scans": 412,
    "unique_visitors": 318,
    "timeline": [{ "date": "2026-10-25", "scans": 17 }, …],
    "breakdowns": {
      "device_type": [{ "label": "mobile", "total": 389 }, …],
      "country": [{ "label": "Colombia", "total": 371 }, …]
    }
  }
}

Enlaces cortos y links de WhatsApp

ServicioAcciónDetalle
GET /api/v1/linksListarEnlaces cortos y links de WhatsApp. Filtros: type (url o whatsapp), tag, q (nombre o alias), is_active; per_page hasta 100.
POST /api/v1/linksCrear enlacedestination_url y, opcionales, title, alias y tags. Sin alias, se genera uno de 6 letras.
POST /api/v1/whatsappCrear link de WhatsAppphone con indicativo (+57 300 123 4567) y message. Lleva a wa.me con el mensaje escrito.
GET /api/v1/links/{id}ConsultarUn enlace con su destino y sus clics.
PATCH /api/v1/links/{id}Editartitle, is_active, tags y el destino: destination_url en un enlace corto; phone o message en uno de WhatsApp.
DELETE /api/v1/links/{id}BorrarLo borra; quien lo abra llegará al inicio de Qubo.
GET /api/v1/links/{id}/statsEstadísticasClics por día y de dónde vienen, igual que en los QR.

Los dos tipos comparten el cupo de enlaces de tu plan, igual que en el panel. El alias no se cambia: es lo que ya imprimiste o compartiste. Si lo necesitas distinto, crea otro enlace.

cURL
# Enlace corto con alias propio
curl -X POST https://qubo.click/api/v1/links \
  -H "X-API-KEY: tu_clave_de_api" -H "Content-Type: application/json" \
  -d '{"destination_url": "https://tutienda.com/promo", "alias": "promo-oct"}'

# Link de WhatsApp con el mensaje ya escrito
curl -X POST https://qubo.click/api/v1/whatsapp \
  -H "X-API-KEY: tu_clave_de_api" -H "Content-Type: application/json" \
  -d '{"phone": "+57 300 123 4567", "message": "Hola, quiero hacer un pedido"}'

# Cambiar el mensaje (el destino wa.me se arma solo)
curl -X PATCH https://qubo.click/api/v1/links/88 \
  -H "X-API-KEY: tu_clave_de_api" -H "Content-Type: application/json" \
  -d '{"message": "Hola, quiero reservar"}'

Tarjetas de lealtad, cupones, tarjetas de regalo y eventos

ServicioAcciónDetalle
GET · POST /api/v1/loyalty-programsProgramasListar y crear tarjetas de sellos. GET, PATCH y DELETE /loyalty-programs/{id} para una; PATCH cambia solo lo que envías.
GET /api/v1/loyalty-programs/{id}/cardsClientesLas tarjetas de tus clientes con sus sellos y premios. since trae solo las nuevas.
POST …/cards/{code}/stampSellarSuma un sello desde tu punto de venta, igual que la caja. result reward si con ese completó la tarjeta.
POST …/cards/{code}/redeemPremioEntrega un premio ganado. 422 si no tiene premios por reclamar.
GET · POST …/couponsCuponesListar y crear cupones con condiciones, vencimiento y cupo. PATCH y DELETE …/coupons/{couponId} para editar, pausar o borrar.
POST …/coupons/{couponId}/claimsDarLe da el cupón a un cliente con tarjeta (card_code) y devuelve el enlace de su cupón para mandárselo.
POST …/coupon-claims/{code}/redeemCanjearCanjea el cupón de un cliente una sola vez. 422 si ya se usó o venció.
GET · POST …/gift-cardsRegaloListar y emitir tarjetas de regalo con su valor en pesos. Cóbralas antes por tu cuenta: Qubo no cobra, solo lleva el saldo.
POST …/gift-cards/{code}/chargeCobrarDescuenta una compra del saldo (amount, note). Si no alcanza responde 422 y no descuenta nada. GET …/gift-cards/{code} da saldo y movimientos.
POST …/gift-cards/{code}/voidAnularDeja el saldo en cero, por ejemplo si devolviste el dinero.
GET · POST …/eventsEventosListar y crear eventos con fecha, lugar, cupo y localidades. mode signup es gratis con inscripción; counter, entradas vendidas en tu mostrador. GET, PATCH y DELETE …/events/{eventId}.
GET · POST …/events/{eventId}/ticketsEntradasListar las entradas o emitirlas tras cobrar (tier_id, quantity, holder_name). order_url abre todas las de la venta.
POST …/tickets/{code}/check-inPuertaRegistra la entrada una sola vez. 422 si ya entró y dice a qué hora. …/tickets/{code}/void la anula y devuelve el cupo.

El code de una tarjeta o de un cupón es lo que va en su QR: tu sistema lo lee y llama a la API. Sellos, premios y cupones canjeados por API quedan en la estadística con el nombre API y se actualizan solos en Google Wallet.

cURL
# Tu punto de venta suma un sello al leer el QR del cliente
curl -X POST https://qubo.click/api/v1/loyalty-programs/3/cards/k2Jd9xQ.../stamp -H "X-API-KEY: tu_clave_de_api"

# {"success": true, "data": {"result": "reward", "card": {"stamps": 0, "rewards_available": 1, ...}}}

# Un cupón que vence a fin de mes, con cupo de 200
curl -X POST https://qubo.click/api/v1/loyalty-programs/3/coupons \
  -H "X-API-KEY: tu_clave_de_api" -H "Content-Type: application/json" \
  -d '{"title": "2x1 en boletas 2D", "expires_on": "2026-11-30", "max_claims": 200}'

# Dáselo a un cliente y mándale el enlace (data.url) por WhatsApp
curl -X POST https://qubo.click/api/v1/loyalty-programs/3/coupons/7/claims \
  -H "X-API-KEY: tu_clave_de_api" -H "Content-Type: application/json" -d '{"card_code": "k2Jd9xQ..."}'

Catálogos

ServicioAcciónDetalle
GET · POST /api/v1/catalogsCatálogosListar y crear catálogos o menús. GET /catalogs/{id} trae sus productos en orden; PATCH y DELETE para editarlo o borrarlo.
POST /api/v1/catalogs/{id}/productsProductosAgrega un producto con categoría, precio y foto. PATCH y DELETE …/products/{productId} para cambiarlo, pausarlo o borrarlo.
GET /api/v1/catalogs/{id}/statsEstadísticasVisitas y pedidos enviados por WhatsApp, con su total.

Los pedidos se arman con los precios que tiene Qubo y llegan solo por WhatsApp. Mantén precios y existencias al día desde tu inventario o tu POS.

cURL
# Tu inventario cambió un precio: actualízalo en el menú
curl -X PATCH https://qubo.click/api/v1/catalogs/5/products/42 \
  -H "X-API-KEY: tu_clave_de_api" -H "Content-Type: application/json" -d '{"price": 9000}'

# Se agotó: pausarlo sin borrarlo
curl -X PATCH https://qubo.click/api/v1/catalogs/5/products/42 \
  -H "X-API-KEY: tu_clave_de_api" -H "Content-Type: application/json" -d '{"is_active": false}'

Clientes

ServicioAcciónDetalle
GET /api/v1/customersListarLa misma lista de Clientes del panel: tarjetas, formularios e importados unidos por WhatsApp o correo. segment filtra new, returning o lapsed; q busca.
POST /api/v1/customersTraerHasta 500 contactos por llamada desde tu CRM, tu tienda o tus concursos. La misma fuente con el mismo correo o WhatsApp solo completa lo que faltaba.
DELETE /api/v1/customersBorrarTodos los datos de una persona en tu cuenta (tarjetas, sellos, formularios e importados), por key, email o whatsapp.

Cada contacto necesita correo o WhatsApp y consented_at: la fecha en que autorizó el tratamiento de sus datos en tu sistema. Sin esa fecha no se guarda. Usa source para saber de dónde llegó; así se ve en Clientes.

cURL
# Traer clientes de tu CRM o de un concurso (hasta 500 por llamada; repetir no duplica)
curl -X POST https://qubo.click/api/v1/customers \
  -H "X-API-KEY: tu_clave_de_api" -H "Content-Type: application/json" \
  -d '{"customers": [{"name": "Ana Gómez", "email": "ana@ejemplo.co", "whatsapp": "300 123 4567",
       "source": "Concurso de octubre", "consented_at": "2026-10-04T14:03:36-05:00"}]}'

# {"success": true, "data": {"created": 1, "updated": 0, "unchanged": 0}}

# Los que no vienen hace 30 días
curl "https://qubo.click/api/v1/customers?segment=lapsed" -H "X-API-KEY: tu_clave_de_api"

# Alguien pidió que borren sus datos
curl -X DELETE https://qubo.click/api/v1/customers -H "X-API-KEY: tu_clave_de_api" \
  -H "Content-Type: application/json" -d '{"email": "ana@ejemplo.co"}'

Errores

Todos los errores responden con la misma forma: success: false y un error.code estable para que tu código decida qué hacer.

422 Unprocessable Entity
{
  "success": false,
  "message": "Revisa los datos enviados: El campo title es obligatorio.",
  "error": { "code": "validation", "message": "Revisa los datos enviados: El campo title es obligatorio." },
  "errors": { "title": ["El campo title es obligatorio."] }
}
401missing_key · invalid_key · key_in_url

Falta la clave, no corresponde a ninguna cuenta activa o llegó en la URL en vez del encabezado.

403plan_required · limit_reached

Tu plan no incluye la API, o ya usaste el cupo de QR de tu plan.

404not_found

Ese QR no existe en tu cuenta.

422validation

Algún dato no es válido. El campo errors dice cuál y por qué.

429rate_limited

Superaste tu cupo de solicitudes por minuto. Espera el tiempo que indica Retry-After.

Límites

Cada clave tiene su propio cupo de solicitudes por minuto según el plan (Pro: 120 por minuto). Las respuestas traen X-RateLimit-Remaining y, si te pasas, Retry-After. La cantidad de QR que puedes crear es la de tu plan; en tu panel ves cuántas solicitudes llevas este mes.

OpenAPI y Postman

Toda la API está descrita en openapi.json (OpenAPI 3.1), el formato estándar que leen las herramientas de desarrollo. Con él generas un cliente en tu lenguaje o pruebas cada servicio sin escribir código.

  • Postman: Import → Link → pega https://qubo.click/openapi.json. Crea la colección con todos los servicios; en Authorization elige API Key, X-API-KEY, Header.
  • Insomnia o Bruno: importa el mismo enlace.
  • Un cliente en tu lenguaje: OpenAPI Generator o el que use tu equipo, con el mismo archivo.

Webhooks

Con el plan Pro, Qubo envía una petición POST a tu URL cada vez que ocurre un evento. Los configuras en tu panel, en Webhooks: hasta 5 URLs https, cada una con los eventos que elijas.

qr.scannedAlguien escaneó un código QR dinámico.
link.clickedAlguien abrió un enlace corto.
whatsapp.clickedAlguien abrió un enlace de WhatsApp.
biolink.viewedAlguien visitó un BioLink.
biolink.clickedAlguien tocó un botón de un BioLink.
biolink.leadAlguien envió un formulario de un BioLink (con sus respuestas).
Cuerpo del evento
{
  "id": "2f0c7a8e-5c1b-4a0e-9f4e-3d2b1a0c9e8f",
  "event": "qr.scanned",
  "occurred_at": "2026-10-07T20:42:10+00:00",
  "data": {
    "asset": { "type": "qr", "id": 1284, "title": "Mesa 4 - Menú", "short_code": "AB12CD" },
    "visitor": { "country": "CO", "device_type": "mobile", "browser": "Chrome", "platform": "Android" }
  }
}

Cada petición lleva el encabezado X-Qubo-Signature: sha256=…: el HMAC-SHA256 del cuerpo con el secreto de ese webhook. Compruébalo antes de confiar en el evento. Responde con un código 2xx en menos de 5 segundos; si falla, Qubo reintenta hasta 3 veces. Por privacidad, los eventos nunca incluyen la IP del visitante.

Verificar la firma
// Node.js (Express): compara la firma con el cuerpo sin modificar
const crypto = require('crypto');

app.post('/webhooks/qubo', express.raw({ type: 'application/json' }), (req, res) => {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', process.env.QUBO_WEBHOOK_SECRET)
    .update(req.body)
    .digest('hex');
  const received = req.get('X-Qubo-Signature') || '';
  if (received.length !== expected.length ||
      !crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected))) {
    return res.sendStatus(401);
  }
  const event = JSON.parse(req.body);
  // ... tu lógica
  res.sendStatus(200);
});