API v1 · Operativa
← Volver al índice de documentación

Recarga.do API

Recargas móviles, paquetes de datos y pago de servicios en República Dominicana. Una sola integración REST, con ambiente de pruebas incluido.

Basehttps://api.recarga.do
Versiónv1
MonedaDOP
FormatoJSON

Introducción

La API de Recarga.do permite a su negocio vender recargas y cobrar servicios desde su propio sistema: punto de venta, aplicación móvil, portal web o bot de WhatsApp.

Es una API de consulta directa — no hay que integrar ningún iframe

Su aplicación llama por HTTPS a https://api.recarga.do y recibe JSON. Nada más. No se incrusta un <iframe>, no se redirige al usuario a ninguna pantalla de Recarga.do, no hace falta instalar un SDK y no hay sesiones ni cookies que mantener. La API solo devuelve JSON y responde con X-Frame-Options: DENY, de modo que embeberla no es siquiera posible.

La interfaz es suya, al cien por ciento: sus pantallas, su flujo, su marca. Recarga.do queda como proveedor invisible detrás de su aplicación. Un navegador puede llamarla desde cualquier origen —Access-Control-Allow-Origin: *—, pero el api_secret debe quedarse en su servidor, nunca dentro de una app distribuida. Vea la guía de integración.

ConceptoDetalle
ModeloPrepago. Usted mantiene un saldo y cada operación lo descuenta.
ProtocoloREST sobre HTTPS. Peticiones y respuestas en JSON UTF-8.
AutenticaciónPar api_key / api_secret por header.
MonedaPeso dominicano (DOP). Montos con hasta 2 decimales.
Zona horariaAmerica/Santo_Domingo (UTC−4).
ServiciosTelefonía, paquetes de datos, electricidad, agua, internet y TV, entretenimiento y más.
Diseñada para no cobrar dos veces

Cada operación financiera exige una clave de idempotencia, y el importe se reserva antes de enviarse al servicio. Si algo falla, el dinero vuelve a su saldo automáticamente. Si el resultado queda en duda, se retiene en lugar de cobrarse. Lea Idempotencia antes de integrar.

Guía rápida

De cero a su primera recarga de prueba en cinco minutos.

1
Verifique su cuenta Consulte GET /v1/cuenta y confirme si su token está en development (pruebas) o production (dinero real).
2
Consulte su saldo GET /v1/saldo le indica cuánto puede gastar ahora mismo.
3
Obtenga el catálogo GET /v1/servicios devuelve el service_id y el rango de montos de cada servicio.
4
Ejecute la recarga POST /v1/recargas con un Idempotency-Key único.
5
Guarde el resultado Almacene el transaction_id y el request_id. Son su referencia ante cualquier consulta.

Todo el flujo en una sola sesión de terminal

# 1 · Credenciales del ambiente de pruebas
KEY="rk_sbx_8c4cacb52c6b8f65bf389480ce3f8891"
SECRET="sk_sbx_a8e4de1dbd5e135a4be191cfd8901f386e541279a6550b1d30af9fbeaa65bbd0"
AUTH="Authorization: Bearer $KEY.$SECRET"

# 2 · ¿En qué ambiente estoy?
curl -s https://api.recarga.do/v1/cuenta -H "$AUTH"

# 3 · ¿Cuánto saldo tengo?
curl -s https://api.recarga.do/v1/saldo -H "$AUTH"

# 4 · ¿Qué servicios puedo vender?
curl -s https://api.recarga.do/v1/servicios -H "$AUTH"

# 5 · Recargar (la clave debe ser única por operación)
curl -s -X POST https://api.recarga.do/v1/recargas \
  -H "$AUTH" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"service_id":23,"identifier":"8095551235","amount":100}'

# 6 · Consultar la transacción
curl -s https://api.recarga.do/v1/transacciones/48849 -H "$AUTH"

Ambiente de pruebas

Las cuentas con ambiente development operan contra un simulador. No se genera ninguna recarga real ni ningún cargo real, pero el saldo, las transacciones y todos los estados se comportan exactamente igual que en producción. Es el lugar correcto para desarrollar y para probar sus manejos de error.

Credenciales de prueba abiertas

Puede usarlas ahora mismo, sin registro. Son compartidas y su saldo se restablece periódicamente: úselas para explorar, y solicite las suyas propias para desarrollar en serio.

api_key    rk_sbx_8c4cacb52c6b8f65bf389480ce3f8891
api_secret sk_sbx_a8e4de1dbd5e135a4be191cfd8901f386e541279a6550b1d30af9fbeaa65bbd0

Escenarios controlados

En el sandbox, el último dígito del campo identifier decide el resultado. Así puede probar todos los caminos de forma determinista, incluidos los que en producción son difíciles de reproducir.

Identifier termina enHTTPCódigoQué ocurre con su saldo
0 — ej. 8095550000422INVALID_PHONE Se reserva y se devuelve completo
1 — ej. 8095550001422TRANSACTION_REJECTED Se reserva y se devuelve completo
2 — ej. 8095550002202TRANSACTION_PENDING Queda retenido — ni cobrado ni disponible
cualquier otro — ej. 8095551235201SUCCESS Se cobra
Pruebe el escenario 202 antes de salir a producción

