Dispersar bolívares a un beneficiario
POST/payoutsProbar
La llamada la hace tu navegador, contra el servidor que elijas arriba. Si la documentación no está servida desde el mismo origen que la API, el navegador puede bloquear la respuesta por CORS: en ese caso abre estas páginas desde la propia API (/docs) o usa el curl de arriba.
const url = 'http://localhost:3000/v1/payouts';const options = { method: 'POST', headers: {'x-api-key': '<x-api-key>', 'Content-Type': 'application/json'}, body: '{"quoteId":"70ec44b5-c1df-45b1-86fe-8f92c3959689","method":"pago_movil","beneficiaryId":"8ade3e33-6f1c-4d0a-9d33-2c6b0a41f7e2"}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url http://localhost:3000/v1/payouts \ --header 'Content-Type: application/json' \ --header 'x-api-key: <x-api-key>' \ --data '{ "quoteId": "70ec44b5-c1df-45b1-86fe-8f92c3959689", "method": "pago_movil", "beneficiaryId": "8ade3e33-6f1c-4d0a-9d33-2c6b0a41f7e2" }'Scope payments:write. Entrega bolívares al beneficiario en su banco venezolano, por pago móvil o por transferencia.
El importe no se manda en esta llamada: lo fija la cotización (quoteId), que también fija la comisión.
El beneficiario va por beneficiaryId (dado de alta antes) o en línea en beneficiary.
Manda siempre Idempotency-Key. Un reintento de red sin ella es exactamente cómo se paga dos veces.
Importe mínimo: el riel tiene un piso por operación de 1 token, que a la tasa medida (924,5231 Bs/USDT) son ~925,00 Bs. El piso está en TOKENS y se comprueba con la tasa vigente en cada envío: su equivalente en Bs se mueve todos los días, así que no dejes tus importes al borde. Por debajo, la dispersión responde 201 con status: failed y reason: amount_below_rail_minimum, sin salir al riel y sin cobrarte.
Tope por operación: en pago_movil, 100.000,00 Bs (10000000); en transferencia, 500.000,00 Bs (50000000). Los fija el ecosistema bancario venezolano, no nosotros. Superarlo devuelve 422 limit_exceeded con el detalle en error.meta.
settled NO es la respuesta habitual: el riel de bolívares es asíncrono y acepta el envío antes de liquidarlo, así que lo normal es recibir submitted. Si tu código exige settled aquí, se rompe en producción.
Authorizations
Sección titulada «Authorizations»Parameters
Sección titulada «Parameters»Header Parameters
Sección titulada «Header Parameters»Único por operación. Reintentar con la misma key y payload devuelve la respuesta original; con otro payload da 409 idempotency_key_reused.
Request Bodyrequired
Sección titulada «Request Bodyrequired»object
Fija el importe en Bs, la tasa y nuestro margen
preferred permite entregar por el otro instrumento si el pedido no es posible (y te lo dice en methodChanged); strict sólo acepta el instrumento pedido.
Beneficiario ya dado de alta. Alternativa a beneficiary.
object
Nombre del titular TAL COMO lo tiene el banco destino
Cédula o RIF: V/E/J/G/P + 6 a 9 dígitos
Banco destino, 4 dígitos
Sólo dígitos, de 16 a 20 (en la práctica venezolana, 20), y debe empezar por el bankCode. Necesario para transferencia.
Móvil del beneficiario, formato 04XXXXXXXXX. Necesario para los DOS instrumentos, no sólo para pago móvil.
Opcional: a qué usuario TUYO corresponde la dispersión. Para tu trazabilidad y para los límites por usuario.
Datos del REMITENTE de la dispersión: quién origina el envío, no quién lo recibe. Es opcional en la API porque hay tres formas de obtenerlo, y se aplican en este orden:
- lo que mandes aquí (manda el cliente: es su usuario final quien origina el envío y sólo él lo conoce);
- el usuario referenciado por
customerId, si lo hay; - el remitente por defecto configurado para tu programa — el caso del cliente que dispersa nóminas por cuenta propia, donde el ordenante es siempre la misma empresa.
Si no hay ninguno de los tres, la dispersión falla antes de la primera llamada al riel en vez de salir con un remitente inventado: un envío atribuido a una persona que no existe no se deshace después.
Si mandas el bloque, todos sus campos son obligatorios salvo nationality.
object
Documento del remitente. Si es venezolano (nationality ausente o VEN): V, E o J seguido de hasta 9 dígitos. Si es extranjero: de 5 a 20 alfanuméricos, tal como lo emite su país.
País de nacionalidad en ISO 3166-1 alfa-3. Ausente equivale a VEN, y cambia el formato que se exige en nationalId.
Móvil venezolano (0412/0414/0416/0424/0426). Se aceptan las tres formas —04141234567, +584141234567 y 4141234567— y se normalizan a 04XXXXXXXXX. Un fijo se rechaza.
Examples
Pago móvil a un beneficiario ya registrado
{ "quoteId": "70ec44b5-c1df-45b1-86fe-8f92c3959689", "method": "pago_movil", "beneficiaryId": "8ade3e33-6f1c-4d0a-9d33-2c6b0a41f7e2"}Transferencia con el beneficiario en la misma llamada
{ "quoteId": "70ec44b5-c1df-45b1-86fe-8f92c3959689", "method": "transferencia", "methodPolicy": "strict", "beneficiary": { "fullName": "María Gómez", "nationalId": "V12345678", "bankCode": "0134", "accountNumber": "01340000123456789012", "phone": "04141234567" }}Con datos del remitente (bloque `sender`)
{ "quoteId": "70ec44b5-c1df-45b1-86fe-8f92c3959689", "method": "pago_movil", "beneficiary": { "fullName": "José Marcano", "nationalId": "V18234112", "bankCode": "0138", "phone": "04141234567" }, "sender": { "nationalId": "V12345678", "firstName": "Ana", "lastName": "Pérez", "phone": "04241234567", "email": "ana@example.com", "nationality": "VEN" }}Responses
Sección titulada «Responses»Resultado de la dispersión. status puede ser:
submitted— el riel la aceptó y está liquidando. Es la respuesta HABITUAL con el riel de bolívares. No reintentes: espera el webhookpayment.settled/payment.failed, o consultaGET /payments/{id}.settled— liquidada. No hay nada que hacer.failed— rechazada, y el asiento ya está revertido:feeAmountviene en"0"y no se te cobra nada. El motivo está enreason(ver la lista en el esquemaPaymentResult). Corrige la causa y reintenta con otra cotización.unknown— el riel no confirmó y no sabemos si pagó. NUNCA reintentes: lo resolvemos consultando al riel con nuestra referencia y te avisamos por webhook. En este caso la respuesta no traetotalDebited.
Ojo con la taxonomía: reason NO es error.code. Un rechazo del riel llega como 201 con status: failed, no como un error HTTP.
El webhook de desenlace sale una sola vez, tanto si la dispersión se resolvió en el acto como si tardó.
object
submitted = el riel la aceptó y está liquidando. Es el estado HABITUAL justo después de dispersar, porque el riel de bolívares es asíncrono. No reintentar: se cierra por webhook.
unknown = el riel no confirmó y no sabemos si pagó. NO reintentar: se resuelve consultando al proveedor y notificamos por webhook.
El comprobante que verá el beneficiario en su banco. null mientras el riel no haya liquidado.
Monto enviado al destino, en unidades mínimas
Comisión cobrada, en unidades mínimas. En un pago failed es 0 porque el asiento se revirtió completo.
Monto + comisión; es lo que bajó del saldo de origen
True si se usó un canal distinto al pedido
Detalle legible del estado. En submitted explica que el riel aceptó y está liquidando; en unknown, que se resolverá solo.
Sólo en status: failed. Motivo del rechazo, con código estable. Es una taxonomía DISTINTA de error.code: llega dentro de una respuesta 201, no de un error HTTP.
Valores: beneficiary_missing_phone (falta el móvil — ocurre también en transferencia), beneficiary_missing_account, beneficiary_mismatch (el nombre o la cédula no coinciden con el titular), invalid_destination_account, sender_required, amount_below_rail_minimum, amount_above_rail_limit, amount_not_positive, amount_precision_loss, bank_unreachable, instrument_unsupported, rail_insufficient_balance, rail_rejected, no_route.
object
Examples
submitted — el desenlace HABITUAL
{ "id": "89920401-26da-4f69-b57d-bc20e0366ce2", "status": "submitted", "message": "El riel aceptó el envío y todavía no lo ha liquidado. Se confirmará por webhook (payment.settled); no lo reintentes con otra Idempotency-Key.", "bankReference": null, "asset": "BS", "amount": "5000000", "feeAmount": "50000", "totalDebited": "5050000", "method": "pago_movil", "methodRequested": "pago_movil", "methodChanged": false, "beneficiaryId": "8ade3e33-6f1c-4d0a-9d33-2c6b0a41f7e2", "beneficiary": { "fullName": "María Gómez", "nationalId": "V12345678", "bankCode": "0138", "instrument": "pago_movil" }}failed — rechazo del riel, asiento ya revertido
{ "id": "89920401-26da-4f69-b57d-bc20e0366ce2", "status": "failed", "reason": "beneficiary_missing_phone", "asset": "BS", "amount": "5000000", "feeAmount": "0", "beneficiaryId": "8ade3e33-6f1c-4d0a-9d33-2c6b0a41f7e2", "beneficiary": { "fullName": "María Gómez", "nationalId": "V12345678", "bankCode": "0134", "instrument": "transferencia" }}unknown — el riel no confirmó. NO reintentar
{ "id": "89920401-26da-4f69-b57d-bc20e0366ce2", "status": "unknown", "message": "El riel no confirmó el resultado. El pago se resolverá automáticamente consultando al proveedor; no lo reintentes con otra Idempotency-Key.", "asset": "BS", "amount": "5000000", "feeAmount": "50000", "beneficiaryId": "8ade3e33-6f1c-4d0a-9d33-2c6b0a41f7e2", "beneficiary": { "fullName": "María Gómez", "nationalId": "V12345678", "bankCode": "0138", "instrument": "pago_movil" }}Error con código estable
object
object
Contexto estructurado del error cuando lo hay: qué límite se superó y de cuánto, qué campo es inválido, cuánto saldo había. No aparece en errores internos.
object
Código ESTABLE. Programa contra esto.
Example
{ "error": { "type": "invalid_request_error", "code": "insufficient_balance", "request_id": "req_8a0312ba896772bc441134cd" }}Error con código estable
object
object
Contexto estructurado del error cuando lo hay: qué límite se superó y de cuánto, qué campo es inválido, cuánto saldo había. No aparece en errores internos.
object
Código ESTABLE. Programa contra esto.
Example
{ "error": { "type": "invalid_request_error", "code": "insufficient_balance", "request_id": "req_8a0312ba896772bc441134cd" }}Error con código estable
object
object
Contexto estructurado del error cuando lo hay: qué límite se superó y de cuánto, qué campo es inválido, cuánto saldo había. No aparece en errores internos.
object
Código ESTABLE. Programa contra esto.
Example
{ "error": { "type": "invalid_request_error", "code": "insufficient_balance", "request_id": "req_8a0312ba896772bc441134cd" }}Error con código estable
object
object
Contexto estructurado del error cuando lo hay: qué límite se superó y de cuánto, qué campo es inválido, cuánto saldo había. No aparece en errores internos.
object
Código ESTABLE. Programa contra esto.
Example
{ "error": { "type": "invalid_request_error", "code": "insufficient_balance", "request_id": "req_8a0312ba896772bc441134cd" }}Error con código estable
object
object
Contexto estructurado del error cuando lo hay: qué límite se superó y de cuánto, qué campo es inválido, cuánto saldo había. No aparece en errores internos.
object
Código ESTABLE. Programa contra esto.
Example
{ "error": { "type": "invalid_request_error", "code": "insufficient_balance", "request_id": "req_8a0312ba896772bc441134cd" }}