LealUp Docs
Integraciones

API de ingesta

Envía eventos de uso y eventos de facturación a LealUp desde tu propio sistema, con autenticación por API key.

Si tu producto no está en la lista de integraciones, o quieres enviar eventos propios, la API de ingesta es el camino directo: tu backend hace un POST y los eventos entran a LealUp igual que los de cualquier conector.

Es la única API pública de LealUp hoy. Está pensada para máquinas, no para el navegador: la llave se usa desde tu servidor.

Antes de empezar

  • URL base: https://api.lealup.com/v1
  • Autenticación: cabecera X-API-Key
  • Formato: JSON en la petición y en la respuesta

Conseguir tu API key

  1. Entra a Configuración → API key.
  2. Genera la llave. Tiene el formato sk_live_ seguido de 64 caracteres hexadecimales.
  3. Cópiala en ese momento. Después solo verás los últimos 4 caracteres; el resto queda enmascarado y no hay forma de recuperarla.
  4. Si la pierdes o se filtra, regenérala desde la misma pantalla. La anterior deja de funcionar de inmediato.

La llave identifica a tu organización: LealUp deduce el tenant de la llave, nunca del cuerpo de la petición. Guárdala como cualquier otro secreto de producción y no la publiques en código de cliente.

Una llave sk_live_ da acceso de escritura a los eventos de tu organización. Trátala como una credencial de servidor: variable de entorno o gestor de secretos, jamás en un repositorio ni en JavaScript del navegador.

Enviar eventos de uso

POST /v1/ingest/events

Es el endpoint principal: registra lo que tus usuarios hacen en tu producto. Esos eventos alimentan la adopción, el health score y los disparadores de playbooks.

Cuerpo de la petición

CampoTipoObligatorioDetalle
eventslistaEntre 1 y 100 eventos por lote

Y dentro de cada evento:

CampoTipoObligatorioDetalle
event_nametextoNombre del evento, 1 a 255 caracteres
customer_idtextover notaUUID del cliente en LealUp
external_customer_idtextover notaEl identificador que ese cliente tiene en tu sistema
external_sourcetextonoSistema de origen de external_customer_id. Por defecto internal
user_idtextonoIdentificador del usuario que generó el evento
product_idUUIDnoAtribución a un producto de tu catálogo
product_codetextonoIgual que product_id, pero por código o SKU
propertiesobjetonoPropiedades libres en JSON
timestampfecha ISO 8601noCuándo ocurrió. Por defecto, el momento de la ingesta

Nota sobre el cliente: cada evento necesita una referencia a un cliente, y vale cualquiera de las dos. Si envías customer_id, se usa ese. Si envías solo external_customer_id, LealUp lo resuelve con la combinación de tu organización, external_source y ese identificador; tiene que coincidir con el valor que traía el cliente cuando se creó en LealUp. Si mandas los dos, gana customer_id.

Nota sobre el producto: si envías product_id y product_code, gana product_id. Un evento sin ninguno de los dos es un evento de cuenta, no de producto, y se comporta como siempre. Un producto que no existe en tu catálogo no rechaza el evento: se guarda como evento de cuenta.

Ejemplo

curl -X POST https://api.lealup.com/v1/ingest/events \
  -H "X-API-Key: $LEALUP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [
      {
        "event_name": "reporte_exportado",
        "external_customer_id": "acme_12345",
        "external_source": "internal",
        "user_id": "u_889",
        "properties": { "formato": "pdf", "filas": 1240 },
        "timestamp": "2026-08-05T14:32:00Z"
      }
    ]
  }'

Respuesta

{
  "accepted": 1,
  "rejected": 0,
  "errors": [],
  "message": "Accepted 1 events for processing"
}

El código es 202 Accepted.

Lotes parcialmente aceptados

Esto es lo más importante de este endpoint: un evento malo no bota el lote. La respuesta sigue siendo 202 y te dice cuáles no entraron:

{
  "accepted": 2,
  "rejected": 1,
  "errors": [
    {
      "index": 1,
      "event_name": "reporte_exportado",
      "code": "unknown_external_customer",
      "message": "..."
    }
  ],
  "message": "Accepted 2 events; 1 rejected"
}

El index es la posición del evento dentro del arreglo que enviaste, empezando en 0. Los códigos posibles:

CódigoQué significa
unknown_customerEnviaste customer_id, pero no existe un cliente con ese UUID en tu organización
unknown_external_customerLa combinación de external_source y external_customer_id no resolvió a ningún cliente
invalid_customer_idEl customer_id no es un UUID válido

Revisa siempre rejected. Un 202 no significa que entraron todos.

Enviar eventos de facturación

POST /v1/ingest/billing-events

Registra hechos de cobranza (pagos fallidos, disputas, reintentos) que alimentan la salud de pago del cliente.

Cuerpo de la petición

CampoTipoObligatorioDetalle
eventslistaEntre 1 y 50 eventos por lote

Y dentro de cada evento:

