Da de alta clientes, registra visitas y canjea premios desde tu sistema de reservas o tu punto de venta. Cada visita llega al teléfono del cliente, en su Apple Wallet o Google Wallet, en segundos.
Y en el teléfono de tu cliente, su tarjeta ya dice 7 de 10.
Empezar en tres pasos
1
Crea una llave
En tu panel: Ajustes, Integraciones. Elige «solo leer» si tu sistema nada más consulta, o «leer y escribir» si va a dar de alta clientes o registrar visitas. La API REST viene en el plan Max.
2
Guárdala como secreto
Se muestra una sola vez. Va en el servidor de tu sistema, nunca en una app o una página web. Una llave por sistema: si dejas de usar uno, revocas la suya y los demás siguen igual.
3
Pruébala
Llama a /yo con tu llave. Si responde el nombre de tu negocio, todo está listo.
El negocio de cada llamada sale de la llave, no de la URL. Lo que es de otro negocio responde 404, igual que lo que no existe.
Un reintento no da dos sellos
Manda un Idempotency-Key único en cada escritura y repítelo al reintentar. Durante 24 horas te regresamos la respuesta original, con el header Idempotency-Replayed, sin repetir la operación.
Las mismas reglas que el mostrador
Una visita por API pasa por promociones, referidos, niveles y el tope diario del programa. La API no puede saltarse el tope.
Datos claros
JSON con nombres en español y camelCase, fechas ISO 8601 en UTC e ids uuid. El id de una tarjeta es lo mismo que trae su QR. Todo queda en la actividad del negocio con origen «api».
Cuando algo sale mal
Siempre con la misma forma. Programa contra codigo, que no cambia; mensaje es para personas.
{
"error": {
"codigo":"conflicto",
"mensaje":"Ya llegó al límite de sellos de hoy",
"detalle": {
"motivo":"limite_diario"
}
}
}
Código
HTTP
Cuándo
invalido
400
El cuerpo o los parámetros no pasan la validación.
no_autenticado
401
Sin llave, mal formada, inexistente o revocada.
plan_no_incluye
403
El plan del negocio no incluye la API REST.
alcance_insuficiente
403
La llave es de solo lectura.
no_encontrado
404
No existe en este negocio (lo de otro negocio también es 404).
conflicto
409
Choca con el estado actual; detalle.motivo dice por qué.
limite
429
Demasiadas llamadas con esta llave: 3,000 lecturas o 600 escrituras por hora, y 60 altas con notificar por hora. Retry-After dice cuánto esperar.
Consultar
Leer programas, clientes y tarjetas. Basta una llave de solo lectura.
Probar la llave
GET/api/v1/yo
Qué negocio y qué llave son. Es lo primero que se llama al integrar: si responde, la llave y el plan están bien.
Los programas activos del negocio. Se necesita el id de uno para dar de alta a un cliente. mecanica es sellos (se suman) o multipase (paquete de clases que se descuentan). urlAlta es la liga de alta del programa (la del QR del mostrador): mándasela a un cliente y se instala la tarjeta solo. niveles (null si el programa no tiene) trae la escalera con la clave de cada nivel, y origen dice quién lo decide: automatico (las visitas), manual (el negocio) o externo (tu sistema, con PUT /clientes/{ref}/nivel).
Busca por identidad: teléfono, email o refExterna —el id del cliente en tu sistema— (al menos uno). El teléfono se compara por sus últimos 10 dígitos, así que +52 55 1234 5678, (55) 1234-5678 y 5512345678 encuentran al mismo cliente. No se busca por nombre a propósito. Máximo 20 resultados.
Basta una llave de solo lectura.
Parámetro
Va en
Qué es
telefono
la query
Teléfono, en cualquier formato.
email
la query
Email; no distingue mayúsculas.
refExterna
la query
El id del cliente en tu sistema (el que mandaste en `PUT /clientes/{ref}/nivel`).
Saldo, meta, premios disponibles, nivel, si el cliente ya la instaló en su wallet, y url: la liga con la que el cliente la abre o la instala. saldo son sellos en un programa de sellos y clases restantes en un paquete.
Basta una llave de solo lectura.
Parámetro
Va en
Qué es
id*
la ruta
El id de la tarjeta (es también el contenido de su QR).
Dar de alta, registrar visitas y canjear. Necesitan una llave de lectura y escritura.
Dar de alta un cliente
POST/api/v1/clientes
Crea al cliente y su tarjeta en un programa. Si ya tenía tarjeta en ese programa, se la re-entrega (200, reentrega: true) en vez de duplicarla. Por default no se le avisa al cliente: con notificar: true recibe el correo de bienvenida (o, si ya la tenía, la liga de su tarjeta), y notificacion dice si salió. Para mandarle la tarjeta por otro canal, usa tarjeta.url.
Necesita una llave de lectura y escritura. Acepta Idempotency-Key.
Un campo no pasa la validación (detalle.campo dice cuál).
no_encontrado
404
El programa no es de este negocio.
conflicto
409
El programa está inactivo.
no_autenticado
401
Sin llave, mal formada, inexistente o revocada.
plan_no_incluye
403
El plan del negocio no incluye la API REST.
alcance_insuficiente
403
La llave es de solo lectura.
Registrar una visita
POST/api/v1/tarjetas/{id}/visitas
El cliente vino. En un programa de sellos suma uno —con las promociones, los referidos y el tope diario del programa, igual que el escáner del mostrador—; en un paquete descuenta una clase. No se puede saltar el tope diario por API. Si con esta visita completa la meta, premiosGanados lo dice.
Necesita una llave de lectura y escritura. Acepta Idempotency-Key.
detalle.motivo: premio_ya_canjeado o premio_vencido.
no_autenticado
401
Sin llave, mal formada, inexistente o revocada.
plan_no_incluye
403
El plan del negocio no incluye la API REST.
alcance_insuficiente
403
La llave es de solo lectura.
Mandarle un mensaje al cliente
POST/api/v1/tarjetas/{id}/mensajes
Una notificación al teléfono del cliente: le suena en la pantalla bloqueada y le queda escrita en la tarjeta, en Apple y Google Wallet. Sirve para lo que tu sistema sabe y PatoCard no: «tu clase es mañana a las 7», «te quedan 2 clases». Acepta los marcadores del panel: {cliente}, {sellos}, {meta}, {faltan}, {premio}, {nivel}. En Apple se ve el cuerpo; en Google, titulo y cuerpo. Tope: 3 avisos por tarjeta cada 24 horas, contando los automáticos del negocio (es el límite de Google, y lo aplicamos también en Apple para no molestar). La tarjeta tiene que estar instalada (instalada: true). Necesita que el plan incluya los mensajes a la tarjeta. Queda en el historial del panel como enviado por la API.
Necesita una llave de lectura y escritura. Acepta Idempotency-Key.
Parámetro
Va en
Qué es
id*
la ruta
El id de la tarjeta.
Campo
Tipo
Qué es
titulo*
string
De 2 a 40 caracteres. Lo muestra Google.
cuerpo*
string
De 4 a 180 caracteres, con marcadores opcionales.
Llamada
curl -X POST https://patocard.com/api/v1/tarjetas/9fb2d02a-2e2f-4567-bd89-e197dc29c0ec/mensajes \
-H "Authorization: Bearer $PATOCARD_LLAVE" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"titulo":"Ila Hot Room","cuerpo":"{cliente}, tu clase de Hot Pilates es mañana a las 7:00. ¡Te esperamos!"}'
Respuesta 201: Entregado. `entrega` dice por qué wallet salió.
{
"mensaje": {
"id":"5b1e0c2a-8d4f-4e7a-9c3b-2f6a1d8e4c70",
"titulo":"Ila Hot Room",
"cuerpo":"Ana López, tu clase de Hot Pilates es mañana a las 7:00. ¡Te esperamos!"
},
"entrega": {
"apple": true,
"google": false
},
"avisosRestantesHoy": 2,
"tarjetaId":"9fb2d02a-2e2f-4567-bd89-e197dc29c0ec"
}
Errores posibles (8)
Código
HTTP
Cuándo
invalido
400
titulo o cuerpo fuera de medida (detalle.campo).
plan_no_incluye
403
El plan del negocio no incluye mensajes a la tarjeta.
no_encontrado
404
La tarjeta no es de este negocio.
conflicto
409
detalle.motivo: sin_pase (no la tiene en su wallet: mándale tarjeta.url), cuota_del_pase (ya recibió 3 avisos en 24 h), no_entregado (ningún teléfono la tiene ya), tarjeta_inactiva.
limite
429
Más de 60 mensajes en una hora con la misma llave.
no_autenticado
401
Sin llave, mal formada, inexistente o revocada.
plan_no_incluye
403
El plan del negocio no incluye la API REST.
alcance_insuficiente
403
La llave es de solo lectura.
Poner el nivel desde tu sistema
PUT/api/v1/clientes/{id}/nivel
Para cuando la membresía la vende y la cancela tu software (de reservas, del gimnasio): tú dices qué nivel tiene cada cliente y su tarjeta cambia de diseño sola en Apple y Google Wallet. El programa tiene que tener el nivel en manos de «Tu sistema» (Ajustes → Programas → Niveles); las claves salen de GET /programas. Crea y actualiza: si PatoCard no conoce al cliente y mandas cliente, lo da de alta (201) y desde entonces lo reconoce por tu {id}; si ya existía con ese teléfono, lo liga a tu {id}. Es un PUT de verdad: mandar lo mismo dos veces no genera un segundo cambio, push ni webhook. Con vence, al pasar ese día (en la hora del negocio) la tarjeta pierde el nivel sola; renovar es volver a mandarlo con otra fecha. Al subir, el cliente recibe la felicitación de nivel si el negocio la tiene prendida, y tus webhooks reciben nivel.cambio.
Necesita una llave de lectura y escritura. Acepta Idempotency-Key.
Parámetro
Va en
Qué es
id*
la ruta
El id del cliente en TU sistema (hasta 100 caracteres). También acepta el `id` de PatoCard.
Campo
Tipo
Qué es
nivel*
string
La clave del nivel, o null para quitárselo (la membresía se canceló).
vence
fecha
YYYY-MM-DD, el último día que vale. Sin él, el nivel no vence.
programaId
uuid
Solo si el negocio tiene más de un programa con nivel de «Tu sistema».
cliente
objeto
{ nombre, telefono, email? } para darlo de alta si PatoCard no lo conoce. Sin él, un cliente desconocido es 404.
Un campo no pasa la validación (detalle.campo). Un nivel que no existe trae detalle.validos con las claves buenas; varios programas con nivel externo y sin programaId trae detalle.programas.
no_encontrado
404
El cliente no existe y no mandaste cliente; o el programa no es de este negocio.
conflicto
409
detalle.motivo: origen_no_externo (el nivel de ese programa no lo decide tu sistema), telefono_de_otra_ref (ese teléfono ya está ligado a otro {id} tuyo), tarjeta_inactiva, en_proceso.
no_autenticado
401
Sin llave, mal formada, inexistente o revocada.
plan_no_incluye
403
El plan del negocio no incluye la API REST.
alcance_insuficiente
403
La llave es de solo lectura.
Asistentes de IA (MCP)
Conecta Claude, ChatGPT o tu propio agente a tu programa de lealtad y pregúntale lo que quieras: «¿cuántos clientes activos tengo este mes?», «¿cuántos sellos lleva el 55 1234 5678?». Es un servidor MCP remoto: la misma llave de la API, en todos los planes, de solo lectura.
Herramienta
Qué hace
Argumentos
mi_negocio
De qué negocio es la llave, su alcance y los programas con metas y premios. La primera que llama un agente.
—
programas
Los programas de lealtad: mecánica, meta de sellos, premio y liga de alta.
—
buscar_cliente
Clientes por teléfono o email, con sus tarjetas y saldos. No hay búsqueda por nombre.
telefono o email
cliente
Un cliente por id, con sus tarjetas.
id
tarjeta
Una tarjeta por id: saldo, premios pendientes, si está en Wallet y últimas visitas.
id
estadisticas
Los números de 7, 30 o 90 días: clientes activos, sellos, premios, por programa y quiénes más vienen.
La URL es https://patocard.com/api/mcp. La llave va en Authorization: Bearer; basta una de lectura.
Cada llamada cuenta contra el mismo tope de lectura que la API REST.
El agente ve solo tu negocio; no hay búsqueda por nombre, igual que en la API.
Hoy es de lectura. Dar sellos y canjear premios desde un agente viene después, con su propio alcance.
Webhooks
Con webhooks, PatoCard le avisa a tu servidor en cuanto pasa algo, sin que tengas que preguntar. Funciona con todo: el escáner del mostrador, el alta por QR, el panel y la API.
1
Agrega tu URL
En Ajustes, Integraciones, Webhooks: una dirección https de tu servidor y qué avisos quieres. Te damos un secreto de firma, una sola vez.
2
Responde 2xx
Cada aviso es un POST con JSON. Responde cualquier 2xx en menos de 10 segundos. Si no, lo reintentamos varias veces durante cerca de hora y media.
3
Verifica la firma
Calcula el HMAC con tu secreto y compáralo con PatoCard-Firma. Así sabes que el aviso viene de nosotros y no de alguien más.
Aviso
Nombre
Cuándo llega
cliente.alta
Cliente nuevo
Alguien sacó su tarjeta: por el QR del mostrador, una liga, la API o una importación.
sello.registrado
Sello
Una visita sumó sellos en un programa de sellos.
visita.registrada
Clase descontada
Una visita descontó una clase de un paquete.
paquete.cargado
Paquete cargado
Se cargaron clases a un paquete (el cliente compró).
premio.ganado
Premio ganado
El cliente completó la meta y tiene un premio por canjear.
import { createHmac, timingSafeEqual } from "node:crypto";
// cuerpo: el texto CRUDO del request, antes de parsear el JSON.
export function avisoValido(cuerpo, firma, secreto) {
const { t, v1 } = Object.fromEntries(firma.split(",").map((p) => p.split("=")));
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; // más de 5 min
const esperada = createHmac("sha256", secreto).update(`${t}.${cuerpo}`).digest("hex");
return v1?.length === esperada.length && timingSafeEqual(Buffer.from(v1), Buffer.from(esperada));
}
Header
Ejemplo
Para qué
PatoCard-Evento
sello.registrado
El tipo de aviso (sello.registrado…), o ping en la prueba del panel.
PatoCard-Entrega
518d8b02-…
El id del evento. No cambia entre reintentos: úsalo para no procesar dos veces el mismo aviso.
PatoCard-Firma
t=1760806860,v1=5f2c…
t=<unix>,v1=<firma>: HMAC-SHA256 con tu secreto de <t>.<cuerpo>. Recházalo si no coincide o si t tiene más de 5 minutos.
datos.tarjeta trae la tarjeta como está al momento de enviar, con la misma forma que GET /tarjetas/{id}. En premios trae además datos.premio, y en cambios de nivel, datos.nivel.
El mismo aviso puede llegar dos veces (por un reintento). Deduplica por PatoCard-Entrega.
Si tu servidor falla 50 veces seguidas, pausamos el webhook y te lo decimos en el panel; lo reanudas con un clic cuando esté arreglado.