PatoCard

Tu sistema, conectado a tus tarjetas de lealtad

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.

Un cliente vino a su clase
curl -X POST https://patocard.com/api/v1/tarjetas/$TARJETA/visitas \
  -H "Authorization: Bearer $PATOCARD_LLAVE" \
  -H "Idempotency-Key: $(uuidgen)"

201 "saldo": 7, "meta": 10

Y en el teléfono de tu cliente, su tarjeta ya dice 7 de 10.

Empezar en tres pasos

  1. 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. 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. 3

    Pruébala

    Llama a /yo con tu llave. Si responde el nombre de tu negocio, todo está listo.

curl https://patocard.com/api/v1/yo \
  -H "Authorization: Bearer $PATOCARD_LLAVE"

Reglas de la API

Una llave, un negocio

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ódigoHTTPCuándo
invalido400El cuerpo o los parámetros no pasan la validación.
no_autenticado401Sin llave, mal formada, inexistente o revocada.
plan_no_incluye403El plan del negocio no incluye la API REST.
alcance_insuficiente403La llave es de solo lectura.
no_encontrado404No existe en este negocio (lo de otro negocio también es 404).
conflicto409Choca con el estado actual; detalle.motivo dice por qué.
limite429Demasiadas 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.

Basta una llave de solo lectura.

Llamada
curl https://patocard.com/api/v1/yo \
  -H "Authorization: Bearer $PATOCARD_LLAVE"
Respuesta 200: El negocio y la llave (nunca la llave completa).
{
  "negocio": {
    "nombre": "Ila Hot Room",
    "slug": "ila"
  },
  "llave": {
    "nombre": "Ollynk",
    "prefijo": "pc_live_hLBzJM",
    "alcance": "escritura"
  }
}
Errores posibles (2)
CódigoHTTPCuándo
no_autenticado401Sin llave, mal formada, inexistente o revocada.
plan_no_incluye403El plan del negocio no incluye la API REST.

Listar programas

GET/api/v1/programas

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).

Basta una llave de solo lectura.

Llamada
curl https://patocard.com/api/v1/programas \
  -H "Authorization: Bearer $PATOCARD_LLAVE"
Respuesta 200: Los programas, del más antiguo al más nuevo.
{
  "programas": [
    {
      "id": "6c1d2e3f-4a5b-4c6d-8e7f-90a1b2c3d4e5",
      "nombre": "Clases",
      "slug": "sellos",
      "mecanica": "sellos",
      "meta": 10,
      "premio": "1 clase gratis",
      "urlAlta": "https://patocard.com/alta/estudio/sellos",
      "niveles": {
        "origen": "externo",
        "escalera": [
          {
            "clave": "member",
            "nombre": "Member"
          },
          {
            "clave": "vip_heat",
            "nombre": "VIP Heat Member"
          }
        ]
      }
    }
  ]
}
Errores posibles (2)
CódigoHTTPCuándo
no_autenticado401Sin llave, mal formada, inexistente o revocada.
plan_no_incluye403El plan del negocio no incluye la API REST.

Buscar clientes

GET/api/v1/clientes

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ámetroVa enQué es
telefonola queryTeléfono, en cualquier formato.
emailla queryEmail; no distingue mayúsculas.
refExternala queryEl id del cliente en tu sistema (el que mandaste en `PUT /clientes/{ref}/nivel`).
Llamada
curl https://patocard.com/api/v1/clientes?telefono=5512345678 \
  -H "Authorization: Bearer $PATOCARD_LLAVE"
