JWT de sesión
Se usa desde la aplicación web y para administrar credenciales de integración. Hereda el acceso real del usuario y caduca con la sesión de Supabase Auth.
REST · HTTPS · JSON
Superficie HTTP autorizada para consultar información financiera y registrar gastos, ingresos, transferencias, presupuestos y categorías. Es la vía recomendada para n8n y futuros canales como WhatsApp, Telegram o un agente financiero.
https://api.kanan.app/v1/financial
Inicio rápido
Usa el secreto de integración guardado como credencial en tu cliente. El prefijo
del secreto es kni_live_; nunca lo incluyas en documentación, logs o
exportaciones públicas del workflow.
curl "https://api.kanan.app/v1/financial/context" \
--header "Authorization: Bearer <KNI_LIVE_SECRET>" \
--header "Accept: application/json"
HTTP 200, un activeTrackerId y
principal.type = integration_client. Todas las operaciones
posteriores se resuelven contra ese tracker activo.
Autenticación y permisos
Se usa desde la aplicación web y para administrar credenciales de integración. Hereda el acceso real del usuario y caduca con la sesión de Supabase Auth.
kni_live_*Se usa en n8n o servicios de confianza. Tiene scopes, trackers permitidos, tracker predeterminado y vencimiento configurables.
| Encabezado | Cuándo | Regla |
|---|---|---|
Authorization: Bearer … |
Todas las solicitudes | JWT de usuario o secreto de integración. |
Content-Type: application/json |
Solicitudes con body | El body debe ser JSON válido y no exceder 64 KiB. |
X-Kanan-Tracker-Id |
Operaciones ligadas a un espacio | Obligatorio para claves seguras con más de un tracker elegible. |
Idempotency-Key |
POST y PATCH financieros | Obligatorio, estable por operación y máximo 200 caracteres. |
If-Match |
PATCH de movimiento | Debe contener el ETag obtenido al consultar el movimiento. |
| Scope | Capacidad |
|---|---|
context:read | Consultar tracker y principal efectivos. |
context:write | Cambiar tracker activo o predeterminado. |
trackers:read | Listar trackers visibles y autorizados. |
catalog:read | Listar categorías. |
catalog:write:tracker | Crear categorías del espacio. |
catalog:write:user | Crear categorías globales del usuario. |
accounts:read | Listar cuentas y cajas. |
balances:read | Consultar saldos por cuenta y caja. |
budgets:read | Consultar presupuestos. |
budgets:write | Crear presupuestos y partidas. |
movements:read | Consultar y filtrar movimientos. |
movements:write | Crear y corregir gastos o ingresos. |
transfers:write | Crear transferencias entre cuentas/cajas. |
/api-keys solo acepta un JWT de usuario. Crear o rotar exige TOTP
reciente y sesión activa. Una credencial de integración no puede administrar claves.
Contexto financiero
Envía X-Kanan-Tracker-Id en cada operación ligada a un espacio.
Las claves nuevas solo aceptan trackers seleccionados o espacios actuales y futuros
donde su propietario sea owner/admin. JWT y claves heredadas conservan el contexto v1.
La API valida el JWT o el secreto y recupera sus restricciones.
Usa el header explícito; el fallback heredado continúa disponible.
Comprueba scopes, membresía y lista de trackers permitidos.
Ejecuta con RLS y devuelve el tracker efectivo en meta.
curl --request PUT \
"https://api.kanan.app/v1/financial/context/active-tracker" \
--header "Authorization: Bearer <KNI_LIVE_SECRET>" \
--header "Content-Type: application/json" \
--data '{"trackerId":"<TRACKER_UUID>"}'
La ruta de contexto activo es compatible con v1. Para claves nuevas con varios espacios,
usa X-Kanan-Tracker-Id y evita estado compartido entre ejecuciones concurrentes.
Catálogo HTTP
/context
Contexto, principal y tracker efectivos.
context:read/context/active-tracker
Cambia el tracker activo del principal.
context:write/context/default-tracker
Cambia el tracker predeterminado.
context:write/trackers
Lista trackers visibles y autorizados.
trackers:read/categories
Lista categorías activas del tracker.
catalog:read/categories/{categoryId}
Consulta una categoría visible para el tracker.
catalog:read/categories
Crea una categoría del tracker o global del usuario.
catalog:write:*/accounts
Lista cuentas financieras activas.
accounts:read/accounts/{accountId}/cashboxes
Lista cajas activas de una cuenta.
accounts:read/balances
Saldos actuales por cuenta y caja.
balances:read/budgets?mode=&status=
Presupuestos del tracker con filtros opcionales.
budgets:read/budgets/{budgetId}
Detalle, partidas, resumen y ejecución del presupuesto.
budgets:read/budgets
Crea un presupuesto de seguimiento o planeación.
budgets:write/budgets/{budgetId}/lines
Agrega una partida a un presupuesto.
budgets:write/movements
Lista paginada con filtros combinables.
movements:read/movements
Registra un gasto o ingreso idempotente.
movements:write/movements/{movementId}
Obtiene un movimiento y su encabezado ETag.
movements:read/movements/{movementId}
Corrige monto, categoría, descripción o comercio.
movements:write/transfers
Crea una transferencia entre cuentas/cajas.
transfers:write/transfers/{groupId}
Consulta ambos movimientos de una transferencia.
movements:read/api-keys
Lista credenciales seguras y heredadas del usuario.
solo JWT/api-keys
Crea una clave segura y muestra el secreto una vez.
JWT + TOTP/api-keys/{clientId}/rotate
Crea un reemplazo sin revocar automáticamente el anterior.
JWT + TOTP/api-keys/{clientId}
Revoca inmediatamente una credencial activa.
solo JWT/integration-clients
Lista credenciales del usuario autenticado.
solo JWT/integration-clients
Crea una credencial y muestra el secreto una vez.
solo JWT/integration-clients/{clientId}
Revoca una credencial activa.
solo JWTGastos e ingresos
type obligatorio — expense o income.amountDecimal obligatorio — importe decimal positivo como string.accountId obligatorio — UUID de la cuenta de pago/cobro.categoryId opcional — sin valor usa la categoría del sistema “Desconocido”.cashboxId condicional — puede omitirse si la cuenta tiene una sola caja elegible.occurredOn opcional — sin valor usa la fecha local del tracker.curl --request POST \
"https://api.kanan.app/v1/financial/movements" \
--header "Authorization: Bearer <KNI_LIVE_SECRET>" \
--header "Content-Type: application/json" \
--header "Idempotency-Key: n8n-expense-<EXECUTION_ID>-<ITEM_INDEX>" \
--data '{
"type": "expense",
"amountDecimal": "36.00",
"accountId": "<ACCOUNT_UUID>",
"categoryId": "<CATEGORY_ID>",
"currencyCode": "MXN",
"occurredOn": "2026-07-30",
"description": "Traslado desde la escuela hasta casa",
"merchantName": "Didi"
}'
La API REST recibe amountDecimal. Si el flujo anterior enviaba
monto: 3600 como unidades menores, debe convertirlo a
"36.00"; no envíes "3600" salvo que realmente sean
MXN 3,600.00.
GET /movements acepta type, categoryId,
categoryIds (máximo cinco IDs separados por coma),
accountId, cashboxId, from,
to, limit y offset. No combines
categoryId con categoryIds.
GET /movements?type=expense&categoryIds=<ID_1>,<ID_2>&from=2026-07-01&to=2026-07-31&limit=50
Primero consulta GET /movements/{movementId} y conserva el
ETag de respuesta. Después envíalo como If-Match. Esta
concurrencia optimista evita sobrescribir cambios más recientes.
curl --request PATCH \
"https://api.kanan.app/v1/financial/movements/<MOVEMENT_UUID>" \
--header "Authorization: Bearer <KNI_LIVE_SECRET>" \
--header "Content-Type: application/json" \
--header "Idempotency-Key: correction-<UNIQUE_ID>" \
--header 'If-Match: "<ETAG_VALUE>"' \
--data '{
"amountDecimal": "42.50",
"categoryId": "<CATEGORY_ID>",
"description": "Descripción corregida",
"merchantName": "Comercio corregido"
}'
Enviar categoryId: null reasigna el movimiento a la categoría
“Desconocido”. Las transferencias no se editan como movimientos individuales.
Transferencias
Cuenta origen y cuenta destino son obligatorias. Cada caja puede omitirse únicamente cuando su cuenta correspondiente tiene una sola caja activa elegible; de lo contrario, la API rechazará la solicitud y pedirá selección explícita.
curl --request POST \
"https://api.kanan.app/v1/financial/transfers" \
--header "Authorization: Bearer <KNI_LIVE_SECRET>" \
--header "Content-Type: application/json" \
--header "Idempotency-Key: n8n-transfer-<EXECUTION_ID>-<ITEM_INDEX>" \
--data '{
"amountDecimal": "500.00",
"fromAccountId": "<ORIGIN_ACCOUNT_UUID>",
"fromCashboxId": "<ORIGIN_CASHBOX_UUID>",
"toAccountId": "<DESTINATION_ACCOUNT_UUID>",
"toCashboxId": "<DESTINATION_CASHBOX_UUID>",
"currencyCode": "MXN",
"occurredOn": "2026-08-01",
"description": "Fondeo de efectivo"
}'
La transferencia se registra como una sola operación de dominio. No reemplaces este endpoint por dos movimientos independientes.
Administración segura
El usuario administra sus claves desde Settings. El secreto se entrega una sola vez; Kanan conserva únicamente su hash SHA-256 y los últimos cuatro caracteres para identificarlo sin poder recuperarlo.
La clave queda limitada a uno o varios trackers actuales donde el usuario es owner o admin. La autorización vuelve a comprobar la membresía en cada solicitud.
La clave cubre los trackers actuales y futuros donde el usuario sea owner o admin. Si pierde ese rol, el acceso desaparece automáticamente.
Exigen sesión remota activa y un TOTP reciente. La rotación deja ambas claves activas para permitir un cambio controlado; después se revoca la anterior.
La vigencia predeterminada es de 90 días y nunca supera el límite configurado por la plataforma. Concede solo los scopes que realmente necesita la integración.
curl --request POST \
"https://api.kanan.app/v1/financial/api-keys" \
--header "Authorization: Bearer <USER_JWT_AAL2>" \
--header "Content-Type: application/json" \
--data '{
"name": "Automatización contable",
"scopes": ["balances:read", "movements:write", "catalog:read"],
"access": {
"mode": "selected",
"trackerIds": ["<TRACKER_UUID_1>", "<TRACKER_UUID_2>"]
}
}'
Copia data.secret inmediatamente a un gestor de secretos. No lo envíes
por chat, no lo guardes en logs y no incluyas respuestas de creación en telemetría.
Receta de integración
Guarda el secreto como credencial de encabezado, no como texto dentro del nodo exportable.
AuthorizationBearer <KNI_LIVE_SECRET>
Usa JSON, la URL de /movements y un
Idempotency-Key único pero estable por item.
Content-Type: application/jsonIdempotency-Key: n8n-{{ $execution.id }}-{{ $itemIndex }}| Campo anterior | Campo API v1 | Transformación |
|---|---|---|
tipo_movimiento | type | expense o income. |
monto | amountDecimal | Si está en centavos: ($json.monto / 100).toFixed(2). |
id_cuenta_pago | accountId | UUID sin cambio. |
categoria_id | categoryId | Enviar como string; puede omitirse. |
fecha | occurredOn | Fecha ISO YYYY-MM-DD. |
descripcion | description | Texto opcional, máximo 500 caracteres. |
nombre_comercio | merchantName | Texto opcional, máximo 200 caracteres. |
currency_code | currencyCode | Código ISO de tres letras. |
$execution.id | Idempotency-Key | Combinar también el índice del item. |
{
"type": "={{ $json.tipo_movimiento }}",
"amountDecimal": "={{ ($json.monto / 100).toFixed(2) }}",
"accountId": "={{ $json.id_cuenta_pago }}",
"categoryId": "={{ $json.categoria_id ? String($json.categoria_id) : undefined }}",
"currencyCode": "={{ $json.currency_code }}",
"occurredOn": "={{ $json.fecha }}",
"description": "={{ $json.descripcion }}",
"merchantName": "={{ $json.nombre_comercio }}"
}
La API conserva RLS, validaciones, tracker activo, idempotencia, auditoría y reglas de dominio. Una credencial SQL remota eludiría parte de esas garantías y ampliaría innecesariamente el impacto de una filtración.
Contrato de respuesta
{
"data": {},
"meta": {
"apiVersion": "1",
"requestId": "<UUID>",
"trackerId": "<TRACKER_UUID>"
}
}
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Solicitud inválida",
"details": {}
},
"meta": {
"apiVersion": "1",
"requestId": "<UUID>"
}
}
Las listas de movimientos agregan meta.pagination. Las escrituras
idempotentes agregan meta.idempotentReplay; una repetición válida
devuelve el recurso previo sin duplicarlo.
| HTTP | Significado | Acción del cliente |
|---|---|---|
200 | Consulta, edición o replay exitoso. | Procesar data. |
201 | Recurso creado. | Guardar ID y versión devueltos. |
204 | Credencial revocada. | No esperar body. |
400 | JSON, campos, filtros o headers inválidos. | Corregir la solicitud; no reintentar igual. |
401 | Credencial ausente, inválida, vencida o revocada. | Renovar o reemplazar la credencial. |
403 | Scope o tracker no autorizado. | No reintentar sin cambiar permisos. |
404 | Recurso no visible en el tracker activo. | Verificar contexto e identificador. |
409 | Conflicto de versión o idempotencia. | Reconsultar; no inventar una nueva clave para ocultarlo. |
429 | Límite de solicitudes. | Esperar el encabezado Retry-After. |
500 | Error interno. | Reintento acotado y reporte con requestId. |
Reglas operativas
Guardar en el gestor de credenciales del cliente. Rotar o revocar ante sospecha. El secreto de integración solo se muestra al crearlo.
Limitar scopes, trackers y vencimiento según el uso real. Una integración de captura no necesita administrar contexto si su tracker ya es fijo.
Reutilizar la misma clave al reintentar la misma intención. Cambiarla crea una operación distinta y puede duplicar un movimiento.
Registrar meta.requestId, estado HTTP y resultado, nunca el
bearer token ni datos financieros innecesarios.
Agentes: consulte /llms.txt y /.well-known/api-catalog para descubrir el contrato.