Es el caso que rompe la mayoría de las integraciones. Un 202 no significa que la recarga falló: significa que aún no se sabe. Su código nunca debe reintentar con una clave nueva ante un 202. Use el identificador terminado en 2 para verificar que lo maneja bien.

Pasar a producción

  1. Solicite sus credenciales de producción a su ejecutivo de cuenta.
  2. Confirme con GET /v1/cuenta que devuelve "sandbox": false.
  3. Deposite saldo. Verifique con GET /v1/saldo.
  4. La URL base y todos los endpoints son idénticos: solo cambian las credenciales.

Consola interactiva

Ejecute peticiones reales contra la API desde este navegador. Viene precargada con las credenciales de prueba, así que funciona sin configurar nada.

1
2
El último dígito decide el escenario de prueba: 0 no existe · 1 sin opciones · 2 sin respuesta · otro, todo bien.
3
4
Elija un servicio y pulse Consultar o Comprar.
La respuesta aparecerá aquí.
Reintente con la misma clave, no con una nueva

Pulse Comprar dos veces sin tocar la clave: obtendrá el mismo transaction_id y el header Idempotent-Replay: true. El saldo baja una sola vez. Pulse «Nueva clave» y verá que sí se ejecuta una segunda recarga: esa es exactamente la diferencia que evita cobrar dos veces a su cliente.

Autenticación

Cada comercio recibe un par api_key / api_secret. Se admiten dos formas equivalentes; elija la que le resulte más cómoda.

Opción A — Bearer (recomendada)

Authorization: Bearer <api_key>.<api_secret>

La clave y el secreto van separados por un punto, en un solo header.

Opción B — Headers separados

X-Api-Key: <api_key>
X-Api-Secret: <api_secret>
Su api_secret es una credencial de pago

Quien lo posea puede gastar su saldo. No lo incluya en aplicaciones móviles, código JavaScript de navegador ni repositorios. Las llamadas deben salir siempre desde su servidor. Si sospecha de una filtración, solicite su rotación de inmediato.

Su empresa la determina el token

No envíe empresa_id en el cuerpo de sus peticiones. La API deriva la identidad exclusivamente de su token. Si envía un empresa_id que no le corresponde, la petición se rechaza con 403 y el intento queda registrado.

Motivos de rechazo

SituaciónHTTPCódigo
Falta el header o el formato es incorrecto401UNAUTHENTICATED
La clave o el secreto no coinciden401UNAUTHENTICATED
El token expiró401UNAUTHENTICATED
Token suspendido o revocado403FORBIDDEN
Empresa sin API habilitada o suspendida403FORBIDDEN
empresa_id ajeno en el cuerpo403FORBIDDEN

Formato de respuesta

Todas las respuestas comparten la misma estructura. Puede escribir un único manejador para toda la API.

{
  "success": true,
  "data": { },
  "message": "Operación realizada correctamente",
  "request_id": "req_9f2c1ab34de5f6a7b8c9d0e1"
}
{
  "success": false,
  "error": {
    "code": "INSUFFICIENT_BALANCE",
    "message": "Saldo insuficiente para procesar la operación.",
    "details": { "saldo_disponible": 120.00, "monto_requerido": 500.00 }
  },
  "request_id": "req_9f2c1ab34de5f6a7b8c9d0e1"
}
Guarde siempre el request_id

Viaja además en el header X-Request-Id y aparece en nuestros registros. Con él, cualquier incidencia se localiza en segundos. Regístrelo junto a cada transacción en su sistema.

Cómo interpretar el resultado

  • Decida por el código HTTP y por error.code, nunca por el texto de message: los mensajes pueden cambiar.
  • 2xx no siempre es "cobrado": el 202 significa resultado pendiente.
  • 5xx y 422 no siempre son "no ocurrió": ante la duda, consulte la transacción.

Idempotencia

Es la parte más importante de esta documentación. Léala completa antes de escribir su integración.

POST /v1/recargas exige el header Idempotency-Key: una cadena única por operación, de hasta 128 caracteres. Se recomienda un UUID v4.

Idempotency-Key: 8fe9d681-a99e-4fda-b610-3c0a1f7e42bd

Qué garantiza

SituaciónRespuestaEfecto sobre el saldo
Primera vez con esa clave201 con la transacciónSe cobra una vez
Misma clave, mismo cuerpo, ya completada 201 + header Idempotent-Replay: true Ninguno — es la respuesta original
Misma clave, operación aún en curso409 IDEMPOTENT_REQUEST_IN_PROGRESS Ninguno — la original sigue su curso
Misma clave, cuerpo distinto422 VALIDATION_ERRORNinguno
Clave nueva201Se cobra otra vez — es otra operación

Las claves se conservan 24 horas. El ámbito es por comercio: dos clientes distintos pueden usar la misma cadena sin interferir.

Ante un timeout: reintente con la MISMA clave

Nunca genere una clave nueva para el mismo cobro. Una clave nueva es una operación nueva y su cliente recibirá dos recargas pagando una. Si su sistema genera la clave dentro del bucle de reintentos, tiene un error grave: la clave debe generarse una sola vez, antes del primer intento, y persistirse junto a la orden.

Patrón correcto

// CORRECTO — la clave se genera una vez y se guarda con la orden
orden.idempotency_key = orden.idempotency_key ?? uuidv4();
guardar(orden);

