Códigos de Error

Todos los códigos de error que devuelve la API, agrupados por tipo, con qué hacer en cada caso.

ᚱ
"Cuando las fuerzas del caos interfieren, los dioses envían mensajes claros a los mortales. Aprende los códigos sagrados que revelan la naturaleza de cada obstáculo en el camino."
Los Guardianes de Bifrost

Formato del error

Todo error de /v1 tiene la misma forma, con el estado HTTP de las tablas siguientes. Compare error.code, que es snake_case estable. error.message es texto libre en inglés para sus logs. error.details solo aparece cuando hay datos útiles, como campos inválidos, límites o scopes.

json
{
  "error": {
    "code": "insufficient_total_balance",
    "message": "Insufficient balance to cover all payouts",
    "details": {
      "crypto_currency": "USDT",
      "available": "150.00000000",
      "required": "420.50000000"
    }
  }
}

En un lote de POST /v1/payout, cada ítem rechazado trae el mismo objeto en results[].error, junto a su index y reference_id. La respuesta es 200 cuando todos los ítems tuvieron éxito, 207 cuando algunos tuvieron éxito y otros fallaron, y 400 cuando ninguno tuvo éxito:

json
{
  "message": "payment_processing_completed",
  "summary": {
    "total_requested": 3,
    "successful": 1,
    "failed": 2
  },
  "results": [
    {
      "index": 0,
      "reference_id": "order_1001",
      "internal_reference": "BIFROST_REF_123456",
      "status": "created"
    },
    {
      "index": 1,
      "reference_id": "order_1002",
      "error": {
        "code": "invalid_parameters",
        "message": "Invalid parameters provided",
        "details": {
          "fields": {
            "wallet": "Invalid wallet format"
          }
        }
      }
    },
    {
      "index": 2,
      "reference_id": "order_1003",
      "error": {
        "code": "daily_payout_limit_exceeded",
        "message": "Daily payout limit for this API key exceeded"
      }
    }
  ]
}
  • Los mensajes de validación están siempre en error.details.fields, indexados por nombre de campo, tanto en el nivel superior como dentro de un ítem del lote.
  • Un ítem de payout duplicado cuenta como fallo: trae error.code duplicate_payment_reference más duplicate, original_status y original_created_at en el propio ítem. Un lote compuesto solo de duplicados responde 400.
  • Decida por error.code, nunca por error.message: los mensajes pueden reescribirse, los códigos no cambian.
  • 4xx significa que la solicitud debe cambiar antes de reenviarse. 5xx significa que el fallo fue de nuestro lado y la misma solicitud puede repetirse; en POST /v1/payout, el reference_id sigue protegiéndole contra un retiro duplicado.
  • Una ruta /v1 desconocida devuelve 404 route_not_found en JSON. Un cliente que pide text/html en Accept recibe una página HTML.

Errores de Autenticación y Acceso

Código HTTPErrorDescripción
401api_key_missingNo se envió el encabezado X-Bifrost-Invoke.
401api_key_invalidAPI Key desconocida, revocada o expirada. También se devuelve mientras la cuenta está suspendida.
403api_access_deniedEl acceso a la API está desactivado en esta cuenta. Se verifica en cada solicitud.
403email_not_verifiedEl e-mail de la cuenta aún no ha sido verificado.
403ip_not_authorizedLa IP de origen no está en la whitelist de la clave.
400invalid_ipNo se pudo determinar la IP del cliente.
401signature_headers_missingLa clave exige firma y falta X-Bifrost-Signature, X-Bifrost-Timestamp o X-Bifrost-Nonce.
401signature_timestamp_invalidX-Bifrost-Timestamp no está compuesto solo de dígitos (segundos Unix).
401signature_timestamp_out_of_rangeEl timestamp difiere de la hora del servidor en más de 300 segundos.
401signature_nonce_length_invalidEl nonce tiene menos de 16 o más de 80 caracteres.
401signature_nonce_charset_invalidEl nonce tiene caracteres fuera de A-Z, a-z, 0-9, guion bajo y guion.
401signature_invalidLa firma no coincide con la cadena canónica.
401signature_replayedEste nonce ya fue usado por esta clave.
401signature_secret_unavailableLa clave exige firma pero no tiene un secreto utilizable. Contacte al soporte.
403scope_deniedLa clave no tiene el alcance que exige este endpoint. details.required_scopes lista los que sirven y details.granted_scopes los que tiene la clave.
429rate_limit_exceededLa cuota de la clave en la ventana actual se agotó. Retry-After y details.retry_after_seconds indican cuándo reintentar.
429too_many_failed_authenticationsEsta IP está bloqueada temporalmente tras fallos de autenticación repetidos. Llega con Retry-After.
401user_not_authenticatedNo se pudo asociar la solicitud a su clave de API. Reintente; si se repite, contacte al soporte.