Respuesta 200: Los clientes que coinciden, con sus tarjetas.
{
  "clientes": [
    {
      "id": "39a8da8f-2a37-4a83-a105-b18d4f8110b5",
      "refExterna": null,
      "nombre": "Ana López",
      "telefono": "5512345678",
      "email": "ana@ejemplo.com",
      "fechaNacimiento": "1990-04-12",
      "creadoEn": "2026-10-18T16:32:32.000Z",
      "tarjetas": [
        {
          "id": "9fb2d02a-2e2f-4567-bd89-e197dc29c0ec",
          "programa": {
            "id": "6c1d2e3f-4a5b-4c6d-8e7f-90a1b2c3d4e5",
            "nombre": "Clases",
            "mecanica": "sellos"
          },
          "activa": true,
          "saldo": 7,
          "meta": 10,
          "premiosDisponibles": [],
          "nivel": null,
          "instalada": true,
          "url": "https://patocard.com/tarjeta/9fb2d02a-2e2f-4567-bd89-e197dc29c0ec?t=…",
          "creadaEn": "2026-10-18T16:32:32.000Z"
        }
      ]
    }
  ]
}
Errores posibles (3)
CódigoHTTPCuándo
invalido400No se mandó ni telefono, ni email, ni refExterna.
no_autenticado401Sin llave, mal formada, inexistente o revocada.
plan_no_incluye403El plan del negocio no incluye la API REST.

Consultar un cliente

GET/api/v1/clientes/{id}

El cliente con todas sus tarjetas en el negocio.

Basta una llave de solo lectura.

ParámetroVa enQué es
id*la rutaEl id del cliente.
Llamada
curl https://patocard.com/api/v1/clientes/39a8da8f-2a37-4a83-a105-b18d4f8110b5 \
  -H "Authorization: Bearer $PATOCARD_LLAVE"
Respuesta 200: El cliente.
{
  "cliente": {
    "id": "39a8da8f-2a37-4a83-a105-b18d4f8110b5",
    "refExterna": null,
    "nombre": "Ana López",
    "telefono": "5512345678",
    "email": "ana@ejemplo.com",
    "fechaNacimiento": "1990-04-12",
    "creadoEn": "2026-10-18T16:32:32.000Z",
    "tarjetas": [
      {
        "id": "9fb2d02a-2e2f-4567-bd89-e197dc29c0ec",
        "programa": {
          "id": "6c1d2e3f-4a5b-4c6d-8e7f-90a1b2c3d4e5",
          "nombre": "Clases",
          "mecanica": "sellos"
        },
        "activa": true,
        "saldo": 7,
        "meta": 10,
        "premiosDisponibles": [],
        "nivel": null,
        "instalada": true,
        "url": "https://patocard.com/tarjeta/9fb2d02a-2e2f-4567-bd89-e197dc29c0ec?t=…",
        "creadaEn": "2026-10-18T16:32:32.000Z"
      }
    ]
  }
}
Errores posibles (3)
CódigoHTTPCuándo
no_encontrado404No existe en este negocio.
no_autenticado401Sin llave, mal formada, inexistente o revocada.
plan_no_incluye403El plan del negocio no incluye la API REST.

Consultar una tarjeta

GET/api/v1/tarjetas/{id}

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ámetroVa enQué es
id*la rutaEl id de la tarjeta (es también el contenido de su QR).
Llamada
curl https://patocard.com/api/v1/tarjetas/9fb2d02a-2e2f-4567-bd89-e197dc29c0ec \
  -H "Authorization: Bearer $PATOCARD_LLAVE"
Respuesta 200: La tarjeta.
{
  "tarjeta": {
    "id": "9fb2d02a-2e2f-4567-bd89-e197dc29c0ec",
    "programa": {
      "id": "6c1d2e3f-4a5b-4c6d-8e7f-90a1b2c3d4e5",
      "nombre": "Clases",
      "mecanica": "sellos"
    },
    "activa": true,
    "saldo": 7,
    "meta": 10,
    "premiosDisponibles": [],
    "nivel": null,
    "instalada": true,
    "url": "https://patocard.com/tarjeta/9fb2d02a-2e2f-4567-bd89-e197dc29c0ec?t=…",
    "creadaEn": "2026-10-18T16:32:32.000Z",
    "cliente": {
      "id": "39a8da8f-2a37-4a83-a105-b18d4f8110b5",
      "nombre": "Ana López"
    }
  }
}
Errores posibles (3)
CódigoHTTPCuándo
no_encontrado404No existe en este negocio.
no_autenticado401Sin llave, mal formada, inexistente o revocada.
plan_no_incluye403El plan del negocio no incluye la API REST.