for (intento = 1; intento <= 3; intento++) {
    r = post('/v1/recargas', cuerpo, orden.idempotency_key);   // misma clave siempre
    if (r.status === 201) return exito(r);
    if (r.status === 409) { esperar(2000); continue; }           // sigue en curso
    if (r.status === 202) return consultarEstado(r);             // pendiente: NO reintentar
    if (r.status === 422) return fallo(r);                       // rechazo: saldo devuelto
}

// INCORRECTO — genera una clave por intento: cobra varias veces
for (intento = 1; intento <= 3; intento++) {
    post('/v1/recargas', cuerpo, uuidv4());                     // ✗ NUNCA
}

Manejo del saldo

Su saldo tiene tres componentes. Entenderlos evita la mayoría de las dudas de conciliación.

CampoSignificado
saldo_disponibleLo que puede gastar ahora mismo.
saldo_retenidoComprometido por operaciones en curso: ni cobrado ni gastable.
saldo_actualLa suma de ambos. Es el dinero que aún le pertenece.

Ciclo de una recarga

1
Reserva Antes de contactar al servicio, el importe pasa de disponible a retenido. Si no alcanza, la operación se rechaza y nunca llega al servicio.
2
Envío al servicio Su dinero está protegido: comprometido pero no cobrado.
3
Liquidación Éxito → sale de retenido y de actual: cobrado.
Rechazo → vuelve a disponible: no se cobró nada.
Desconocido → permanece en retenido hasta resolverse.
Nunca se entrega una recarga sin respaldo de saldo

Y nunca se cobra una recarga que la operación fue rechazada. Si ve saldo en retenido, corresponde a operaciones cuyo resultado se está confirmando; se resuelven automáticamente.

Límites y rate limit

Límites de consumo

Su cuenta puede tener topes por período, visibles en GET /v1/saldo. El valor 0 significa «sin límite».

PeríodoSe cuenta desde
DiarioLas 00:00 del día en curso
SemanalEl lunes de la semana en curso
MensualEl día 1 del mes en curso

Al superarlos recibirá 422 LIMIT_EXCEEDED, con el límite y lo consumido en details.

Rate limit

Por defecto 120 peticiones por minuto por token. Cada respuesta incluye:

X-RateLimit-Limit: 120
X-RateLimit-Remaining: 118
X-RateLimit-Reset: 1786568460

Al excederlo: 429 RATE_LIMIT_EXCEEDED con retry_after en segundos. Aplique espera exponencial; si reintenta una recarga, conserve la misma clave de idempotencia.

Endpoints · Sistema

GET/v1/estado Público
Estado del servicio. Úselo para monitoreo; no requiere credenciales.
curl -s https://api.recarga.do/v1/estado

{"success":true,"data":{"status":"ok"},"message":"Servicio operativo","request_id":"req_..."}
GET/v1/informacion Público
Versión y entorno de la API.
{
  "success": true,
  "data": {
    "application": "Recarga.do API",
    "api_version": "v1",
    "environment": "production",
    "documentation": "https://doc.recarga.do",
    "currency": "DOP",
    "timezone": "America/Santo_Domingo"
  },
  "message": "Información de la API",
  "request_id": "req_..."
}
GET/v1/estado/completo Autenticado
Comprueba las dependencias internas. Devuelve 503 si alguna está degradada.

Endpoints · Cuenta y saldo

GET/v1/cuenta Autenticado
Datos de su empresa y del token. Aquí confirma si está en pruebas o en producción.
{
  "success": true,
  "data": {
    "empresa_id": 370,
    "razon_social": "Comercial Ejemplo SRL",
    "codigo_empresa": "EMP-00370",
    "ambiente": "development",
    "sandbox": true,
    "token": { "nombre": "integracion-pos", "permisos": [] }
  },
  "message": "Cuenta consultada",
  "request_id": "req_..."
}

Compruebe sandbox al arrancar su aplicación y muéstrelo en su interfaz. Es la forma más simple de evitar que alguien crea que está probando cuando está gastando dinero real.

GET/v1/saldo Autenticado
Saldo, retenciones, límites y consumo por período.
{
  "success": true,
  "data": {
    "saldo_actual": 15400.00,
    "saldo_disponible": 15150.00,
    "saldo_retenido": 250.00,
    "tipo_credito": "prepago",
    "alerta_saldo_bajo": 5000.00,
    "limites":         { "diario": 50000.00, "semanal": 250000.00, "mensual": 1000000.00 },
    "consumo_periodo": { "diario": 3200.00,  "semanal": 18400.00,  "mensual": 61200.00 },
    "saldo_bajo": false,
    "moneda": "DOP",
    "actualizado_en": "2026-08-12 20:31:04"
  },
  "message": "Saldo consultado",
  "request_id": "req_..."
}

saldo_bajo se activa cuando el disponible cae por debajo de alerta_saldo_bajo: úselo para avisar a su equipo antes de quedarse sin fondos.

Endpoints · Recargas

POST/v1/recargas AutenticadoIdempotency-Key
Ejecuta una recarga y descuenta el importe de su saldo.

Headers

