Skip to content

Importar/migrar un crédito existente​

POST
/import/receivable

Importa (migra) hacia Kuenta un crédito que ya existe en un sistema externo, junto con
el deudor y su historial de pagos. Está pensado para migraciones masivas de cartera:
el endpoint resuelve o crea la entidad del deudor, copia su perfil, crea el crédito y
ejecuta la amortización aplicando los pagos históricos que se envíen.

A diferencia de POST /receivables, aquí no se dispara análisis de crédito: el crédito
entra directamente con el status que indique el llamante y con la fecha de originación
(startedAt) del sistema de origen, de modo que la edad de cartera y la mora se calculen
como en el sistema legado.

El crédito importado queda marcado con source = "import-cli".


⚠️ Advertencias que debe leer antes de integrar

1. rate es un PORCENTAJE (EA), no una fracción

El valor recibido se divide entre 100 internamente
(parseRate en receivable_service.go). Es decir:

Envía Kuenta almacena Significa
"25.0452604625315" 0.250452604625315 25,045 % EA ≈ 1,88 % mensual
"0.25" (rechazado) Se interpretaría como 0,25 % EA; el guard de escala lo rechaza con 412

Enviar "0.25" con la intención de "25 %" ya NO produce un crédito sin intereses:
un guard de escala rechaza con 412 ValidationFailed toda tasa resultante fuera del
rango [0,5 % ; 200 %] EA, precisamente para atajar este error de unidad. Ojo con el
borde: "0.5" (queriendo 50 %) cae exactamente en el mínimo y sí se acepta como
0,5 % EA; toda tasa por debajo de 5 % EA queda trazada en el log del servidor para
poder auditar la migración después.

"0" sí se acepta: un crédito sin intereses es legítimo. La única excepción es una
línea de crédito con cambios de tasa habilitados, donde se rechaza con
412 ValidationFailed: el motor de amortización interpreta un techo de tasa en cero
como "sin definir" y caería al techo de la línea, así que un 0 % ahí no se puede
representar sin que mute solo.
Verifique siempre la escala contra la cuota esperada antes de importar en lote.

Además, parseRate elimina el signo % y convierte la coma decimal en punto. Por eso
un separador de miles se malinterpreta en silencio: "1,234" se lee como 1.234
(⇒ 0,01234 EA), no como 1234. Nunca use separador de miles.

2. La tasa de mora (debtRate) por defecto sale de la línea de crédito

debtRate es opcional y se expresa en la misma unidad que rate (porcentaje EA
como cadena). Se resuelve con el mismo modelo que la originación normal: el valor del
payload es un override y, si no viene, manda la tasa de mora configurada en la línea de
crédito
. rate y debtRate son independientes: ninguno se deriva del otro.

Sólo si la línea no tiene mora configurada (cero) se cae a la tasa corriente del propio
crédito, para no dejar el crédito con una mora en cero (que el motor de amortización
interpreta como "sin definir").

Sobre el default no se aplica el tope de usura: la importación reconstruye historia,
y recortar un crédito antiguo contra la usura vigente hoy alteraría el dato migrado.

Si la cartera de origen maneja una mora por crédito distinta de la de la línea,
envíela explícitamente.

3. Las tasas quedan fijadas en el crédito, no se recalculan contra la línea

Además de Rate y DebtRate, el crédito importado guarda sus topes
(CurrentRate, MaxRate, CurrentDebtRate, MaxDebtRate) con la tasa importada.

Esto importa porque el motor de amortización trata un tope en cero como
"sin definir" y, en ese caso, recalcula contra la tasa configurada en la línea de
crédito. Al fijarlos explícitamente, la tasa que se ve en la ficha del crédito, en los
reportes y en los exportes es la que se importó, y no la de la línea.

Es la misma razón por la que un rate de 0 sólo se rechaza sobre líneas con cambios
de tasa habilitados (ver advertencia 1): ahí el cero sería ambiguo.

4. Idempotencia por externalID