Hacer cambios

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.

CampoTipoQué es
programaId*uuidEl programa (de GET /programas).
nombre*stringDe 2 a 120 caracteres.
telefono*string10 dígitos, en cualquier formato.
emailstringPara el correo de bienvenida.
fechaNacimientofechaYYYY-MM-DD, para la felicitación de cumpleaños.
notificarbooleanAvisarle al cliente. Default false.
Llamada
curl -X POST https://patocard.com/api/v1/clientes \
  -H "Authorization: Bearer $PATOCARD_LLAVE" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"programaId":"6c1d2e3f-4a5b-4c6d-8e7f-90a1b2c3d4e5","nombre":"Ana López","telefono":"+52 55 1234 5678","email":"ana@ejemplo.com","notificar":true}'
Respuesta 201: Creado (`201`) o re-entregado (`200`).
{
  "reentrega": false,
  "cliente": {
    "id": "39a8da8f-2a37-4a83-a105-b18d4f8110b5",
    "refExterna": null,
    "nombre": "Ana López",
    "telefono": "5512345678",
    "email": "ana@ejemplo.com",
    "fechaNacimiento": "1990-04-12",
    "creadoEn": "2026-10-18T16:32:32.000Z",
    "tarjetas": [
      {
        "id": "9fb2d02a-2e2f-4567-bd89-e197dc29c0ec",
        "programa": {
          "id": "6c1d2e3f-4a5b-4c6d-8e7f-90a1b2c3d4e5",
          "nombre": "Clases",
          "mecanica": "sellos"
        },
        "activa": true,
        "saldo": 0,
        "meta": 10,
        "premiosDisponibles": [],
        "nivel": null,
        "instalada": false,
        "url": "https://patocard.com/tarjeta/9fb2d02a-2e2f-4567-bd89-e197dc29c0ec?t=…",
        "creadaEn": "2026-10-18T16:32:32.000Z"
      }
    ]
  },
  "tarjeta": {
    "id": "9fb2d02a-2e2f-4567-bd89-e197dc29c0ec",
    "programa": {
      "id": "6c1d2e3f-4a5b-4c6d-8e7f-90a1b2c3d4e5",
      "nombre": "Clases",
      "mecanica": "sellos"
    },
    "activa": true,
    "saldo": 0,
    "meta": 10,
    "premiosDisponibles": [],
    "nivel": null,
    "instalada": false,
    "url": "https://patocard.com/tarjeta/9fb2d02a-2e2f-4567-bd89-e197dc29c0ec?t=…",
    "creadaEn": "2026-10-18T16:32:32.000Z",
    "cliente": {
      "id": "39a8da8f-2a37-4a83-a105-b18d4f8110b5",
      "nombre": "Ana López"
    }
  },
  "notificacion": {
    "tipo": "bienvenida",
    "enviada": true
  }
}
Errores posibles (6)
CódigoHTTPCuándo
invalido400Un campo no pasa la validación (detalle.campo dice cuál).
no_encontrado404El programa no es de este negocio.
conflicto409El programa está inactivo.
no_autenticado401Sin llave, mal formada, inexistente o revocada.
plan_no_incluye403El plan del negocio no incluye la API REST.
alcance_insuficiente403La 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.

ParámetroVa enQué es
id*la rutaEl id de la tarjeta.
Llamada
curl -X POST https://patocard.com/api/v1/tarjetas/9fb2d02a-2e2f-4567-bd89-e197dc29c0ec/visitas \
  -H "Authorization: Bearer $PATOCARD_LLAVE" \
  -H "Idempotency-Key: $(uuidgen)"