HeaderObligatorioDescripción
AuthorizationSíBearer <api_key>.<api_secret>
Idempotency-KeySíCadena única por operación, máx. 128 caracteres. UUID v4 recomendado.
Content-TypeSíapplication/json

Cuerpo

CampoTipoObligatorioDescripción
service_identeroSíObtenido de GET /v1/servicios.
identifierstringSíTeléfono, contrato o NIC según el servicio. 3–100 caracteres.
amountdecimalSí> 0, máx. 2 decimales, dentro del rango del servicio.
terminal_idstringNoReferencia de caja o terminal. Máx. 50 caracteres.

No envíe empresa_id: lo determina su token.

POST /v1/recargas HTTP/1.1
Host: api.recarga.do
Authorization: Bearer rk_live_xxx.sk_live_xxx
Idempotency-Key: 8fe9d681-a99e-4fda-b610-3c0a1f7e42bd
Content-Type: application/json

{
  "service_id": 12,
  "identifier": "8095551234",
  "amount": 100,
  "terminal_id": "CAJA-01"
}
HTTP/1.1 201 Created

{
  "success": true,
  "data": {
    "transaction_id": 48849,
    "status": "SUCCESS",
    "xid": "RD-b1d02d05e291c41716ff89bf",
    "auth_code": "884213",
    "service": { "id": 12, "nombre": "Claro Recarga" },
    "identifier": "8095551234",
    "amount": 100.00,
    "currency": "DOP",
    "message": "Recarga procesada exitosamente",
    "processed_at": "2026-08-12T21:11:26-04:00"
  },
  "message": "Recarga procesada exitosamente",
  "request_id": "req_eb4e618bfd13949169702e52"
}

El importe fue cobrado. Entregue el comprobante al cliente y guarde transaction_id y auth_code.

HTTP/1.1 202 Accepted

{
  "success": false,
  "error": {
    "code": "TRANSACTION_PENDING",
    "message": "La operación quedó en proceso. Consulte su estado antes de reintentar; el importe permanece retenido.",
    "details": {
      "transaction_id": 48861,
      "xid": "RD-b4812b7c6a84b3c2a2aed260",
      "status": "PENDING",
      "consultar_en": "/v1/transacciones/48861"
    }
  },
  "request_id": "req_6dca78bb8032673599c5e47a"
}
No lo trate como un fallo

La recarga pudo haberse entregado. El importe queda retenido, no cobrado. No reintente ni con la misma clave ni con una nueva: consulte GET /v1/transacciones/{id} hasta que pase a SUCCESS o FAILED. En su caja, muéstrelo como «verificando», nunca como «fallida».

HTTP/1.1 422 Unprocessable Entity

{
  "success": false,
  "error": { "code": "INVALID_PHONE", "message": "Número inválido." },
  "request_id": "req_2fe16c3c610ef66635a798ac"
}

La operación fue rechazada de forma definitiva. El importe ya fue devuelto a su saldo. Puede corregir los datos y volver a intentar con una clave nueva.

HTTP/1.1 409 Conflict

{
  "success": false,
  "error": {
    "code": "IDEMPOTENT_REQUEST_IN_PROGRESS",
    "message": "Ya hay una operación en curso con esta Idempotency-Key. Consulte su estado antes de reintentar."
  },
  "request_id": "req_..."
}

Otra petición con la misma clave se está ejecutando. Espere unos segundos y reintente con la misma clave, o consulte el historial por identifier.

Endpoints · Seguro de ley (Seguro Dominicana)

El seguro de ley de vehículos se vende en dos partes: unos pasos previos que no mueven dinero (datos del dueño, código SMS, versión, plan y cotización) y la venta por POST /v1/recargas, que cobra el monto_total cotizado. Todas las rutas requieren el permiso recharges.create y tienen alias en inglés (/v1/insurance, /otp/verify, /plans, /plan, /quote, /document).

Se habilita por comercio

Pídalo a Recarga.do. Mientras no esté habilitado, GET /v1/seguros responde disponible: false con motivo: "no_habilitado". Úselo para mostrar u ocultar el producto en su caja.

El flujo, paso a paso

PasoLlamadaEnvíaObtiene
0GET /v1/seguros—disponible, servicios[].service_id, motivo
1POST /v1/segurosservice_id, cedula (11 dígitos), telefono (10 dígitos, 809/829/849), placa (ej. A123456), terminal_id opcional201 con seguro_id, titular, vehículo y teléfono enmascarados. SMS con un código de 6 dígitos al dueño, vence en 10 min
1-bisPOST /v1/seguros/{id}/otp—Reenvía el código
2POST /v1/seguros/{id}/otp/verificarcodigomarca, modelo, version_unica, sugerida, versiones[] (id, nombre, sugerida). Máx. 5 intentos
3GET /v1/seguros/{id}/planes?version=…version = versiones[].id, codificada en la URLdetalle y planes[] (cd_tipo_plan, nombre, vigencia_meses, cobertura, precio_referencia, sugerido)
4POST /v1/seguros/{id}/planversion, cd_tipo_planplan y servicios_opcionales[] (grúa y casa del conductor, con precio). La grúa no aplica a motos
5POST /v1/seguros/{id}/cotizarincluye_grua, incluye_casa_conductormonto_total, opcion_id "seguro:{id}", valida_minutos: 30 y los datos del vehículo
6POST /v1/recargas + Idempotency-Keyservice_id, identifier = placa, opcion_id. Sin amount201 = póliza emitida; auth_code es el número de póliza
7GET /v1/seguros/{id}—estado, numero_poliza, vigencia y documentos
8GET /v1/seguros/{id}/documento?tipo=poliza|marbete—PDF (application/pdf) cuando estado es emitida