externalID es la llave de idempotencia. Si ya existe un crédito importado con ese
externalID para la organización autenticada, no se crea un duplicado: se reutiliza
el crédito existente. Reintentar la misma petición es seguro.

La aplicación de payments también es idempotente: cada pago recibe un identificador
determinístico derivado de (organización acreedora, externalID, paymentDate,
paymentValue, ordinal dentro de ese grupo de fecha+monto)
, así que reintentar no
vuelve a abonar los mismos pagos. El ordinal es lo que permite que dos pagos legítimos
del mismo día por el mismo valor se apliquen ambos
en vez de confundirse con un
duplicado. Ese identificador es compartido con la importación de pagos por CSV, de modo
que un pago cargado por ambas vías se deduplica.

5. Orden de los pagos

El orden del arreglo no importa: los payments se ordenan internamente por
paymentDate ascendente antes de aplicarse, porque el orden de aplicación afecta el
reparto entre capital, interés y mora. Enviarlos desordenados produce el mismo resultado
que enviarlos ordenados.

6. profile es la única forma de enviar la identidad del deudor

Los campos planos name, lastName y email en la raíz del payload
(contrato anterior a 2026-01) ya no se aceptan: la petición se rechaza con
412 ValidationFailed indicando que use profile.firstName, profile.lastName y
profile.email. Vea la sección Campos rechazados o ignorados más abajo.

7. Costos externos opcionales (costs)

costs usa exactamente el mismo formato que POST /payment-links/external.
Si se envía, reemplaza por completo los costos de la línea para este crédito y queda
guardado en el crédito: pagos, mora, activación y cambios de condiciones posteriores
siguen usando esos costos. Si se omite o va vacío, se usan los costos de la línea, como
hasta ahora. Un costo inválido rechaza toda la importación con 412 ValidationFailed
y no se crea nada.

8. creditLineID siempre resuelve a la versión vigente de la línea

Al editar una línea se crea una versión nueva y la anterior queda archivada. Si se envía
el ID de cualquier versión (la original, una intermedia o la vigente), el crédito se crea
sobre la versión vigente, así que no hace falta actualizar el payload después de cada
edición. Las versiones creadas por contraofertas nunca se eligen.


Resolución del deudor

phone es la llave con la que se resuelve el deudor. Se normaliza internamente a
formato E.164 con + y se busca una entidad natural con ese teléfono:

  1. El teléfono ya existe ⇒ se reutiliza esa entidad. profile es opcional.
  2. El teléfono no existe, pero el documento sí ⇒ se reutiliza la entidad hallada por
    (documentType, document). No se crea un deudor duplicado y profile sigue
    siendo opcional. La relación con esta organización se crea si no existía.
  3. No resuelve ni por teléfono ni por documento ⇒ se registra un deudor nuevo.
    Sólo en este caso profile es obligatorio, y lo único exigido es
    profile.firstName y profile.lastName, con mínimo 2 caracteres cada uno;
    de lo contrario se responde 412 ValidationFailed.

profile.email, profile.idNumber y profile.idType no son obligatorios, aunque
enviarlos es muy recomendable: sin email el usuario queda sin correo de contacto, y
sin documento el deudor no se puede reutilizar por documento en importaciones futuras.

El documento y su tipo se resuelven campo por campo: cada uno se toma de la raíz
(document / documentType) y, si la raíz no lo trae, de profile.idNumber /
profile.idType. Mandar el número en la raíz y el tipo dentro de profile es válido. Si el documento resuelto no trae un tipo
válido se responde 412 ValidationFailed. Al crear una entidad nueva —o al completar
una existente que no tenía documento— se escribe sobre la entidad; si la entidad ya
tiene un documento distinto, no se sobrescribe.

Campos rechazados o ignorados

