REST · HTTPS · JSON

API financiera externa de Kanan

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.

Base URL https://api.kanan.app/v1/financial
Estado: Implementada en producción Versión: API v1.1 compatible Formato: JSON Autenticación: Bearer Última revisión: 2026-09-04

Inicio rápido

Primera consulta autenticada

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.

Consultar el contexto efectivo
curl "https://api.kanan.app/v1/financial/context" \
  --header "Authorization: Bearer <KNI_LIVE_SECRET>" \
  --header "Accept: application/json"
Respuesta esperada.

HTTP 200, un activeTrackerId y principal.type = integration_client. Todas las operaciones posteriores se resuelven contra ese tracker activo.

Autenticación y permisos

Dos tipos de credencial, un mismo encabezado

Usuario

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.

Automatización

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

Scopes disponibles

ScopeCapacidad
context:readConsultar tracker y principal efectivos.
context:writeCambiar tracker activo o predeterminado.
trackers:readListar trackers visibles y autorizados.
catalog:readListar categorías.
catalog:write:trackerCrear categorías del espacio.
catalog:write:userCrear categorías globales del usuario.
accounts:readListar cuentas y cajas.
balances:readConsultar saldos por cuenta y caja.
budgets:readConsultar presupuestos.
budgets:writeCrear presupuestos y partidas.
movements:readConsultar y filtrar movimientos.
movements:writeCrear y corregir gastos o ingresos.
transfers:writeCrear transferencias entre cuentas/cajas.
Administración de credenciales.

/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

Selecciona el tracker de cada solicitud

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.

1

Autenticar

La API valida el JWT o el secreto y recupera sus restricciones.

2

Resolver

Usa el header explícito; el fallback heredado continúa disponible.

3

Autorizar

Comprueba scopes, membresía y lista de trackers permitidos.

4

Operar

Ejecuta con RLS y devuelve el tracker efectivo en meta.

Cambiar el tracker activo de la credencial
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

Endpoints implementados en v1

GET/context

Contexto, principal y tracker efectivos.

context:read
PUT/context/active-tracker

Cambia el tracker activo del principal.

context:write
PUT/context/default-tracker

Cambia el tracker predeterminado.

context:write
GET/trackers

Lista trackers visibles y autorizados.

trackers:read
GET/categories

Lista categorías activas del tracker.

catalog:read
GET/categories/{categoryId}

Consulta una categoría visible para el tracker.

catalog:read
POST/categories

Crea una categoría del tracker o global del usuario.

catalog:write:*
GET/accounts

Lista cuentas financieras activas.

accounts:read
GET/accounts/{accountId}/cashboxes

Lista cajas activas de una cuenta.

accounts:read
GET/balances

Saldos actuales por cuenta y caja.

balances:read
GET/budgets?mode=&status=

Presupuestos del tracker con filtros opcionales.

budgets:read
GET/budgets/{budgetId}

Detalle, partidas, resumen y ejecución del presupuesto.

budgets:read
POST/budgets

Crea un presupuesto de seguimiento o planeación.

budgets:write
POST/budgets/{budgetId}/lines

Agrega una partida a un presupuesto.

budgets:write
GET/movements

Lista paginada con filtros combinables.

movements:read
POST/movements

Registra un gasto o ingreso idempotente.

movements:write
GET/movements/{movementId}

Obtiene un movimiento y su encabezado ETag.

movements:read
PATCH/movements/{movementId}

Corrige monto, categoría, descripción o comercio.

movements:write
POST/transfers

Crea una transferencia entre cuentas/cajas.

transfers:write
GET/transfers/{groupId}

Consulta ambos movimientos de una transferencia.

movements:read
GET/api-keys

Lista credenciales seguras y heredadas del usuario.

solo JWT
POST/api-keys

Crea una clave segura y muestra el secreto una vez.

JWT + TOTP
POST/api-keys/{clientId}/rotate

Crea un reemplazo sin revocar automáticamente el anterior.

JWT + TOTP
DELETE/api-keys/{clientId}

Revoca inmediatamente una credencial activa.

solo JWT
GET/integration-clients

Lista credenciales del usuario autenticado.

solo JWT
POST/integration-clients

Crea una credencial y muestra el secreto una vez.