Todo el flujo en una sesión de terminal

# 0 · ¿Puedo venderlo? De aquí sale el service_id
curl -s https://api.recarga.do/v1/seguros -H "$AUTH"
# {"disponible":true,"servicios":[{"service_id":12,"nombre":"Seguro Dominicana"}],"motivo":null}

# 1 · Datos del dueño y del vehículo → SMS al dueño
curl -s -X POST https://api.recarga.do/v1/seguros -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"service_id":12,"cedula":"00112345678","telefono":"8095551234","placa":"A123456"}'
# {"seguro_id":381,"titular":"JUAN C*** R***","vehiculo":"TOYOTA C*** 2015","tipo_vehiculo":"AUTOMOVIL",
#  "tiene_renovacion":false,"placa":"A123456","telefono":"809-***-1234","otp_enviado":true,
#  "otp_expira_en":600,"mensaje":null}

# 2 · El código que dictó el dueño → versiones del vehículo
curl -s -X POST https://api.recarga.do/v1/seguros/381/otp/verificar -H "$AUTH" \
  -H "Content-Type: application/json" -d '{"codigo":"482913"}'
# {"seguro_id":381,"marca":"TOYOTA","modelo":"COROLLA","version_unica":false,"sugerida":"10|1050|20",
#  "versiones":[{"id":"10|1050|20","nombre":"COROLLA LE 1.8","sugerida":true}, ...]}

# 3 · Planes de esa versión ( | se codifica como %7C )
curl -s "https://api.recarga.do/v1/seguros/381/planes?version=10%7C1050%7C20" -H "$AUTH"

# 4 · Confirmar versión y plan → servicios opcionales con su precio
curl -s -X POST https://api.recarga.do/v1/seguros/381/plan -H "$AUTH" \
  -H "Content-Type: application/json" -d '{"version":"10|1050|20","cd_tipo_plan":3}'

# 5 · Cotizar: precio definitivo, válido 30 minutos
curl -s -X POST https://api.recarga.do/v1/seguros/381/cotizar -H "$AUTH" \
  -H "Content-Type: application/json" -d '{"incluye_grua":true,"incluye_casa_conductor":false}'
# {"seguro_id":381,"service_id":12,"opcion_id":"seguro:381","monto_total":3300.00,"moneda":"DOP",
#  "valida_minutos":30,"titular":"JUAN CARLOS RODRIGUEZ","placa":"A123456", ...}

# 6 · Vender: la placa como identifier, sin amount
curl -s -X POST https://api.recarga.do/v1/recargas -H "$AUTH" \
  -H "Idempotency-Key: $(uuidgen)" -H "Content-Type: application/json" \
  -d '{"service_id":12,"identifier":"A123456","opcion_id":"seguro:381"}'

# 7 · Póliza y PDF
curl -s https://api.recarga.do/v1/seguros/381 -H "$AUTH"
curl -s -o poliza.pdf "https://api.recarga.do/v1/seguros/381/documento?tipo=poliza" -H "$AUTH"

Desenlaces de la venta

RespuestaQué pasóQué hacer
201Póliza emitida y cobrada. auth_code = número de pólizaEntregue el comprobante y los PDF
202 TRANSACTION_PENDINGLa aseguradora aún no confirma. El importe queda retenido, no cobradoMuestre «verificando» y consulte GET /v1/transacciones/{id}. Se resuelve solo
422 SEGURO_NO_EMITIDONo se emitió. El importe ya volvió a su saldoLa venta queda cerrada: para intentarlo otra vez, empiece desde el paso 1
409 COTIZACION_VENCIDAPasaron más de 30 minutos desde la cotizaciónVuelva a cotizar y confirme el precio con el cliente
409 SEGURO_YA_VENDIDOEse seguro ya se vendió o se está vendiendoConsulte GET /v1/seguros/{id}. No lo venda otra vez
409 SEGURO_SIN_COTIZARFalta el paso 5Cotice primero
Sin respuestaDesconocidoReintente con la misma Idempotency-Key
Las reglas de dinero son las de cualquier venta

Un reintento con la misma Idempotency-Key nunca cobra dos veces: devuelve el estado real de la primera. Un 202 no es un fallo. La cotización vale 30 minutos y cada una se vende una sola vez. Si el vehículo ya tiene póliza vigente, cotizar responde 422 POLIZA_ACTIVA con la fecha de renovación en el mensaje.

La póliza

GET /v1/seguros/{id} devuelve seguro_id, estado (iniciada, otp_enviado, otp_validado, plan_confirmado, cotizado, cobrando, emitiendo, emitida, fallida), placa, monto_total, transaction_id, numero_poliza, titular, vehículo, plan, opcionales, vigencia_desde, vigencia_hasta y documentos (poliza y marbete, o null hasta la emisión). El PDF se descarga con /documento?tipo=poliza o ?tipo=marbete; antes de la emisión responde 409 POLIZA_NO_EMITIDA. El cliente también recibe el enlace por SMS de la aseguradora.