Errores de la Solicitud

Código HTTPErrorDescripción
404route_not_foundNinguna ruta /v1 corresponde a la ruta y al método. Se responde antes de la autenticación.
400json_parse_exceptionEl cuerpo de la solicitud no es JSON válido. Cualquier ruta /v1.
400json_parse_failedPOST /v1/payout: el cuerpo se decodificó como null o false.
400invalid_parametersParámetros inválidos o ausentes. details.fields asocia cada campo a su mensaje (POST /v1/payout con todos los ítems inválidos, POST /v1/payin, direcciones fijas, filtros de extractos).
400invalid_payment_listPOST /v1/payout: el cuerpo está vacío o no es un objeto de payout ni una lista de payouts.
400max_payouts_exceededPOST /v1/payout: más de 50 payouts en una solicitud.
409idempotency_key_in_progressUna solicitud con esta Idempotency-Key aún está en curso. Espere y reintente con la misma clave.
400crypto_amount_requiredPOST /v1/payin: process_by es crypto_amount y falta crypto_amount.
400fiat_amount_requiredPOST /v1/payin: process_by es fiat_amount y falta fiat_amount.
400fiat_currency_requiredPOST /v1/payin: process_by es fiat_amount y falta fiat_currency.
400invalid_page_numberlist-payout / list-payin: page menor que 1.
400invalid_per_page_limitlist-payout / list-payin: per_page fuera de 1 a 100.
400invalid_status_filterlist-payout / list-payin: valor de status desconocido.
400invalid_network_filterlist-payout / list-payin: red desconocida.
400invalid_date_fromlist-payout / list-payin: date_from no es una fecha.
400invalid_date_tolist-payout / list-payin: date_to no es una fecha.
400invalid_reference_idcheck-payout / check-payin: referencia de menos de 10 o más de 756 caracteres. Extractos: reference_id de más de 255.
400invalid_paginationExtractos: page o per_page fuera de rango.
400invalid_formatExtractos: date_from o date_to no es una fecha (details.field).
400invalid_date_rangeExtractos: date_from es posterior a date_to.
400date_range_too_wideExtractos: el rango es más amplio de lo permitido. details.max_days indica el tope.
400invalid_amountExtractos: amount_from o amount_to inválido, o amount_from mayor que amount_to.
400missing_parametersget-price: falta currency1 o currency2.
400invalid_currencyget-price: currency1 o currency2 no es un código de moneda válido.
400invalid_webhook_urlPUT /v1/fixed-wallets/config: webhook_url apunta a un destino no permitido.

Errores de Negocio

