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.
| Concepto | Detalle |
|---|---|
| Modelo | Prepago. Usted mantiene un saldo y cada operación lo descuenta. |
| Protocolo | REST sobre HTTPS. Peticiones y respuestas en JSON UTF-8. |
| Autenticación | Par api_key / api_secret por header. |
| Moneda | Peso dominicano (DOP). Montos con hasta 2 decimales. |
| Zona horaria | America/Santo_Domingo (UTC−4). |
| Servicios | Telefonía, paquetes de datos, electricidad, agua, internet y TV, entretenimiento y más. |
Cada operación financiera exige una clave de idempotencia, y el importe se reserva antes de enviarse al proveedor. 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.
GET /v1/account y confirme si su token está en
development (pruebas) o production (dinero real).GET /v1/balance le indica cuánto puede gastar ahora mismo.GET /v1/services devuelve el service_id
y el rango de montos de cada servicio.POST /v1/recharges con un Idempotency-Key único.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/account -H "$AUTH" # 3 · ¿Cuánto saldo tengo? curl -s https://api.recarga.do/v1/balance -H "$AUTH" # 4 · ¿Qué servicios puedo vender? curl -s https://api.recarga.do/v1/services -H "$AUTH" # 5 · Recargar (la clave debe ser única por operación) curl -s -X POST https://api.recarga.do/v1/recharges \ -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/transactions/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.
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 en | HTTP | Código | Qué ocurre con su saldo |
|---|---|---|---|
0 — ej. 8095550000 | 502 | INVALID_PHONE |
Se reserva y se devuelve completo |
1 — ej. 8095550001 | 502 | TRANSACTION_REJECTED |
Se reserva y se devuelve completo |
2 — ej. 8095550002 | 202 | TRANSACTION_PENDING |
Queda retenido — ni cobrado ni disponible |
cualquier otro — ej. 8095551235 | 201 | SUCCESS |
Se cobra |
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
- Solicite sus credenciales de producción a su ejecutivo de cuenta.
- Confirme con
GET /v1/accountque devuelve"sandbox": false. - Deposite saldo. Verifique con
GET /v1/balance. - 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.
Probar la API
Envíe la misma recarga dos veces sin tocar el campo Idempotency-Key: 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>
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ón | HTTP | Código |
|---|---|---|
| Falta el header o el formato es incorrecto | 401 | UNAUTHENTICATED |
| La clave o el secreto no coinciden | 401 | UNAUTHENTICATED |
| El token expiró | 401 | UNAUTHENTICATED |
| Token suspendido o revocado | 403 | FORBIDDEN |
| Empresa sin API habilitada o suspendida | 403 | FORBIDDEN |
empresa_id ajeno en el cuerpo | 403 | FORBIDDEN |
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": "Balance insuficiente para procesar la operación.",
"details": { "saldo_disponible": 120.00, "monto_requerido": 500.00 }
},
"request_id": "req_9f2c1ab34de5f6a7b8c9d0e1"
}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 demessage: los mensajes pueden cambiar. 2xxno siempre es "cobrado": el202significa resultado pendiente.5xxy502no 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/recharges 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ón | Respuesta | Efecto sobre el saldo |
|---|---|---|
| Primera vez con esa clave | 201 con la transacción | Se 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 curso | 409 IDEMPOTENT_REQUEST_IN_PROGRESS |
Ninguno — la original sigue su curso |
| Misma clave, cuerpo distinto | 422 VALIDATION_ERROR | Ninguno |
| Clave nueva | 201 | Se 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.
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/recharges', 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 === 502) return fallo(r); // rechazo: saldo devuelto } // INCORRECTO — genera una clave por intento: cobra varias veces for (intento = 1; intento <= 3; intento++) { post('/v1/recharges', cuerpo, uuidv4()); // ✗ NUNCA }
Manejo del saldo
Su saldo tiene tres componentes. Entenderlos evita la mayoría de las dudas de conciliación.
| Campo | Significado |
|---|---|
saldo_disponible | Lo que puede gastar ahora mismo. |
saldo_retenido | Comprometido por operaciones en curso: ni cobrado ni gastable. |
saldo_actual | La suma de ambos. Es el dinero que aún le pertenece. |
Ciclo de una recarga
disponible
a retenido. Si no alcanza, la operación se rechaza y nunca llega al proveedor.retenido y de
actual: cobrado.Rechazo → vuelve a
disponible: no se cobró nada.Desconocido → permanece en
retenido hasta resolverse.Y nunca se cobra una recarga que el proveedor rechazó. 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/balance.
El valor 0 significa «sin límite».
| Período | Se cuenta desde |
|---|---|
| Diario | Las 00:00 del día en curso |
| Semanal | El lunes de la semana en curso |
| Mensual | El 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
curl -s https://api.recarga.do/v1/health
{"success":true,"data":{"status":"ok"},"message":"Servicio operativo","request_id":"req_..."}{
"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_..."
}Endpoints · Cuenta y saldo
{
"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.
{
"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": "Balance 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 · Catálogo
[
{ "id": 1, "nombre": "Telefonía", "servicios": 7 },
{ "id": 2, "nombre": "Electricidad","servicios": 10 },
{ "id": 3, "nombre": "Agua", "servicios": 5 }
]Parámetros
| Parámetro | Tipo | Descripción |
|---|---|---|
categoria | string | Nombre exacto de la categoría. Ej. Telefonía |
q | string | Búsqueda parcial por nombre. Ej. claro |
curl -s "https://api.recarga.do/v1/services?categoria=Telefon%C3%ADa" -H "$AUTH"
[{
"id": 12,
"nombre": "Claro Recarga",
"descripcion": "Recarga de balance Claro",
"tipo": "standard",
"categoria": "Telefonía",
"categoria_id": 1,
"monto_minimo": 20.00,
"monto_maximo": 5000.00,
"acepta_anulacion": true
}]Cambia con poca frecuencia. Refrésquelo cada pocas horas y respete siempre
monto_minimo y monto_maximo:
validar del lado de su aplicación evita un viaje de ida y vuelta y una mala
experiencia en la caja. El valor 0 significa «sin límite por ese lado».
[{
"id": 5, "codigo": "CLARO_D1", "nombre": "Paquete diario 1GB",
"descripcion": "1GB por 24 horas", "precio": 60.00,
"duracion": "1 día", "datos": "1GB", "minutos": 30, "sms": 50
}]Endpoints · Recargas
Headers
| Header | Obligatorio | Descripción |
|---|---|---|
Authorization | Sí | Bearer <api_key>.<api_secret> |
Idempotency-Key | Sí | Cadena única por operación, máx. 128 caracteres. UUID v4 recomendado. |
Content-Type | Sí | application/json |
Cuerpo
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
service_id | entero | Sí | Obtenido de GET /v1/services. |
identifier | string | Sí | Teléfono, contrato o NIC según el servicio. 3–100 caracteres. |
amount | decimal | Sí | > 0, máx. 2 decimales, dentro del rango del servicio. |
terminal_id | string | No | Referencia de caja o terminal. Máx. 50 caracteres. |
No envíe empresa_id: lo determina su token.
POST /v1/recharges 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",
"provider_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/transactions/48861"
}
},
"request_id": "req_6dca78bb8032673599c5e47a"
}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/transactions/{id} hasta que pase a
SUCCESS o FAILED. En su caja,
muéstrelo como «verificando», nunca como «fallida».
HTTP/1.1 502 Bad Gateway
{
"success": false,
"error": { "code": "INVALID_PHONE", "message": "Número inválido." },
"request_id": "req_2fe16c3c610ef66635a798ac"
}El proveedor rechazó la operación 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 · Transacciones
| Parámetro | Por defecto | Descripción |
|---|---|---|
page | 1 | Número de página. |
per_page | 50 | Entre 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/transactions?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_..."
}{
"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
| status | Significado | Su saldo |
|---|---|---|
SUCCESS | Entregada y cobrada | Cobrado |
FAILED | Rechazada por el proveedor | Devuelto |
PENDING | Resultado aún desconocido | Retenido |
PROCESSING | En curso hacia el proveedor | Retenido |
REVERSED | Anulada tras completarse | Devuelto |
{
"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ódigo | HTTP | Significado | Qué hacer |
|---|---|---|---|
UNAUTHENTICATED | 401 | Credenciales ausentes o inválidas | Revise el header. No reintente en bucle. |
FORBIDDEN | 403 | Token o empresa sin permiso | Contacte a soporte. |
NOT_FOUND | 404 | Recurso inexistente o de otra empresa | Verifique el identificador. |
VALIDATION_ERROR | 422 | Parámetros inválidos | Revise details. No reintente sin corregir. |
INSUFFICIENT_BALANCE | 422 | Saldo disponible insuficiente | Deposite. details trae disponible y requerido. |
LIMIT_EXCEEDED | 422 | Límite del período superado | Espere al siguiente período o solicite ampliación. |
IDEMPOTENT_REQUEST_IN_PROGRESS | 409 | Operación en curso con la misma clave | Espere y reintente con la misma clave. |
IDEMPOTENCY_CONFLICT | 409 | Clave irresoluble | Consulte el historial; use una clave nueva solo si confirma que no se ejecutó. |
RATE_LIMIT_EXCEEDED | 429 | Demasiadas peticiones | Espera exponencial. Conserve la clave de idempotencia. |
TRANSACTION_PENDING | 202 | Resultado desconocido | Consulte la transacción. No reintente. |
INVALID_PHONE | 502 | Número inválido | Corrija y use una clave nueva. Saldo devuelto. |
INVALID_PRODUCT | 502 | Servicio no válido | Refresque el catálogo. |
INVALID_AMOUNT | 502 | Monto no admitido | Respete el rango del servicio. |
DUPLICATE_TRANSACTION | 502 | El proveedor la considera duplicada | Consulte el historial antes de reintentar. |
TRANSACTION_REJECTED | 502 | Rechazo genérico | Saldo devuelto. Vea el mensaje del proveedor. |
PROVIDER_TIMEOUT | 502 | El proveedor no respondió | Consulte el estado antes de decidir. |
PROVIDER_UNAVAILABLE | 502 | Sin comunicación con el proveedor | Reintente con espera. Misma clave. |
INTERNAL_ERROR | 500 | Error interno | Consulte 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/recharges
│
├─ 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.
├─ 502 ─→ 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/transactions?identifier=8095551234&date_from=HOY
└─ Localiza la transacción aunque haya perdido el transaction_idGuardar 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
- Al cierre, consulte
GET /v1/transactions?date_from=HOY&date_to=HOYpaginando. - Compare contra sus ventas registradas por
transaction_id. - Toda transacción en
PENDINGdebe revisarse al día siguiente. - El total de
SUCCESSdebe 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/recharges \
-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" ;;
502) 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/recharges');
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 502: // 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/recharges`, {
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 502: // 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/recharges",
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 == 502:
# 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
Authorize e introduzca api_key.api_secret
(con el punto en medio) para usar las credenciales de prueba.
Descargas y soporte
| Recurso | Descripción |
|---|---|
| openapi.yaml | Especificación OpenAPI 3.0. Genere un cliente en su lenguaje. |
| Colección de Postman | Todos los endpoints listos para importar, con el sandbox preconfigurado. |
| API.md | Esta referencia en Markdown. |
| Reglas de negocio | Saldo, límites, estados e idempotencia en detalle. |
Documentación técnica interna
| Documento | Contenido |
|---|---|
| Análisis del sistema | Arquitectura, módulos, integraciones y deuda técnica. |
| Arquitectura de la API | Diseño de la v1 y el porqué de cada decisión. |
| Auditoría de seguridad | Hallazgos clasificados con evidencia. |
| Esquema de datos | Tablas, relaciones y anomalías. |
| Despliegue | Configuración, migraciones y monitoreo. |
| Remediaciones | Acciones pendientes sobre el sistema existente. |
| Control de cambios | Qué cambió, cuándo y con qué impacto. |
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/transactions/{id}/reverse— anulación de recargas.GET /v1/invoicesyPOST /v1/service-payments— consulta y pago de facturas.- Webhooks salientes para notificar cambios de estado sin necesidad de consultar.