Los seguros no se anulan por la API (422 REVERSAL_NOT_SUPPORTED), y POST /v1/consultas con un servicio de seguro responde 501 indicando usar /v1/seguros.

Errores de los pasos previos

CódigoHTTPQué pasó
VALIDATION_ERROR422Cédula, teléfono o placa con formato inválido. details dice cuál
VEHICULO_NO_SOPORTADO422«Tipo de vehículo no soportado» por la aseguradora
SEGURO_RECHAZADO422La aseguradora rechazó los datos. Muestre el mensaje
OTP_INCORRECTO422Código equivocado o vencido
OTP_BLOQUEADO4095 intentos fallidos. Empiece de nuevo
SEGURO_PASO_INVALIDO409Paso fuera de orden
FORBIDDEN403El seguro no está habilitado para su comercio
SEGUROS_NO_DISPONIBLE503Venta de seguros no disponible por el momento

Endpoints · Transacciones

GET/v1/transacciones Autenticado
Historial paginado. Solo devuelve transacciones de su empresa.
ParámetroPor defectoDescripción
page1Número de página.
per_page50Entre 1 y 200.
status—SUCCESS, FAILED, REVERSED, PENDING, PROCESSING
date_from—Fecha YYYY-MM-DD, inclusive.
date_to—Fecha YYYY-MM-DD, inclusive.
identifier—Coincidencia exacta. Su mejor herramienta tras un timeout.
curl -s "https://api.recarga.do/v1/transacciones?identifier=8095551234&date_from=2026-08-12" -H "$AUTH"

{
  "success": true,
  "data": {
    "items": [ { "transaction_id": 48849, "status": "SUCCESS", "amount": 100.00, ... } ],
    "pagination": { "page": 1, "per_page": 50, "total": 1284, "total_pages": 26 }
  },
  "message": "1284 transacciones encontradas",
  "request_id": "req_..."
}
GET/v1/transacciones/{id} Autenticado
Detalle de una transacción. Una transacción ajena devuelve 404.
{
  "transaction_id": 48849,
  "xid": "RD-b1d02d05e291c41716ff89bf",
  "status": "SUCCESS",
  "service": { "id": 12, "nombre": "Claro Recarga" },
  "identifier": "8095551234",
  "amount": 100.00,
  "currency": "DOP",
  "auth_code": "884213",
  "reference": "RD-b1d02d05e291c41716ff89bf",
  "terminal_id": "CAJA-01",
  "message": "Recarga procesada exitosamente",
  "created_at": "2026-08-12 21:11:25",
  "processed_at": "2026-08-12 21:11:26"
}

Estados posibles

statusSignificadoSu saldo
SUCCESSEntregada y cobradaCobrado
FAILEDRechazada por el proveedorDevuelto
PENDINGResultado aún desconocidoRetenido
PROCESSINGEn curso hacia el proveedorRetenido
REVERSEDAnulada tras completarseDevuelto
GET/v1/transacciones/{id}/recibo Autenticado
Datos del recibo en JSON, para imprimir en su formato. Solo transacciones SUCCESS.
{
  "transaction_id": 48849,
  "xid": "RD-b1d02d05e291c41716ff89bf",
  "auth_code": "884213",
  "emisor": "Recarga.do",
  "comercio": "Comercial Ejemplo SRL",
  "servicio": "Claro Recarga",
  "identifier": "8095551234",
  "amount": 100.00,
  "currency": "DOP",
  "processed_at": "2026-08-12 21:11:26",
  "mensaje": "Recarga procesada exitosamente"
}

Códigos de error

Decida siempre por error.code, no por el texto del mensaje.

CódigoHTTPSignificadoQué hacer
UNAUTHENTICATED401Credenciales ausentes o inválidasRevise el header. No reintente en bucle.
FORBIDDEN403Token o empresa sin permisoContacte a soporte.
NOT_FOUND404Recurso inexistente o de otra empresaVerifique el identificador.
VALIDATION_ERROR422Parámetros inválidosRevise details. No reintente sin corregir.
INSUFFICIENT_BALANCE422Saldo disponible insuficienteDeposite. details trae disponible y requerido.
LIMIT_EXCEEDED422Límite del período superadoEspere al siguiente período o solicite ampliación.
IDEMPOTENT_REQUEST_IN_PROGRESS409Operación en curso con la misma claveEspere y reintente con la misma clave.
IDEMPOTENCY_CONFLICT409Clave irresolubleConsulte el historial; use una clave nueva solo si confirma que no se ejecutó.
RATE_LIMIT_EXCEEDED429Demasiadas peticionesEspera exponencial. Conserve la clave de idempotencia.
TRANSACTION_PENDING202Resultado desconocidoConsulte la transacción. No reintente.
INVALID_PHONE422Número inválidoCorrija y use una clave nueva. Saldo devuelto.
INVALID_PRODUCT422Servicio no válidoRefresque el catálogo.
INVALID_AMOUNT422Monto no admitidoRespete el rango del servicio.
DUPLICATE_TRANSACTION422Se detectó una operación duplicadaConsulte el historial antes de reintentar.
TRANSACTION_REJECTED422Rechazo genéricoSaldo devuelto. Vea el mensaje del servicio.
SERVICE_TIMEOUT422El servicio no respondióConsulte el estado antes de decidir.
SERVICE_UNAVAILABLE422Servicio no disponibleReintente con espera. Misma clave.
SEGURO_NO_EMITIDO422Seguro: la póliza no se emitióSaldo devuelto. Empiece el seguro de nuevo (Seguro de ley).
COTIZACION_VENCIDA409Seguro: la cotización tiene más de 30 minutosVuelva a cotizar.
SEGURO_YA_VENDIDO409Seguro: ya se vendió o se está vendiendoConsulte GET /v1/seguros/{id}. No lo venda otra vez.
POLIZA_ACTIVA422Seguro: el vehículo ya tiene póliza vigenteEl mensaje trae la fecha de renovación.
INTERNAL_ERROR500Error internoConsulte el estado y reporte el request_id.