Código HTTPErrorDescripción
400insufficient_total_balancePOST /v1/payout: los ítems de una moneda suman más que el saldo disponible. No se debita nada. details trae crypto_currency, available y required.
404user_not_foundPOST /v1/payout: la configuración de la cuenta está incompleta. Contacte al soporte.
404record_not_foundEl payout o payin consultado por check-payout / check-payin no existe en esta cuenta. También se devuelve cuando falta un dato de la cuenta que la solicitud necesita.
400duplicate_reference_idPOST /v1/payin: ya existe un payin con este reference_id en la cuenta.
400invalid_network_or_deposits_disabledPOST /v1/payin: red desconocida, o los depósitos están desactivados en ella.
400unsupported_token_or_deposits_disabledPOST /v1/payin: token no soportado, o los depósitos están desactivados para él.
400token_network_mismatchPOST /v1/payin: el token no está disponible en esta red.
400memo_requiredPOST /v1/payin: este token exige memo.
400amount_out_of_limitsPOST /v1/payin: el monto en cripto está fuera de los límites del token. details trae min_allowed y max_allowed.
400value_below_minimumPOST /v1/payin con fiat_amount: el monto convertido está por debajo del mínimo.
400value_above_maximumPOST /v1/payin con fiat_amount: el monto convertido está por encima del máximo.
400token_not_supported_or_inactivePOST /v1/payin con fiat_amount: el token no es soportado o está inactivo para conversión.
400crypto_amount_does_not_correspond_to_fiat_amount_providedPOST /v1/payin: se enviaron crypto_amount y fiat_amount y no coinciden con la cotización actual.
400invalid_parameters_for_process_byPOST /v1/payin: los montos enviados no encajan con el process_by elegido.
404price_not_foundget-price: no hay cotización para este par de monedas.
403feature_disabledLas direcciones fijas no están habilitadas para la cuenta (config, actualización de config y creación).
422type_not_supportedPOST /v1/fixed-wallets: este type no está disponible para direcciones fijas.
403limit_not_allowedPOST /v1/fixed-wallets: el límite de la cuenta para este type es cero.
409limit_reachedPOST /v1/fixed-wallets: la cuenta ya tiene el número máximo de direcciones fijas.
409no_wallet_availablePOST /v1/fixed-wallets: no hay ninguna dirección de ese tipo disponible ahora. Reintente más tarde.

Errores por Ítem del Lote de Payout (results[].error)

Código HTTPErrorDescripción
207/400invalid_parametersEl ítem no pasó la validación. error.details.fields asocia cada campo a su mensaje.
207/400missing_required_fieldsFalta un campo obligatorio del ítem.
207/400duplicate_reference_id_in_requestEl mismo reference_id aparece antes en el mismo lote.
207/400duplicate_payment_referenceEl reference_id ya se usó en esta cuenta. El ítem trae duplicate, original_status, original_created_at y el internal_reference original; no se hace una segunda transferencia.
207/400invalid_crypto_amountprocess_by es crypto_amount y crypto_amount falta o es inválido.
207/400invalid_fiat_amountprocess_by es fiat_amount y fiat_amount falta o es inválido.
207/400fiat_currency_requiredprocess_by es fiat_amount y falta fiat_currency.
207/400fiat_currency_not_acceptedLa fiat_currency no es aceptada.
207/400memo_requiredEste token exige memo.
207/400invalid_network_or_withdrawals_disabledRed desconocida, o los retiros están desactivados en ella.
207/400unsupported_token_or_withdrawals_disabledToken no soportado, o los retiros están desactivados para él.
207/400token_network_mismatchEl token no está disponible en esta red.
207/400token_not_activeEl token no está activo para retiros o para conversión.
207/400invalid_formatLa dirección de la wallet no es válida para la red.
207/400limit_exceededEl monto está fuera del mínimo y del máximo del token (también cuando el monto fiat convertido queda fuera).
207/400amount_does_not_cover_feesLas comisiones consumen todo el monto, y el destinatario no recibiría nada.
207/400crypto_amount_does_not_correspond_to_fiat_amount_providedcrypto_amount y fiat_amount no coinciden con la cotización actual.
207/400price_unavailableSin cotización para convertir el monto, o la clave tiene tope diario y el token no tiene cotización en USD para contabilizar el retiro.
207/400insufficient_balanceEl saldo disponible no cubre este ítem.
207/400daily_payout_limit_exceededEl ítem superaría el tope diario configurado en la clave.
207/400daily_payout_limit_unavailableLa clave tiene tope diario pero no se pudo registrar el consumo. Reenvíe el ítem.
207/400error_fetching_active_tokens · error_fetching_active_currenciesNo se pudo cargar el catálogo de tokens o monedas. Reenvíe el ítem.
207/400user_not_found · record_not_foundFalta un dato de la cuenta que el retiro necesita.
207/400internal_error · database_error · transaction_failed · network_unavailable · duplicate_recordEl registro del retiro falló de nuestro lado y se revirtió. No se debitó nada por este ítem; verifíquelo con check-payout o list-payout antes de reenviar.