CampoTipoObligatorioDetalle
event_typetextoUno de: payment_failed, payment_succeeded, invoice_disputed, dunning_attempt, refund_issued
customer_idUUIDUUID del cliente en LealUp. Aquí no sirve el identificador externo
amountnúmeronoMonto, cero o positivo
currencytextonoCódigo ISO 4217 de 3 letras. Por defecto USD
statustextonoUno de: pending, resolved, escalated. Por defecto pending

Ejemplo

curl -X POST https://api.lealup.com/v1/ingest/billing-events \
  -H "X-API-Key: $LEALUP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [
      {
        "event_type": "payment_failed",
        "customer_id": "6f1c2e40-9b3a-4a11-8f0e-2a7c5d9e1b44",
        "amount": 249.90,
        "currency": "USD",
        "status": "pending"
      }
    ]
  }'

Respuesta

{
  "accepted": 1,
  "skipped": 0,
  "message": "Accepted 1 billing events (0 skipped)"
}

También 202 Accepted. skipped cuenta los eventos que la base de datos rechazó por chocar con uno ya registrado.

Este endpoint no deduplica: envía cada evento una sola vez. El choque que skipped reporta se decide, entre otros campos, por el instante exacto en que se escribió la fila, así que dos envíos del mismo evento entran como dos eventos distintos. En la práctica skipped siempre llega en 0.

Importa porque estos eventos alimentan la salud de pago del cliente: reintentar un lote que falló a medias inflaría sus pagos fallidos. Si tu proceso reintenta, lleva tú el control de qué ya enviaste.

Límites

LímiteValor
Peticiones por minuto300 por organización
Eventos de uso por lote100
Eventos de facturación por lote50

El límite de 300 por minuto es compartido entre los dos endpoints y se cuenta por organización, no por llave ni por IP. Es el valor por defecto: si tu volumen lo justifica, se puede subir para tu organización.

Para volúmenes altos, agrupa: 100 eventos en una petición cuestan lo mismo que 1.

Errores

Toda respuesta, exitosa o no, incluye la cabecera X-Trace-Id. Guárdala: es lo primero que te va a pedir soporte.

401, problema con la llave

{ "detail": "Missing X-API-Key header" }

Aparece si falta la cabecera X-API-Key o si la llave no es válida (el mensaje cambia a Invalid API key). La causa más común es haber regenerado la llave y no haber actualizado la variable de entorno.

422, el cuerpo no pasa la validación

{
  "detail": [
    {
      "type": "too_long",
      "loc": ["body", "events"],
      "msg": "List should have at most 100 items after validation, not 101",
      "input": []
    }
  ]
}

loc te dice exactamente dónde está el problema: ["body", "events", 0, "event_type"] significa el campo event_type del primer evento del lote.

Ojo con la diferencia: un lote de 101 eventos, o un event_type que no está en la lista permitida, son errores de validación y botan el lote completo con 422. Un cliente que no existe es un error por evento y devuelve 202 con el detalle en errors.

429, superaste el límite

{
  "detail": {
    "type": "https://lealup.com/errors/rate-limited",
    "title": "Rate limit exceeded",
    "status": 429,
    "detail": "Rate limit exceeded for tier 'ingestion'. Try again in 37 seconds.",
    "retry_after": 37
  }
}

La respuesta trae la cabecera Retry-After con los segundos que faltan. Respétala en vez de reintentar de inmediato.

500, algo falló de nuestro lado

{
  "type": "https://lealup.local/errors/500",
  "title": "Internal Server Error",
  "status": 500,
  "detail": "An unexpected error occurred"
}

Ningún evento del lote se guardó: la escritura es atómica por petición. Reintenta con espera creciente y, si persiste, escríbenos con el X-Trace-Id.

Cómo integrar bien

  • Envía por lotes, no evento por evento. Acumula y despacha cada pocos segundos o cada 100 eventos, lo que llegue primero.
  • Reintenta solo los 429 y los 5xx, con espera creciente. Un 422 no mejora al reintentarlo: el cuerpo está mal y hay que corregirlo.
  • Reintentar reenvía. Si una petición se cae por timeout sin que sepas si llegó, un reintento puede registrar los eventos dos veces. Para métricas de adopción suele ser tolerable; si necesitas exactitud, lleva registro de qué lotes confirmaste.
  • No bloquees a tu usuario. Manda los eventos desde una cola en tu backend, no dentro del request que atiende a la persona.
  • Empieza con external_customer_id. Te evita mantener una tabla de equivalencias con los UUID de LealUp.
  • Revisa rejected y registra los errors. Es donde vas a ver que un cliente nuevo todavía no existe en LealUp.

Preguntas frecuentes

¿Puedo llamar la API desde el navegador?

No. La llave da acceso de escritura a los eventos de toda tu organización y en el navegador queda expuesta. Llama siempre desde tu servidor.

¿Qué pasa si envío un evento de un cliente que todavía no existe en LealUp?

Ese evento se rechaza con unknown_customer o unknown_external_customer, y el resto del lote entra igual. Crea primero el cliente (a mano, por CSV o por una integración de CRM) y reenvía.

¿Puedo borrar un evento ya enviado?

No por la API. Escríbenos a soporte@lealup.com si necesitas corregir datos.

¿Hay librería oficial?

Todavía no. Son dos endpoints con JSON: cualquier cliente HTTP sirve.

On this page