Campo Comportamiento
name, lastName, email (raíz) Rechazados con 412 ValidationFailed. Eran el contrato anterior a 2026-01. Use profile.firstName, profile.lastName, profile.email.
entityID Ignorado. Siempre se sobrescribe con la organización de la sesión autenticada (data.EntityID = session.Role.EntityID). Enviarlo no tiene efecto.
frequency Se guarda tal cual viene, pero puede no ser la que se aplica. El valor enviado queda en el campo paymentFrequency del crédito; sin embargo, la amortización usa la frecuencia de la línea cuando ésta tiene exactamente una configurada, y el plazo total cuando la línea es de período único. Es obligatorio y debe ser mayor que cero: alimenta un divisor en el cálculo de cuotas.
status Los estados previos al desembolso (0, 1, 2, 5, 13, 14) se respetan. Si se omite o se envía cualquier otro, el motor de amortización (setGlobalStatus) lo recalcula — por ejemplo, un crédito con cuotas vencidas termina en 10 (MORA).

Hay tres formas de importar, y comparten las reglas

Vía Cómo se usa Formato de fecha
Este endpoint REST Un crédito por petición, con sus pagos en el mismo payload YYYY-MM-DD
Carga masiva por CSV Procesos por lotes "Importar Créditos" e "Importar Pagos" YYYY-MM-DD
CLI (import-credits / import-payments) Operación interna dd/mm/aaaa

Las reglas descritas arriba —contrato de identidad, resolución del deudor, escala de
tasas, default de mora desde la línea, startedAt estricto y topes de tasa— rigen en
las tres
. Dos consecuencias prácticas:

  • La idempotencia de pagos cruza las tres vías. El identificador determinístico se
    calcula sobre la fecha ya normalizada, así que un pago cargado por CSV y re-enviado en
    un payload REST (o al revés) no se abona dos veces, pese a que los formatos de
    fecha de entrada sean distintos.
  • En el CSV, las columnas planas sí construyen la identidad (Nombre, Apellido,
    Correo, Documento), y la columna Profile con un JSON anidado las sobreescribe
    campo por campo. Es la única diferencia deliberada con este endpoint: en una hoja de
    cálculo las columnas son el canal natural, mientras que en un JSON el objeto anidado
    ya existe y tener dos canales compitiendo fue justamente el origen del problema.

Limitación conocida del CSV de pagos: cada fila se procesa aislada, así que dos filas
con la misma fecha y el mismo monto para el mismo crédito se deduplican entre sí
. Si la
cartera trae pagos legítimos repetidos el mismo día, impórtelos por este endpoint, que sí
los distingue.

Permisos requeridos

  • write:receivables

Encabezados obligatorios

  • Config-Organization-ID
  • Organization-ID

Authorizations​

test

OAuth 2.0 Client Credentials Grant (RFC 6749 §4.4).
Utilice este flow para autenticación de aplicación-a-aplicación sin intervención del usuario.
Ideal para servicios backend y integración entre sistemas.

clientCredentials Flow
Token URLhttps://test-api.kuenta.co/v1/oauth/token
Scopes:
  • *Acceso completo (administrador)
  • users:readLectura de usuarios
  • users:writeEscritura de usuarios
  • users.info:writeEscritura de información de usuarios
  • clients:readLectura de clientes OAuth
  • clients:writeEscritura de clientes OAuth
  • verifications:writeEscritura de verificaciones
  • passwords:writeEscritura de contraseñas
  • keys:readLectura de claves
  • settings:readLectura de configuraciones
  • settings:writeEscritura de configuraciones
or
stage

OAuth 2.0 Client Credentials Grant (RFC 6749 §4.4).
Utilice este flow para autenticación de aplicación-a-aplicación sin intervención del usuario.
Ideal para servicios backend y integración entre sistemas.

clientCredentials Flow
Token URLhttps://stage-api.kuenta.co/v1/oauth/token
Scopes:
  • *Acceso completo (administrador)
  • users:readLectura de usuarios
  • users:writeEscritura de usuarios
  • users.info:writeEscritura de información de usuarios
  • clients:readLectura de clientes OAuth
  • clients:writeEscritura de clientes OAuth
  • verifications:writeEscritura de verificaciones
  • passwords:writeEscritura de contraseñas
  • keys:readLectura de claves
  • settings:readLectura de configuraciones
  • settings:writeEscritura de configuraciones