Errores de Sistema

Código HTTPErrorDescripción
500internal_errorFallo inesperado. En POST /v1/payout, un fallo a mitad del lote también devuelve summary (con not_processed) y results en el nivel superior, para que reenvíe solo lo que falta.
500database_errorExtractos: la consulta falló.
500error_fetching_active_currenciesPOST /v1/payout y POST /v1/payin: no se pudo cargar el catálogo de tokens o fiats. No se procesó nada.
500token_type_not_found · receipt_creation_failed · database_transaction_failedPOST /v1/payin: el payin no pudo crearse de nuestro lado.
502external_processor_errorPOST /v1/payin y POST /v1/fixed-wallets: la dirección de depósito no pudo crearse ahora. No se creó nada; reintente más tarde.
503no_processor_configured · no_wallet_availablePOST /v1/payin: no hay procesador ni dirección de depósito disponible para este token y red ahora. Reintente más tarde.
503price_not_available_for_conversion · price_not_available_for_validationPOST /v1/payin: no hay cotización disponible para convertir o verificar el monto. Reintente más tarde.
503service_unavailablePOST /v1/payin y endpoints de summary: una dependencia no está disponible temporalmente.
500config_update_failedPUT /v1/fixed-wallets/config: la configuración no pudo guardarse.
500assign_failedPOST /v1/fixed-wallets: la dirección no pudo asignarse.

Mejores Prácticas

ᚱ
"La sabiduría de los ancianos enseña que seguir los caminos correctos garantiza un viaje seguro y próspero por los nueve reinos digitales."
Mímir, Guardián de la Sabiduría Ancestral

Seguridad

Hacer

  • •Almacena tu API Key en variables de entorno
  • •Configura IP Whitelist
  • •Verifique el X-Bifrost-Signature de cada webhook
  • •Usa HTTPS en todas las solicitudes
  • •Implementa timeouts en las solicitudes

No Hacer

  • •Compartir API Key públicamente
  • •Hacer commit de API Keys en Git
  • •Usar la misma API Key en múltiples entornos

Rendimiento

  • •Los datos de extracto y de resumen pueden tener hasta 5 minutos de retraso; no los consulte con más frecuencia
  • •Implementa paginación adecuada
  • •Evita solicitudes excesivas (respeta el rate limit)
  • •Procesa webhooks de forma asíncrona

Idempotencia

  • •Use un reference_id único y significativo: es el cerrojo contra el pago duplicado, y volver a presentarlo devuelve el pago original en vez de crear otro
  • •Envíe Idempotency-Key en los lotes: si se cae la conexión, repetir con la misma clave devuelve la respuesta original en vez de reprocesar
  • •POST /v1/payin no tiene Idempotency-Key: tras un timeout reenvíe con el mismo reference_id y, si la respuesta es duplicate_reference_id, obtenga el payin con GET /v1/list-payin?reference_id=…
  • •Trate 207 y 500 leyendo results: ambos traen lo que ya se creó, para que reenvíe solo lo que faltó
  • •Gestione los reintentos de webhook deduplicando por data.internal_reference junto con event, para no procesar dos veces el mismo aviso