Respuesta 201: La tarjeta ya actualizada.
{
  "tarjeta": {
    "id": "9fb2d02a-2e2f-4567-bd89-e197dc29c0ec",
    "programa": {
      "id": "6c1d2e3f-4a5b-4c6d-8e7f-90a1b2c3d4e5",
      "nombre": "Clases",
      "mecanica": "sellos"
    },
    "activa": true,
    "saldo": 0,
    "meta": 10,
    "premiosDisponibles": [
      {
        "id": "a80709d3-3773-4ae5-8dc0-a193dc47e14a",
        "descripcion": "1 clase gratis",
        "venceEn": null
      }
    ],
    "nivel": null,
    "instalada": true,
    "url": "https://patocard.com/tarjeta/9fb2d02a-2e2f-4567-bd89-e197dc29c0ec?t=…",
    "creadaEn": "2026-10-18T16:32:32.000Z",
    "cliente": {
      "id": "39a8da8f-2a37-4a83-a105-b18d4f8110b5",
      "nombre": "Ana López"
    }
  },
  "premiosGanados": [
    "1 clase gratis"
  ],
  "promocion": null,
  "subioNivel": null
}
Errores posibles (5)
CódigoHTTPCuándo
no_encontrado404La tarjeta no es de este negocio.
conflicto409detalle.motivo: limite_diario (llegó al tope de hoy), sin_saldo (paquete agotado), tarjeta_inactiva…
no_autenticado401Sin llave, mal formada, inexistente o revocada.
plan_no_incluye403El plan del negocio no incluye la API REST.
alcance_insuficiente403La llave es de solo lectura.

Canjear un premio

POST/api/v1/premios/{id}/canje

Marca un premio disponible como canjeado. El id sale de tarjeta.premiosDisponibles.

Necesita una llave de lectura y escritura. Acepta Idempotency-Key.

ParámetroVa enQué es
id*la rutaEl id del premio.
Llamada
curl -X POST https://patocard.com/api/v1/premios/a80709d3-3773-4ae5-8dc0-a193dc47e14a/canje \
  -H "Authorization: Bearer $PATOCARD_LLAVE" \
  -H "Idempotency-Key: $(uuidgen)"
Respuesta 200: El premio canjeado y la tarjeta.
{
  "premio": {
    "id": "a80709d3-3773-4ae5-8dc0-a193dc47e14a",
    "descripcion": "1 clase gratis",
    "canjeadoEn": "2026-10-18T17:01:00.000Z"
  },
  "tarjeta": {
    "id": "9fb2d02a-2e2f-4567-bd89-e197dc29c0ec",
    "programa": {
      "id": "6c1d2e3f-4a5b-4c6d-8e7f-90a1b2c3d4e5",
      "nombre": "Clases",
      "mecanica": "sellos"
    },
    "activa": true,
    "saldo": 0,
    "meta": 10,
    "premiosDisponibles": [],
    "nivel": null,
    "instalada": true,
    "url": "https://patocard.com/tarjeta/9fb2d02a-2e2f-4567-bd89-e197dc29c0ec?t=…",
    "creadaEn": "2026-10-18T16:32:32.000Z",
    "cliente": {
      "id": "39a8da8f-2a37-4a83-a105-b18d4f8110b5",
      "nombre": "Ana López"
    }
  }
}
Errores posibles (5)
CódigoHTTPCuándo
no_encontrado404El premio no es de este negocio.
conflicto409detalle.motivo: premio_ya_canjeado o premio_vencido.
no_autenticado401Sin llave, mal formada, inexistente o revocada.
plan_no_incluye403El plan del negocio no incluye la API REST.
alcance_insuficiente403La 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ámetroVa enQué es
id*la rutaEl id de la tarjeta.
CampoTipoQué es
titulo*stringDe 2 a 40 caracteres. Lo muestra Google.
cuerpo*stringDe 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ódigoHTTPCuándo
invalido400titulo o cuerpo fuera de medida (detalle.campo).
plan_no_incluye403El plan del negocio no incluye mensajes a la tarjeta.
no_encontrado404La tarjeta no es de este negocio.
conflicto409detalle.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.
limite429Más de 60 mensajes en una hora con la misma llave.
no_autenticado401Sin llave, mal formada, inexistente o revocada.
plan_no_incluye403El plan del negocio no incluye la API REST.
alcance_insuficiente403La 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ámetroVa enQué es
id*la rutaEl id del cliente en TU sistema (hasta 100 caracteres). También acepta el `id` de PatoCard.
CampoTipoQué es
nivel*stringLa clave del nivel, o null para quitárselo (la membresía se canceló).
vencefechaYYYY-MM-DD, el último día que vale. Sin él, el nivel no vence.
programaIduuidSolo si el negocio tiene más de un programa con nivel de «Tu sistema».
clienteobjeto{ nombre, telefono, email? } para darlo de alta si PatoCard no lo conoce. Sin él, un cliente desconocido es 404.
Llamada
curl -X PUT https://patocard.com/api/v1/clientes/OLY-48213/nivel \
  -H "Authorization: Bearer $PATOCARD_LLAVE" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"nivel":"vip_heat","vence":"2026-11-30","cliente":{"nombre":"Ana López","telefono":"+52 55 1234 5678"}}'