Flujos recomendados

Venta en punto de venta

Cajero introduce número y monto
        │
        ├─ Validar contra el catálogo cacheado (rango de monto)
        ├─ Generar Idempotency-Key UUID v4  ← UNA sola vez
        ├─ Persistir la orden con esa clave  ← ANTES de llamar
        │
        ▼
POST /v1/recargas
        │
        ├─ 201 ─→ Imprimir recibo. Guardar transaction_id y auth_code.
        ├─ 202 ─→ Mostrar "VERIFICANDO". Consultar cada 10 s hasta 2 min.
        ├─ 409 ─→ Esperar 2 s y reintentar con LA MISMA clave.
        ├─ 422 ─→ Mostrar el motivo. El saldo ya volvió. Nueva clave si corrige.
        └─ timeout de red ─→ Reintentar con LA MISMA clave.

Resolver una operación en duda

¿Timeout, 202 o 500?
        │
        ├─ 1. Reintentar POST con LA MISMA Idempotency-Key
        │       └─ Si ya se ejecutó → 201 con Idempotent-Replay: true
        │
        ├─ 2. Si devuelve 409 → la original sigue en curso: esperar
        │
        └─ 3. Consultar GET /v1/transacciones?identifier=8095551234&date_from=HOY
                └─ Localiza la transacción aunque haya perdido el transaction_id
Nunca quedará en duda si persiste la clave

Guardar la Idempotency-Key junto a la orden, antes de la primera llamada, resuelve por sí solo casi todos los escenarios de red. Es la única pieza de estado que su sistema necesita conservar.

Conciliación diaria

  1. Al cierre, consulte GET /v1/transacciones?date_from=HOY&date_to=HOY paginando.
  2. Compare contra sus ventas registradas por transaction_id.
  3. Toda transacción en PENDING debe revisarse al día siguiente.
  4. El total de SUCCESS debe coincidir con lo descontado de su saldo.

Ejemplos de código

Implementaciones completas de una recarga, con manejo de los cuatro desenlaces posibles.

#!/usr/bin/env bash
set -euo pipefail

KEY="rk_sbx_8c4cacb52c6b8f65bf389480ce3f8891"
SECRET="sk_sbx_a8e4de1dbd5e135a4be191cfd8901f386e541279a6550b1d30af9fbeaa65bbd0"
AUTH="Authorization: Bearer $KEY.$SECRET"

# La clave se genera UNA vez y se guarda junto a la orden
IDEM=$(uuidgen)
echo "$IDEM" > /var/orders/orden-1234.key

RESP=$(curl -s -w '\n%{http_code}' -X POST https://api.recarga.do/v1/recargas \
  -H "$AUTH" -H "Idempotency-Key: $IDEM" -H "Content-Type: application/json" \
  -d '{"service_id":23,"identifier":"8095551235","amount":100}')

CODE=$(echo "$RESP" | tail -1)
BODY=$(echo "$RESP" | head -n -1)

case "$CODE" in
  201) echo "COBRADA: $(echo "$BODY" | jq -r .data.transaction_id)" ;;
  202) echo "PENDIENTE: consultar $(echo "$BODY" | jq -r .error.details.consultar_en)" ;;
  409) echo "EN CURSO: reintentar con LA MISMA clave $IDEM" ;;
  422) echo "RECHAZADA: $(echo "$BODY" | jq -r .error.code) — saldo devuelto" ;;
  *)   echo "REVISAR: $CODE / $(echo "$BODY" | jq -r .request_id)" ;;
esac
<?php

class RecargaDo
{
    public function __construct(
        private string $apiKey,
        private string $apiSecret,
        private string $base = 'https://api.recarga.do'
    ) {}

    public function recargar(int $servicioId, string $numero, float $monto, string $idemKey): array
    {
        $ch = curl_init($this->base . '/v1/recargas');
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST           => true,
            CURLOPT_HTTPHEADER     => [
                'Authorization: Bearer ' . $this->apiKey . '.' . $this->apiSecret,
                'Idempotency-Key: ' . $idemKey,
                'Content-Type: application/json',
            ],
            CURLOPT_POSTFIELDS => json_encode([
                'service_id' => $servicioId,
                'identifier' => $numero,
                'amount'     => $monto,
            ]),
            CURLOPT_CONNECTTIMEOUT => 5,
            CURLOPT_TIMEOUT        => 40,
        ]);

        $body   = curl_exec($ch);
        $status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        return ['status' => $status, 'body' => json_decode((string) $body, true) ?? []];
    }
}

