Estructura de errores
Todas las respuestas de error tienen este shape:{
"error": "CODIGO_MAYUSCULAS_SNAKE_CASE",
"message": "Descripción human-readable en español",
"details": { /* opcional, contextual */ }
}
| Status | Significado | Acción recomendada |
|---|---|---|
| 400 | Body inválido | Corregir request |
| 401 | API key inválida o no presente | Verificar header Authorization |
| 403 | Scope insuficiente o cuenta inactiva | Regenerar llave con scope correcto |
| 404 | Recurso no encontrado | Verificar ID |
| 422 | Validación lógica falló | Mensaje propio del error |
| 429 | Rate limit excedido | Esperar Retry-After segundos |
| 502 | Error del proveedor upstream | Reintento con backoff exponencial |
| 503 | Servicio temporalmente no disponible | Reintento con backoff |
Errores comunes
| Código | Status | Significado |
|---|---|---|
AUTH_REQUIRED | 401 | Falta header Authorization |
INVALID_API_KEY | 401 | Llave inválida o revocada |
COMPANY_INACTIVE | 401 | Cuenta suspendida — contactar soporte |
INSUFFICIENT_SCOPE | 403 | Llave READ intentando operación WRITE |
API_KEY_AUTH_NOT_ALLOWED | 403 | Operación requiere sesión dashboard (no API key) |
RATE_LIMIT_EXCEEDED | 429 | Demasiadas requests — esperar Retry-After |
INVALID_REQUEST | 400 | Body o query parameters inválidos |
Saldos
| Código | Status | Significado |
|---|---|---|
WALLET_NOT_PROVISIONED | 404 | La empresa aún no tiene wallet aprovisionada |
FAST_WALLET_MISSING | 404 | Schema invariant defensivo — contactar soporte |
Retiros BREB
| Código | Status | Significado |
|---|---|---|
BREB_DIRECTORY_LOOKUP_FAILED | 503 | Red Bre-B no responde — reintento con backoff |
INVALID_KEY | 422 | Llave Bre-B malformada |
KEY_INACTIVE | 422 | Llave existe pero no está activa en Bre-B |
INSUFFICIENT_BALANCE | 422 | Saldo BREB no alcanza para monto + fee + IVA |
TARGET_DOCUMENT_REQUIRED | 422 | Falta targetDocument (compliance SARLAFT) |
AMOUNT_EXCEEDS_BREB_CAP | 422 | Bre-B individual cap es $12M COP |
NAME_MISMATCH | 422 | Nombre cargado no coincide con titular Bre-B |
Retiros ACH
| Código | Status | Significado |
|---|---|---|
ACH_RAIL_UNAVAILABLE | 503 | Rail ACH temporalmente caído |
INVALID_BANK_ACCOUNT | 422 | Validación de cuenta falló (formato o titular) |
INSUFFICIENT_BALANCE | 422 | Saldo ACH no alcanza |
AMOUNT_EXCEEDS_ACH_CAP | 422 | ACH individual cap es $2.000M COP |
DESTINATION_REJECTED | 400 | Banco / cuenta rechazado por reglas internas |
Recaudos PSE
| Código | Status | Significado |
|---|---|---|
PSE_PROVIDER_NOT_CONFIGURED | 503 | Operator misconfig — contactar soporte |
PSE_INFLOW_DISABLED | 503 | PSE temporalmente bloqueado por incident response |
PSE_PROVIDER_ERROR | 502 | Proveedor PSE upstream falló — reintentar |
FEE_EXCEEDS_AMOUNT | 422 | FeeConfig produce fee+IVA >= monto del depósito |
RECIPIENT_BLACKLISTED | 422 | El pagador está en lista AML — bloqueado por compliance |
Multi-sig
| Código | Status | Significado |
|---|---|---|
AWAITING_APPROVAL | 200 (body) | Transferencia espera firmas (no es error técnico) |
APPROVAL_EXPIRED | 422 | TTL 24h expirado, transferencia revertida |
INVALID_TOTP_CODE | 401 | Código 2FA inválido al firmar |
AML / Counterparty limits
| Código | Status | Significado |
|---|---|---|
COUNTERPARTY_DAILY_LIMIT_EXCEEDED | 422 | Tope diario por tercero excedido |
COUNTERPARTY_MONTHLY_LIMIT_EXCEEDED | 422 | Tope mensual por tercero excedido |
Estrategia de reintentos recomendada
async function mouvRequestWithRetry(url, options, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
const res = await fetch(url, options);
// Éxito
if (res.ok) return res.json();
const body = await res.json();
// Errores que NO se reintenan
if (res.status === 400 || res.status === 401 || res.status === 403 || res.status === 422) {
throw new Error(body.error);
}
// Rate limit: respect Retry-After
if (res.status === 429) {
const wait = parseInt(res.headers.get('Retry-After') || '1') * 1000;
await new Promise(r => setTimeout(r, wait));
continue;
}
// 5xx: backoff exponencial
if (res.status >= 500) {
const wait = Math.pow(2, i) * 1000; // 1s, 2s, 4s
await new Promise(r => setTimeout(r, wait));
continue;
}
}
throw new Error('MAX_RETRIES_EXCEEDED');
}
Idempotencia
Para operaciones financieras, el camporeference del request body actúa como idempotency key opcional. Si reenviás la misma request con el mismo reference dentro de los próximos 60s, recibís la transacción original en lugar de duplicar.
curl -X POST https://consola.mouvlatam.com/api/transfers/send \
-H "Authorization: Bearer mvk_..." \
-d '{ "amount": 100000, ..., "reference": "factura-001-2026" }'
reference, Mouv resuelve el conflicto:
- Si la operación original SÍ se dispatchó upstream → response original
- Si NO se dispatchó → procesa la nueva request normalmente
Errores del modo agregador (recaudo Bre-B)
| HTTP | Código | Significado |
|---|---|---|
| 403 | AGGREGATOR_NOT_ENABLED | El modo agregador no está habilitado para tu empresa — escribinos para activarlo |
| 400 | NAME_NOT_DERIVABLE | El nombre del cliente no alcanza para derivar una llave (mínimo 3 caracteres alfanuméricos) |
| 409 | KEY_TAKEN | La llave colisiona en la red nacional tras 3 variantes automáticas — el body trae attempted[]; probá con otro nombre |
| 409 | ACCOUNT_NOT_READY | Tu cuenta de recaudo Bre-B aún no está lista — contactá a soporte |
| 409 | KEY_TYPE_NOT_AVAILABLE | Ese tipo de llave no está disponible por ahora |
| 404 | KEY_NOT_FOUND | La llave no existe o no pertenece a tu empresa |
| 429 | RATE_LIMIT_EXCEEDED | Límite de creación de llaves por minuto — esperá y reintentá (el mismo externalId es idempotente) |
Conversión COP→USDT
Los errores de/swaps tienen un shape plano (sin objeto details):
{
"error": "AMOUNT_BELOW_MINIMUM",
"message": "El monto está por debajo del mínimo permitido.",
"minUsdt": "1000.000000"
}
string: minUsdt, maxUsdt, retryAfterSeconds, scope (PER_OPERATION, DAILY o MONTHLY) y swapId. Usa error para la lógica (es estable); message es para mostrar. Guía: Conversión COP→USDT.
| HTTP | Código | Significado y acción |
|---|---|---|
| 400 | INVALID_REQUEST | Body o parámetro inválido (por ejemplo network en minúsculas). Corrige la solicitud |
| 400 | INVALID_AMOUNT | amount no es un string decimal válido o es ≤ 0 |
| 400 | IDEMPOTENCY_KEY_REQUIRED | Falta Idempotency-Key o su formato es inválido (8 a 128 caracteres: letras, números y _ - : .) |
| 403 | INSUFFICIENT_SCOPE | La llave no es scope SWAP (una llave WRITE o READ no sirve en /swaps) |
| 403 | SCOPE_ROUTE_FORBIDDEN | Una llave SWAP solo funciona en /swaps/* |
| 403 | IP_NOT_ALLOWED | La llave tiene allowedIps y la petición viene de otra IP |
| 400 | INVALID_ALLOWED_IPS | Al crear la llave, allowedIps tiene una IP o rango inválido (máximo 20 entradas; CIDR hasta /24 IPv4 o /48 IPv6) |
| 403 | SWAP_NOT_ENABLED | Tu empresa no tiene el servicio habilitado. Solicita la habilitación |
| 403 | SWAPS_NOT_ENABLED | Al crear una llave SWAP: el servicio no está habilitado para tu empresa |
| 403 | SWAP_NOT_CONFIGURED | El servicio de tu empresa aún no está configurado. Contacta a soporte |
| 403 | COMPANY_NOT_ACTIVE | Tu empresa no está activa para operar |
| 403 | BLACKLISTED | No es posible procesar la operación. Contacta a soporte |
| 403 | MULTISIG_NOT_SUPPORTED | El servicio aún no está disponible para empresas con aprobación multifirma |
| 403 | WALLETS_REQUIRE_DASHBOARD | Esta acción sobre billeteras solo se hace desde el dashboard, con 2FA |
| 403 | WALLET_PERMISSION_REQUIRED | La llave no tiene el permiso Gestionar billeteras. Crea una llave SWAP con el permiso |
| 403 | IP_ALLOWLIST_REQUIRED | La llave no tiene IPs autorizadas (allowedIps). Se exigen para gestionar billeteras por API. Al crear la llave responde 400 |
| 403 | WALLET_API_NOT_ENABLED | La gestión de billeteras por API no está habilitada para tu empresa. Contacta a soporte. Al crear la llave también responde 403 |
| 400 | INVALID_SCOPE_COMBINATION | Al crear la llave: el permiso Gestionar billeteras solo existe en llaves SWAP |
| 409 | WALLET_API_KEY_LIMIT | Al crear la llave: ya hay 2 llaves vigentes con el permiso. El body trae activeCount y cap. Revoca una |
| 400 | IDEMPOTENCY_KEY_INVALID | En POST /swaps/wallets: formato inválido de Idempotency-Key (8 a 128 caracteres: letras, números y _ - : .) |
| 400 | OWNERSHIP_DECLARATION_REQUIRED | En POST /swaps/wallets: falta ownershipDeclaration: true |
| 400 | INVALID_ADDRESS, INVALID_ADDRESS_CHECKSUM | La dirección no tiene un formato válido para la red, o su checksum no coincide |
| 403 | ADDRESS_NOT_ALLOWED | No es posible registrar esta dirección. Contacta a soporte |
| 404 | WALLET_NOT_FOUND | La billetera no existe o es de otra empresa |
| 409 | WALLET_DUPLICATE | La dirección ya está registrada para esa red |
| 409 | IDEMPOTENCY_KEY_REUSED | En POST /swaps/wallets: la Idempotency-Key ya se usó con otra billetera (en POST /swaps este mismo código responde 422) |
| 422 | WALLET_BENEFICIARY_MUST_BE_COMPANY | La billetera debe estar a nombre de tu empresa |
| 422 | WALLET_LIMIT_REACHED | Máximo 20 billeteras vigentes. Elimina una |
| 429 | WALLET_API_DAILY_LIMIT | Máximo 10 billeteras registradas por API en 24 horas |
| 404 | QUOTE_NOT_FOUND | La cotización no existe o es de otra empresa |
| 404 | SWAP_NOT_FOUND | La operación no existe o es de otra empresa |
| 409 | QUOTE_ALREADY_USED | La cotización ya fue aceptada. El body trae swapId: consúltalo con GET /swaps/:id |
| 409 | STRUCTURING | Tienes operaciones recientes. Espera retryAfterSeconds |
| 409 | RECEIPT_NOT_AVAILABLE | El comprobante existe solo para operaciones COMPLETADO |
| 409 | INVALID_TRANSITION, CONCURRENT_UPDATE | La operación cambió o no admite la acción. Consulta su estado y reintenta |
| 410 | QUOTE_EXPIRED | La cotización venció. Cotiza de nuevo |
| 422 | IDEMPOTENCY_KEY_REUSED | La Idempotency-Key ya se usó con otra cotización. El body trae swapId |
| 422 | NETWORK_NOT_ALLOWED | La red no está habilitada para tu empresa |
| 422 | AMOUNT_BELOW_MINIMUM | Monto menor al mínimo (ver minUsdt). En TRON el mínimo es mayor por el costo de la red: para montos pequeños usa POLYGON o aumenta el monto |
| 422 | LIMIT_EXCEEDED | Supera el límite por operación (scope: PER_OPERATION, con maxUsdt), diario (DAILY) o mensual (MONTHLY) |
| 422 | WALLET_NOT_WHITELISTED | Falta walletId, no es de tu empresa, es de otra red o aún no está DISPONIBLE |
| 422 | SWAP_NOT_SUPPORTED | La operación solicitada no está disponible (solo se puede comprar: side: "BUY") |
| 422 | INSUFFICIENT_FUNDS | Saldo COP insuficiente |
| 422 | ACCOUNT_NOT_ELIGIBLE | Tu cuenta no está habilitada para conversiones. Contacta a soporte |
| 429 | RATE_LIMITED | Más de 30 cotizaciones por minuto por empresa. Espera según Retry-After |
| 503 | RATE_UNAVAILABLE, RATE_STALE, RATE_JUMP_BREAKER, RATE_DEVIATION | No hay tasa disponible por ahora. Reintenta con backoff |
| 503 | SWAP_CAPACITY_UNAVAILABLE | Sin cupo en este momento. Reintenta más tarde o con un monto menor |
| 503 | SWAP_TEMPORARILY_UNAVAILABLE, SWAPS_DISABLED | Servicio no disponible temporalmente. Reintenta con backoff; si persiste, contacta a soporte |
| 503 | GATE_CHECK_FAILED, QUOTE_INTEGRITY, SWAP_UNAVAILABLE | No se pudo validar o confirmar. Reintenta con una cotización nueva; si persiste, contacta a soporte con el error |
4xx: corrige o contacta a soporte; 503: reintenta con backoff) y muestra el message.
Reintentos de
POST /swaps: ante un timeout o un 5xx, reintenta con la misma Idempotency-Key. Nunca generes una key nueva para el mismo intento.