or
demo

OAuth 2.0 Client Credentials Grant (RFC 6749 §4.4).
Utilice este flow para autenticación de aplicación-a-aplicación sin intervención del usuario.
Ideal para servicios backend y integración entre sistemas.

clientCredentials Flow
Token URLhttps://demo-api.kuenta.co/v1/oauth/token
Scopes:
  • *Acceso completo (administrador)
  • users:readLectura de usuarios
  • users:writeEscritura de usuarios
  • users.info:writeEscritura de información de usuarios
  • clients:readLectura de clientes OAuth
  • clients:writeEscritura de clientes OAuth
  • verifications:writeEscritura de verificaciones
  • passwords:writeEscritura de contraseñas
  • keys:readLectura de claves
  • settings:readLectura de configuraciones
  • settings:writeEscritura de configuraciones
or
prod

OAuth 2.0 Client Credentials Grant (RFC 6749 §4.4).
Utilice este flow para autenticación de aplicación-a-aplicación sin intervención del usuario.
Ideal para servicios backend y integración entre sistemas.

clientCredentials Flow
Token URLhttps://api.kuenta.co/v1/oauth/token
Scopes:
  • *Acceso completo (administrador)
  • users:readLectura de usuarios
  • users:writeEscritura de usuarios
  • users.info:writeEscritura de información de usuarios
  • clients:readLectura de clientes OAuth
  • clients:writeEscritura de clientes OAuth
  • verifications:writeEscritura de verificaciones
  • passwords:writeEscritura de contraseñas
  • keys:readLectura de claves
  • settings:readLectura de configuraciones
  • settings:writeEscritura de configuraciones

Parameters​

Header Parameters

Config-Organization-ID*

ID de configuración de la organización.
Formato: UUID v4

Typestring
Required
Example123e4567-e89b-12d3-a456-426614174000
formatuuid
Organization-ID*

ID de la organización que está importando el crédito.
Formato: UUID v4

Typestring
Required
Example123e4567-e89b-12d3-a456-426614174000
formatuuid

Request Body​

application/json

El teléfono no existe en Kuenta, así que se crea el deudor a partir de
profile. Tasa 25,045 % EA (≈ 1,88 % mensual), mora 30,5 % EA explícita, y
dos pagos históricos en orden ascendente.

JSON
{
"creditLineID": "456e7890-e89b-12d3-a456-426614174001",
"externalID": "LEGACY-CRED-000123",
"phone": "+573001234567",
"document": "1020304050",
"documentType": 1,
"principal": 1000000,
"rate": "25.0452604625315",
"debtRate": "30.5",
"timeDays": 360,
"frequency": 30,
"startedAt": "2025-03-11",
"status": 7,
"profile": {
"firstName": "Ana María",
"lastName": "Rodríguez Pérez",
"email": "[email protected]",
"idNumber": "1020304050",
"idType": "1",
"birthdate": "1990-05-14",
"addressHome": "Calle 123 # 45-67 Apto 802",
"neighborhoodHome": "Chapinero Alto",
"occupation": "Comerciante",
"incomeMonthly": 4500000
},
"payments": [
{
"paymentDate": "2025-04-11",
"paymentValue": 120000
},
{
"paymentDate": "2025-05-12",
"paymentValue": 120000
}
]
}

Responses​

Crédito importado (o reutilizado, si el `externalID` ya existía). La respuesta usa el sobre **JSend**: `status: "success"` y los datos del crédito bajo `data.import_credit`. **Nota:** el encabezado `location` de esta respuesta trae la cadena literal `credit imported`, no una URL. No lo use para navegar al recurso.
application/json
JSON
{
"status": "success",
"data": {
"import_credit": {
"id": "123e4567-e89b-12d3-a456-426614174000",
"external_id": "LEGACY-CRED-000123",
"reference": "K7XQ2M"
}
}
}

Playground​

Server
Authorization
Headers
Body

Samples​

Powered by VitePress OpenAPI