Documentación de API - Kharyo AI

Documentación para desarrolladores

API de Kharyo

Recursos HTTP versionados para integrar conciliación de pagos, avisos de WhatsApp y mensajería de bandeja de entrada desde tu propio sistema.

Guía de inicio

1. Crea tu llave

Desde tu cuenta de Kharyo, entra a Configuración de la API y crea una llave eligiendo solo los permisos (abilities) que tu integración necesita. Solo el propietario del espacio de trabajo puede crear llaves.

2. Autentícate

Envía la llave como Bearer token en cada solicitud.

Authorization: Bearer kh_xxxxxxxxxxxxxxxxxxxxxxxxxxxx

3. Haz tu primera solicitud

Un buen primer request es consultar tu propio uso del mes.

curl https://api.kharyo.com/v1/usage \
  -H "Authorization: Bearer kh_xxxxxxxxxxxxxxxxxxxxxxxxxxxx"

4. Maneja errores e idempotencia

Los errores siempre traen error y message. En los endpoints de escritura que mueven dinero o mensajes, envía una clave de idempotencia (header Idempotency-Key en cobros, campo client_id en mensajes) para que un reintento de red nunca duplique el efecto.

{ "error": "currency_mismatch", "message": "La referencia coincide con un pago registrado en USDT, no en USD." }

5. Respeta los límites

Cada llave tiene un límite base de 60 solicitudes por minuto. Algunos endpoints de escritura tienen además su propio límite, más estricto, indicado en la referencia de abajo.

Permisos (abilities)

Cada llave tiene su propio conjunto de permisos, elegidos al crearla. Una llave sin el permiso que exige un endpoint recibe 403.

notify:sendEnviar avisos de WhatsApp
targets:readLeer destinos de avisos habilitados
claims:writeRegistrar cobros
claims:readLeer cobros y el cierre de caja
reconciliation:readLeer el cierre de caja diario
usage:readLeer el uso del espacio de trabajo
inbox:readLeer conversaciones, mensajes y plantillas
inbox:writeEnviar mensajes y plantillas
templates:readLeer plantillas de WhatsApp y los WABA conectados
templates:writeCrear, sincronizar y borrar plantillas de WhatsApp
webhooks:manageCrear, listar y eliminar suscripciones de webhooks salientes

Manejo de errores

Todas las respuestas de error son JSON. Existen dos formas, según en qué capa se generó el rechazo: es importante distinguirlas si tu integración hace switch sobre el código.

Antes de llegar al endpoint

Token inválido, permiso faltante, ruta inexistente, validación de parámetros o límite genérico excedido. error es una frase legible.

{
  "error": "Validation failed",
  "message": "The amount field is required.",
  "errors": { "amount": ["The amount field is required."] }
}

Rechazado por la lógica de negocio

La solicitud llegó al endpoint y este la rechazó. error es un código estable en snake_case, pensado para hacer switch sobre él.

{
  "error": "subscription_required",
  "message": "El plan del espacio de trabajo no está activo."
}

Nota: un 403 por permiso faltante trae error: "Forbidden" (forma de arriba), mientras que un 403 porque tu usuario ya no pertenece al espacio de trabajo de la llave trae error: "forbidden_tenant" (forma de abajo). De igual forma, un 404 de ruta inexistente trae "Not found", y un 404 de recurso no encontrado dentro de un endpoint trae "not_found". El status HTTP es el mismo; el código de error distingue el motivo.

Límites de uso

  • Límite base: 60 solicitudes por minuto por llave, sobre todos los endpoints.
  • Algunos endpoints de escritura tienen además su propio límite, más estricto y por espacio de trabajo (30/min en cobros y en envíos de bandeja de entrada; 30/min y 2500/día en avisos de WhatsApp). Se indica en cada endpoint de la referencia.
  • Un reintento con la misma Idempotency-Key o el mismo client_id nunca consume cupo nuevo, solo un intento que de verdad va a crear algo cuenta contra el límite.
  • Al exceder un límite, la respuesta es 429 con retry_after (segundos hasta que se libera cupo).

Formatos de paginación

La API usa dos formatos, según el endpoint. Revisa cada uno en la referencia antes de asumir un shape.

Estándar (GET /payment-claims, GET /inbox/conversations, GET /templates)

{
  "data": [...],
  "links": { "first": "...", "last": "...", "prev": null, "next": "..." },
  "meta": { "current_page": 1, "last_page": 3, "per_page": 20, "total": 45, ... }
}