Respuesta 200: Actualizado (`200`) o cliente dado de alta (`201`). `cambio` es null si ya tenía ese nivel.
{
  "clienteCreado": false,
  "cambio": {
    "de": "member",
    "a": "vip_heat",
    "subio": true
  },
  "cliente": {
    "id": "39a8da8f-2a37-4a83-a105-b18d4f8110b5",
    "refExterna": "OLY-48213",
    "nombre": "Ana López",
    "telefono": "5512345678",
    "email": "ana@ejemplo.com",
    "fechaNacimiento": "1990-04-12",
    "creadoEn": "2026-10-18T16:32:32.000Z",
    "tarjetas": [
      {
        "id": "9fb2d02a-2e2f-4567-bd89-e197dc29c0ec",
        "programa": {
          "id": "6c1d2e3f-4a5b-4c6d-8e7f-90a1b2c3d4e5",
          "nombre": "Clases",
          "mecanica": "sellos"
        },
        "activa": true,
        "saldo": 7,
        "meta": 10,
        "premiosDisponibles": [],
        "nivel": {
          "clave": "vip_heat",
          "nombre": "VIP Heat Member",
          "vence": "2026-12-01T06:00:00.000Z"
        },
        "instalada": true,
        "url": "https://patocard.com/tarjeta/9fb2d02a-2e2f-4567-bd89-e197dc29c0ec?t=…",
        "creadaEn": "2026-10-18T16:32:32.000Z"
      }
    ]
  },
  "tarjeta": {
    "id": "9fb2d02a-2e2f-4567-bd89-e197dc29c0ec",
    "programa": {
      "id": "6c1d2e3f-4a5b-4c6d-8e7f-90a1b2c3d4e5",
      "nombre": "Clases",
      "mecanica": "sellos"
    },
    "activa": true,
    "saldo": 7,
    "meta": 10,
    "premiosDisponibles": [],
    "nivel": {
      "clave": "vip_heat",
      "nombre": "VIP Heat Member",
      "vence": "2026-12-01T06:00:00.000Z"
    },
    "instalada": true,
    "url": "https://patocard.com/tarjeta/9fb2d02a-2e2f-4567-bd89-e197dc29c0ec?t=…",
    "creadaEn": "2026-10-18T16:32:32.000Z",
    "cliente": {
      "id": "39a8da8f-2a37-4a83-a105-b18d4f8110b5",
      "nombre": "Ana López"
    }
  }
}
Errores posibles (6)
CódigoHTTPCuándo
invalido400Un 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_encontrado404El cliente no existe y no mandaste cliente; o el programa no es de este negocio.
conflicto409detalle.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_autenticado401Sin llave, mal formada, inexistente o revocada.
plan_no_incluye403El plan del negocio no incluye la API REST.
alcance_insuficiente403La 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.