solo JWT
DELETE/integration-clients/{clientId}

Revoca una credencial activa.

solo JWT

Gastos e ingresos

Registrar un movimiento

Campos obligatorios

  • type obligatorioexpense o income.
  • amountDecimal obligatorio — importe decimal positivo como string.
  • accountId obligatorio — UUID de la cuenta de pago/cobro.

Resolución automática

  • 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.
Crear un gasto
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"
  }'
Importes: diferencia respecto al RPC anterior.

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.

Consultar con filtros

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.

Gastos de dos categorías durante julio
GET /movements?type=expense&categoryIds=<ID_1>,<ID_2>&from=2026-07-01&to=2026-07-31&limit=50

Corregir un movimiento

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.

Corregir monto, categoría, descripción o comercio
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

Mover saldo entre cuentas y cajas

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.

Crear una transferencia
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"
  }'
Atomicidad.

La transferencia se registra como una sola operación de dominio. No reemplaces este endpoint por dos movimientos independientes.

Administración segura

Crear, limitar, rotar y revocar API keys

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.

Espacios seleccionados

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.

Todos los administrados

La clave cubre los trackers actuales y futuros donde el usuario sea owner o admin. Si pierde ese rol, el acceso desaparece automáticamente.

Creación y rotación

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.

Vigencia y mínimo privilegio

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.

Crear una clave limitada a dos espacios
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>"]
    }
  }'
Respuesta sensible.

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

Configuración recomendada en n8n

Credencial

Guarda el secreto como credencial de encabezado, no como texto dentro del nodo exportable.

  • Nombre: Authorization
  • Valor: Bearer <KNI_LIVE_SECRET>

HTTP Request

Usa JSON, la URL de /movements y un Idempotency-Key único pero estable por item.

  • Content-Type: application/json
  • Idempotency-Key: n8n-{{ $execution.id }}-{{ $itemIndex }}

Mapeo desde el formato anterior

Campo anteriorCampo API v1Transformación
tipo_movimientotypeexpense o income.
montoamountDecimalSi está en centavos: ($json.monto / 100).toFixed(2).
id_cuenta_pagoaccountIdUUID sin cambio.
categoria_idcategoryIdEnviar como string; puede omitirse.
fechaoccurredOnFecha ISO YYYY-MM-DD.
descripciondescriptionTexto opcional, máximo 500 caracteres.
nombre_comerciomerchantNameTexto opcional, máximo 200 caracteres.
currency_codecurrencyCodeCódigo ISO de tres letras.
$execution.idIdempotency-KeyCombinar también el índice del item.
Body JSON como expresiones de n8n
{
  "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 }}"
}
No conectes n8n directamente a PostgreSQL.

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

Envoltorios consistentes y trazables

Éxito

Envelope estándar
{
  "data": {},
  "meta": {
    "apiVersion": "1",
    "requestId": "<UUID>",
    "trackerId": "<TRACKER_UUID>"
  }
}

Error

Envelope de error
{
  "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.

HTTPSignificadoAcción del cliente
200Consulta, edición o replay exitoso.Procesar data.
201Recurso creado.Guardar ID y versión devueltos.
204Credencial revocada.No esperar body.
400JSON, campos, filtros o headers inválidos.Corregir la solicitud; no reintentar igual.
401Credencial ausente, inválida, vencida o revocada.Renovar o reemplazar la credencial.
403Scope o tracker no autorizado.No reintentar sin cambiar permisos.
404Recurso no visible en el tracker activo.Verificar contexto e identificador.
409Conflicto de versión o idempotencia.Reconsultar; no inventar una nueva clave para ocultarlo.
429Límite de solicitudes.Esperar el encabezado Retry-After.
500Error interno.Reintento acotado y reporte con requestId.

Reglas operativas

Garantías que el cliente debe conservar

Secretos

Guardar en el gestor de credenciales del cliente. Rotar o revocar ante sospecha. El secreto de integración solo se muestra al crearlo.

Mínimo privilegio

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.

Idempotencia

Reutilizar la misma clave al reintentar la misma intención. Cambiarla crea una operación distinta y puede duplicar un movimiento.

Trazabilidad

Registrar meta.requestId, estado HTTP y resultado, nunca el bearer token ni datos financieros innecesarios.