// La clave se genera UNA vez y se persiste con la orden.
$orden->idem_key ??= sprintf('%s-%s', $orden->id, bin2hex(random_bytes(16)));
$orden->save();

$api = new RecargaDo(getenv('RECARGA_KEY'), getenv('RECARGA_SECRET'));
$r   = $api->recargar(23, '8095551235', 100.00, $orden->idem_key);

switch ($r['status']) {
    case 201:
        $orden->marcarCobrada($r['body']['data']['transaction_id']);
        break;

    case 202: // Desconocido: el importe está retenido, NO reintentar
        $orden->marcarVerificando($r['body']['error']['details']['transaction_id']);
        break;

    case 409: // La original sigue en curso: reintentar con LA MISMA clave
        $orden->programarReintento();
        break;

    case 422: // Rechazo definitivo: el saldo ya fue devuelto
        $orden->marcarFallida($r['body']['error']['code']);
        break;

    default:  // Ante la duda, verificar antes de decidir
        $orden->marcarVerificando(null);
}
import { randomUUID } from 'node:crypto';

const BASE = 'https://api.recarga.do';
const AUTH = `Bearer ${process.env.RECARGA_KEY}.${process.env.RECARGA_SECRET}`;

async function recargar({ serviceId, identifier, amount, idemKey }) {
  const res = await fetch(`${BASE}/v1/recargas`, {
    method: 'POST',
    headers: {
      'Authorization': AUTH,
      'Idempotency-Key': idemKey,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ service_id: serviceId, identifier, amount }),
    signal: AbortSignal.timeout(40_000),
  });
  return { status: res.status, body: await res.json() };
}

// La clave se genera UNA vez y se guarda con la orden.
orden.idemKey ??= randomUUID();
await orden.save();

let r;
try {
  r = await recargar({ serviceId: 23, identifier: '8095551235', amount: 100, idemKey: orden.idemKey });
} catch (e) {
  // Timeout de red: la recarga PUDO ejecutarse. Reintentar con LA MISMA clave.
  r = await recargar({ serviceId: 23, identifier: '8095551235', amount: 100, idemKey: orden.idemKey });
}

switch (r.status) {
  case 201:
    await orden.cobrada(r.body.data.transaction_id);
    break;
  case 202: // Retenido, no cobrado. Consultar; nunca reintentar con clave nueva.
    await orden.verificando(r.body.error.details.transaction_id);
    break;
  case 409: // Sigue en curso
    await orden.reintentarEn(2000);
    break;
  case 422: // Saldo ya devuelto
    await orden.fallida(r.body.error.code);
    break;
  default:
    await orden.verificando(null);
}
import os, uuid, requests

BASE = "https://api.recarga.do"
AUTH = f"Bearer {os.environ['RECARGA_KEY']}.{os.environ['RECARGA_SECRET']}"


def recargar(service_id: int, identifier: str, amount: float, idem_key: str):
    r = requests.post(
        f"{BASE}/v1/recargas",
        headers={
            "Authorization": AUTH,
            "Idempotency-Key": idem_key,
            "Content-Type": "application/json",
        },
        json={"service_id": service_id, "identifier": identifier, "amount": amount},
        timeout=(5, 40),
    )
    return r.status_code, r.json()


# La clave se genera UNA vez y se persiste con la orden.
if not orden.idem_key:
    orden.idem_key = str(uuid.uuid4())
    orden.save()

try:
    status, body = recargar(23, "8095551235", 100.00, orden.idem_key)
except requests.Timeout:
    # La recarga PUDO ejecutarse: se reintenta con LA MISMA clave.
    status, body = recargar(23, "8095551235", 100.00, orden.idem_key)

if status == 201:
    orden.cobrada(body["data"]["transaction_id"])
elif status == 202:
    # Importe retenido, no cobrado. Consultar, nunca reintentar con clave nueva.
    orden.verificando(body["error"]["details"]["transaction_id"])
elif status == 409:
    orden.reintentar_en(segundos=2)
elif status == 422:
    # Rechazo definitivo: el saldo ya fue devuelto.
    orden.fallida(body["error"]["code"])
else:
    orden.verificando(None)

Explorador OpenAPI

Especificación OpenAPI 3.0 completa. Puede probar cada endpoint desde aquí: pulse Autenticar e introduzca api_key.api_secret (con el punto en medio) para usar las credenciales de prueba.

Descargas y soporte

RecursoDescripción
openapi.yamlEspecificación OpenAPI 3.0. Genere un cliente en su lenguaje.
Colección de PostmanTodos los endpoints listos para importar, con el sandbox preconfigurado.
Guía de integraciónLa versión paso a paso, en español llano.

Documentación técnica interna

DocumentoContenido

Soporte

Al reportar una incidencia, incluya siempre el request_id de la respuesta y, si aplica, el transaction_id. Con eso localizamos la operación completa —incluida la conversación con el proveedor— en segundos.

Próximamente

  • POST /v1/transacciones/{id}/anular — anulación de recargas.
  • GET /v1/invoices y POST /v1/service-payments — consulta y pago de facturas.
  • Webhooks salientes para notificar cambios de estado sin necesidad de consultar.