HerramientaQué haceArgumentos
mi_negocioDe qué negocio es la llave, su alcance y los programas con metas y premios. La primera que llama un agente.—
programasLos programas de lealtad: mecánica, meta de sellos, premio y liga de alta.—
buscar_clienteClientes por teléfono o email, con sus tarjetas y saldos. No hay búsqueda por nombre.telefono o email
clienteUn cliente por id, con sus tarjetas.id
tarjetaUna tarjeta por id: saldo, premios pendientes, si está en Wallet y últimas visitas.id
estadisticasLos números de 7, 30 o 90 días: clientes activos, sellos, premios, por programa y quiénes más vienen.dias: 7 | 30 | 90
claude.ai y Claude Desktop (sin configurar nada)
  1. Ajustes → Conectores → Agregar conector personalizado.
  2. Pega la URL https://patocard.com/api/mcp y conecta.
  3. Se abre una pantalla de PatoCard: pega la llave de tu negocio y autoriza. Listo.
Claude Code (una línea)
claude mcp add --transport http patocard https://patocard.com/api/mcp --header "Authorization: Bearer pc_live_TU_LLAVE"
claude.ai, Claude Desktop, Cursor y otros (MCP remoto)
{
  "mcpServers": {
    "patocard": {
      "url": "https://patocard.com/api/mcp",
      "headers": {
        "Authorization": "Bearer pc_live_TU_LLAVE"
      }
    }
  }
}
Clientes que solo hablan stdio (vía mcp-remote)
{
  "mcpServers": {
    "patocard": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "https://patocard.com/api/mcp",
        "--header",
        "Authorization: Bearer pc_live_TU_LLAVE"
      ]
    }
  }
}
  • 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. 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. 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. 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.

AvisoNombreCuándo llega
cliente.altaCliente nuevoAlguien sacó su tarjeta: por el QR del mostrador, una liga, la API o una importación.
sello.registradoSelloUna visita sumó sellos en un programa de sellos.
visita.registradaClase descontadaUna visita descontó una clase de un paquete.
paquete.cargadoPaquete cargadoSe cargaron clases a un paquete (el cliente compró).
premio.ganadoPremio ganadoEl cliente completó la meta y tiene un premio por canjear.
premio.canjeadoPremio canjeadoSe canjeó un premio.
nivel.cambioCambio de nivelEl cliente subió o bajó de nivel.
Lo que recibe tu servidor (sello.registrado)
{
  "id": "518d8b02-89d1-4245-aeff-70386669a2cc",
  "tipo": "sello.registrado",
  "creadoEn": "2026-10-18T17:01:00.000Z",
  "negocio": {
    "slug": "ila"
  },
  "datos": {
    "tarjeta": {
      "id": "9fb2d02a-2e2f-4567-bd89-e197dc29c0ec",
      "programa": {
        "id": "6c1d2e3f-4a5b-4c6d-8e7f-90a1b2c3d4e5",
        "nombre": "Clases",
        "mecanica": "sellos"
      },
      "activa": true,
      "saldo": 8,
      "meta": 10,
      "premiosDisponibles": [],
      "nivel": null,
      "instalada": true,
      "url": "https://patocard.com/tarjeta/9fb2d02a-2e2f-4567-bd89-e197dc29c0ec?t=…",
      "creadaEn": "2026-10-18T16:32:32.000Z",
      "cliente": {
        "id": "39a8da8f-2a37-4a83-a105-b18d4f8110b5",
        "nombre": "Ana López"
      }
    },
    "cantidad": 1,
    "origen": "escaner"
  }
}
Verificar la firma (Node)
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));
}
HeaderEjemploPara qué
PatoCard-Eventosello.registradoEl tipo de aviso (sello.registrado…), o ping en la prueba del panel.
PatoCard-Entrega518d8b02-…El id del evento. No cambia entre reintentos: úsalo para no procesar dos veces el mismo aviso.
PatoCard-Firmat=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.
  • Vienen en el plan Max, con la API REST.