Reducido (GET .../conversations/{id}/messages)

{
  "data": [...],
  "meta": { "current_page": 1, "last_page": 2, "per_page": 20, "total": 33 }
}

GET /notification-targets, GET /inbox/templates, GET /templates/wabas, GET /webhooks y GET /webhooks/{id}/deliveries no están paginados: devuelven { "data": [...] } completo (deliveries, además, siempre trae como máximo 50 filas).

Webhooks

Además de llamar a la API, puedes dejar que Kharyo te avise: cuando ocurre alguno de estos eventos en tu espacio de trabajo, Kharyo envía una solicitud POST a la URL que registres, firmada para que puedas verificar que viene de nosotros.

Eventos disponibles

payment_claim.matchedUn cobro registrado cruzó con una notificación bancaria.
payment_claim.registeredSe registró un cobro esperado, todavía sin cruzar contra el banco.
lead.capturedUn chatbot capturó un lead con teléfono o correo.
inbox.message.receivedLlegó un mensaje nuevo de un contacto en la bandeja de entrada.
inbox.sla.breachedUna conversación superó el SLA de primera respuesta del espacio de trabajo.
template.status_changedMeta aprobó, rechazó, pausó o deshabilitó una plantilla de WhatsApp.

Crear una suscripción

curl -X POST https://api.kharyo.com/v1/webhooks \
  -H "Authorization: Bearer kh_xxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/webhooks/kharyo", "events": ["payment_claim.matched", "lead.captured"]}'

Verificar la firma

Verifica la firma sobre los bytes crudos que llegaron, antes de parsear el JSON: la mayoría de frameworks web parsean el cuerpo automáticamente, así que asegúrate de capturar el cuerpo original primero. Compara con una función de tiempo constante, nunca con ==.

import hashlib
import hmac

def verify_kharyo_signature(secret, timestamp, raw_body, signature_header):
    expected = "sha256=" + hmac.new(
        secret.encode(), f"{timestamp}.".encode() + raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature_header)

# timestamp = headers["X-Kharyo-Timestamp"]
# signature_header = headers["X-Kharyo-Signature"]
# raw_body = el cuerpo tal cual llegó (bytes), antes de parsear el JSON

Reintentos y desactivación automática

  • Si tu endpoint no responde un 2xx dentro de 10 segundos, Kharyo reintenta hasta 5 intentos en total, con esperas de 1, 5 y 30 minutos y 2 horas entre ellos, sin seguir redirecciones. Al llegar a 20 fallos consecutivos, la suscripción se desactiva sola y se avisa al dueño del espacio de trabajo; la única forma de recuperarla es eliminarla y crear una nueva.
  • Responde rápido y procesa después: confirma con un 2xx apenas encoles el evento en tu sistema, y deja el trabajo pesado para un proceso aparte. Un handler lento no ayuda: el tiempo de espera siempre es de 10 segundos.

Referencia de endpoints

Todos los endpoints viven bajo https://api.kharyo.com/v1 y exigen un Bearer token con el permiso indicado.

Avisos

Envío de notificaciones de WhatsApp a destinos verificados del espacio de trabajo.

POST/v1/notifications/whatsappnotify:send

Envía un aviso a uno o todos los destinos habilitados para la API.

Límite propio: 30/min y 2500/día (propio) + 60/min y 5000/día (compartido con el negocio)

curl -X POST https://api.kharyo.com/v1/notifications/whatsapp \
  -H "Authorization: Bearer kh_xxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"message": "Nuevo comprobante recibido por $45,00"}'
GET/v1/notification-targetstargets:read

Lista los destinos verificados y habilitados para la API (chat_id enmascarado).

Conciliación

Registro y consulta de cobros, y cierre de caja diario.

POST/v1/payment-claimsclaims:write

Registra un cobro esperado para que el conciliador lo cruce contra el banco. Soporta Idempotency-Key.

Límite propio: 30/min

curl -X POST https://api.kharyo.com/v1/payment-claims \
  -H "Authorization: Bearer kh_xxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 8f14e45f-ceea-467e-add3-8e0a1c0f5f3a" \
  -d '{"amount": 250.50, "currency": "VES", "reference": "987654321"}'
GET/v1/payment-claimsclaims:read

Lista los cobros del espacio de trabajo, con filtros por estado, referencia y fecha.

GET/v1/payment-claims/{id}claims:read

Consulta un cobro por id.

GET/v1/reconciliation/daily-closereconciliation:read

Cierre de caja de un día (mismos números que la pantalla "Hoy" del conciliador).

Uso

Consumo del propio espacio de trabajo.

GET/v1/usageusage:read

Tokens y ejecuciones del mes, más el conteo de requests a esta API.

curl https://api.kharyo.com/v1/usage \
  -H "Authorization: Bearer kh_xxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Bandeja de entrada

Conversaciones, mensajes y plantillas de WhatsApp.

GET/v1/inbox/conversationsinbox:read

Lista conversaciones, con filtros por estado, canal y última actualización.

GET/v1/inbox/conversations/{id}inbox:read

Consulta una conversación.

GET/v1/inbox/conversations/{id}/messagesinbox:read

Lista los mensajes de una conversación (paginación sin links, ver más abajo).

POST/v1/inbox/conversations/{id}/messagesinbox:write

Envía un mensaje manual en nombre del negocio. Soporta client_id para reintentos seguros.

Límite propio: 30/min (compartido con .../template)

curl -X POST https://api.kharyo.com/v1/inbox/conversations/{id}/messages \
  -H "Authorization: Bearer kh_xxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"content": "Hola, tenemos tu pedido listo."}'
POST/v1/inbox/conversations/{id}/templateinbox:write

Envía una plantilla de WhatsApp ya aprobada, sustituyendo variables (template_id es ULID; variables es una lista posicional; media.link para headers de imagen/video/documento). Solo en conversaciones de WhatsApp.

Límite propio: 30/min (compartido con .../messages)

curl -X POST https://api.kharyo.com/v1/inbox/conversations/{id}/template \
  -H "Authorization: Bearer kh_xxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"template_id": "01J9Z8Q3K7N4M2P5R6S8T0V1W3", "variables": ["Juan", "#123"]}'
POST/v1/inbox/conversations/{id}/modeinbox:write

Pausa o reactiva la IA de una conversación (active | paused).

GET/v1/inbox/templatesinbox:read

Lista las plantillas de WhatsApp aprobadas del espacio de trabajo (proyección mínima; ver /v1/templates para gestión completa).

Plantillas de WhatsApp

Gestión completa de plantillas: listar, crear, sincronizar con Meta y borrar.

GET/v1/templatestemplates:read

Lista paginada de plantillas del espacio de trabajo, con filtros por waba_id, status, language y category.

GET/v1/templates/wabastemplates:read

Lista los WABA (WhatsApp Business Account) conectados del espacio de trabajo.

POST/v1/templatestemplates:write

Crea una plantilla y la envía a aprobación de Meta. 409 si ya existe una con el mismo (waba_id, name, language); 502 si Meta la rechaza.

curl -X POST https://api.kharyo.com/v1/templates \
  -H "Authorization: Bearer kh_xxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"waba_id": "123456789012345", "name": "order_confirmation", "language": "es", "category": "UTILITY", "components": [{"type": "BODY", "text": "Hola {{1}}, tu pedido {{2}} está confirmado."}]}'
POST/v1/templates/synctemplates:write

Encola una sincronización con Meta del estado de las plantillas de un WABA (aprobación, calidad, rechazo). Devuelve 202.

GET/v1/templates/{id}templates:read

Consulta una plantilla por su id (ULID).

DELETE/v1/templates/{id}templates:write

Borra una plantilla en Meta y localmente. 502 si Meta rechaza el borrado.

Webhooks

Gestión de suscripciones a webhooks salientes: eventos que Kharyo envía a tu propio endpoint.

POST/v1/webhookswebhooks:manage

Crea una suscripción de webhook (hasta 5 activas). El secreto solo se muestra en esta respuesta.

curl -X POST https://api.kharyo.com/v1/webhooks \
  -H "Authorization: Bearer kh_xxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/webhooks/kharyo", "events": ["payment_claim.matched", "lead.captured"]}'
GET/v1/webhookswebhooks:manage

Lista las suscripciones del espacio de trabajo.

DELETE/v1/webhooks/{id}webhooks:manage

Elimina una suscripción y su historial de entregas. Única forma de rotar el secreto.

POST/v1/webhooks/{id}/testwebhooks:manage

Envía un evento ping de prueba, firmado igual que una entrega real.

GET/v1/webhooks/{id}/deliverieswebhooks:manage

Lista los últimos 50 intentos de entrega, sin el cuerpo enviado.

La especificación completa (OpenAPI 3.1), con todos los schemas de request/response, vive en un solo archivo descargable.

Descargar especificación OpenAPI