# Guía de integración — API Recarga.do v1
**Para quién es esto:** para el comercio que va a vender recargas desde su propio sistema, y
para el técnico que va a conectarlo. Punto de venta, app móvil, portal web, ERP o bot de
WhatsApp.
**Qué logrará al terminar:** su aplicación consultará saldo, mostrará el catálogo de servicios,
ejecutará recargas y consultará transacciones, todo desde su propio código y con su propia
interfaz, sin depender de ninguna pantalla de Recarga.do.
**Si usted no programa:** entregue esta guía a su técnico y quédese con cuatro decisiones que son
suyas, no de él:
1. **Cuánto saldo mantiene depositado, y quién vigila que no se acabe.** El modelo es prepago
([§4](#s4)).
2. **Qué hace la caja cuando una recarga queda «en verificación».** Es una política de negocio,
no un detalle técnico ([§6](#s6)).
3. **Qué hace la caja cuando una recarga «en verificación» se cobra un rato después.** Le puede
costar dinero si no lo decide antes ([§6](#s6)).
4. **Quién revisa al cierre las operaciones que quedaron pendientes** ([§6](#s6) y [§13](#s13)).
Si además quiere el camino corto —un punto de venta ya hecho o una librería de un solo
archivo—, empiece por [EMPIECE-AQUI.md](EMPIECE-AQUI.md).
### Datos fijos
| Dato | Valor |
|---|---|
| API | `https://api.recarga.do` |
| Moneda | DOP |
| Formato de los importes | **Pesos con hasta 2 decimales. No son centavos.** `100` es RD$100.00. Más de 2 decimales se rechaza con `422` |
| Zona horaria | `America/Santo_Domingo` (UTC−4) |
| Modelo | Prepago: usted deposita saldo y cada venta lo consume |
**¿No usa terminal ni escribe código?** Pruebe la API desde la consola interactiva de
[integracion.html](https://doc.recarga.do/integracion.html).
### Índice
| Sección | De qué trata |
|---|---|
| [1. Su primera recarga en 5 minutos](#s1) | El código que hay que ejecutar, y lo que devuelve |
| [2. No es una pantalla incrustada](#s2) | Qué NO hay que construir |
| [3. Cómo se conecta su aplicación](#s3) | Arquitectura, credenciales y qué guardar |
| [4. Cómo funciona el dinero](#s4) | Depósitos, saldo, comisión y aviso de saldo bajo |
| [5. La regla que evita cobrar dos veces](#s5) | Idempotencia, reintentos y sus límites |
| [6. Los desenlaces y qué hacer con cada uno](#s6) | Los cinco finales posibles de una venta |
| [6-ter. Seguro de ley (Seguro Dominicana)](#s6c) | Datos, código SMS, plan, cotización, venta y PDF de la póliza |
| [7. Probar sin gastar dinero](#s7) | El simulador y los escenarios de prueba |
| [8. Errores: qué significan y qué hacer](#s8) | Tabla completa de códigos |
| [9. Límites y buenas prácticas de consumo](#s9) | Rate limit, timeouts, caché y fechas |
| [10. Lista de comprobación para producción](#s10) | Antes de mover dinero real |
| [11. Cómo pasar a producción](#s11) | Qué pedir y qué verificar |
| [12. Qué no existe todavía](#s12) | Para que no lo diseñe contando con ello |
| [13. Soporte](#s13) | A quién escribir y con qué datos |
### Palabras que va a leer
| Término | Qué significa |
|---|---|
| *endpoint* | Una ruta de la API. Por ejemplo `GET /v1/saldo` |
| *token* / *credenciales* | El par `api_key` + `api_secret` que identifica a su empresa. En esta guía «el token» y «sus credenciales» son la misma cosa. Un token puede llevar permisos declarados y una lista de IPs autorizadas |
| *cabecera* (*header*) | Un dato que viaja junto a la petición o la respuesta, aparte del cuerpo. Por ejemplo `Authorization`. En esta guía se dice siempre «cabecera» |
| *cuerpo* o *payload* | El JSON que usted envía en un `POST` |
| *clave de idempotencia* (`Idempotency-Key`) | Un código único por venta que le dice a la API: «esta es la misma venta de antes, no la ejecutes otra vez». En esta guía, «clave» a secas siempre se refiere a esta, nunca al `api_key` |
| *UUID v4* | Identificador único universal: una cadena aleatoria que en la práctica nunca se repite. En Linux y macOS se genera con `uuidgen` |
| *sondear* | Consultar el estado de una operación cada cierto tiempo, hasta que cambie. Es lo que se hace con una recarga que queda pendiente |
| *sandbox* / *ambiente `development`* | El simulador. Son la misma cosa. En producción verá `ambiente: production` y `sandbox: false` |
| *el proveedor* | Quien entrega la recarga: la operadora (Claro, Altice, Viva, Digicel, Natcom) a través del mayorista con el que Recarga.do se conecta |
| *reconciliación* | El proceso automático de Recarga.do que corre **cada 5 minutos**, le pregunta al proveedor por cada operación sin desenlace y la cierra en cuanto hay una respuesta firme |
| `request_id` | El identificador de una llamada HTTP concreta. Viene en el cuerpo de toda respuesta y en la cabecera `X-Request-Id` |
- **Referencia de endpoints:** [API.md](API.md) · [openapi.yaml](openapi.yaml)
- **Manual con consola interactiva:** [integracion.html](https://doc.recarga.do/integracion.html)
---
## 1. SU PRIMERA RECARGA EN 5 MINUTOS
Las credenciales de abajo son públicas a propósito y apuntan al simulador.
```bash
# Credenciales públicas del ambiente de pruebas
KEY="rk_sbx_8c4cacb52c6b8f65bf389480ce3f8891"
SECRET="sk_sbx_a8e4de1dbd5e135a4be191cfd8901f386e541279a6550b1d30af9fbeaa65bbd0"
AUTH="Authorization: Bearer $KEY.$SECRET"
# 1 · ¿En qué ambiente estoy? → "ambiente":"development" y "sandbox":true = pruebas
curl -s https://api.recarga.do/v1/account -H "$AUTH"
# 2 · ¿Cuánto puedo gastar?
curl -s https://api.recarga.do/v1/saldo -H "$AUTH"
# 3 · ¿Qué puedo vender? De aquí salen service_id, monto_minimo y monto_maximo.
curl -sG https://api.recarga.do/v1/services --data-urlencode "categoria=Telefonía" -H "$AUTH"
# 4 · Recargar. La clave de idempotencia debe ser única por operación.
# Ojo: en el simulador el ÚLTIMO CARÁCTER del identifier decide el resultado.
# Este termina en 5, así que da éxito. Los demás escenarios, en la §7.
IDEM=$(uuidgen) # alternativa: IDEM=$(cat /proc/sys/kernel/random/uuid)
RESP=$(curl -s -X POST https://api.recarga.do/v1/recharges \
-H "$AUTH" \
-H "Idempotency-Key: $IDEM" \
-H "Content-Type: application/json" \
-d '{"service_id":1,"identifier":"8095551235","amount":100}')
echo "$RESP"
# 5 · Consultar la transacción por su id (el suyo, no el del ejemplo)
TX=$(printf '%s' "$RESP" | python3 -c "import json,sys; print(json.load(sys.stdin)['data']['transaction_id'])")
curl -s "https://api.recarga.do/v1/transactions/$TX" -H "$AUTH"
```
> **Antes de copiar y pegar.** Los ejemplos usan `bash` + `curl` (Linux, macOS o WSL —el Linux
> que corre dentro de Windows—). `uuidgen` no está en todas las distribuciones: la alternativa es
> `cat /proc/sys/kernel/random/uuid`. La línea con `python3` solo extrae un campo del JSON; si no
> tiene `python3`, copie el `transaction_id` a mano de la respuesta anterior. **En Windows sin
> WSL, use la [colección de Postman](recarga-do.postman_collection.json)**, que ya trae estas
> credenciales cargadas.
> **El sandbox no ejecuta recargas reales ni genera cargos reales, pero sí mueve el saldo de la
> cuenta de pruebas:** reserva, confirma y libera con la mecánica real. Esa cuenta es pública y
> compartida, así que otro lector pudo dejarla sin fondos. Si el paso 4 responde
> `422 INSUFFICIENT_BALANCE`, no es su código: pida a soporte una cuenta de pruebas propia
> ([§13](#s13)).
> **Sobre el paso 3:** `categoria` es el nombre exacto de la categoría, con su mayúscula inicial.
> Si su cliente HTTP no codifica la URL por usted, escríbala codificada
> (`?categoria=Telefon%C3%ADa`) o use `curl -G --data-urlencode`, como arriba. Lo que devuelve una
> lista vacía es un nombre que no coincida exactamente, no la tilde.
### Lo que devuelve el paso 3, recortado
```json
{
"success": true,
"data": [
{ "id": 1, "nombre": "CLARO", "tipo": "mobile_recharge", "categoria": "Telefonía",
"monto_minimo": 30, "monto_maximo": 2000, "acepta_anulacion": true },
{ "id": 2, "nombre": "ALTICE", "tipo": "mobile_recharge", "categoria": "Telefonía",
"monto_minimo": 30, "monto_maximo": 2000, "acepta_anulacion": true },
{ "id": 4, "nombre": "DIGICEL", "tipo": "mobile_recharge", "categoria": "Telefonía",
"monto_minimo": 50, "monto_maximo": 2000, "acepta_anulacion": false }
],
"message": "Servicios consultados",
"request_id": "req_…"
}
```
`monto_minimo` y `monto_maximo` son los que hay que respetar en el paso 4. Un monto fuera de ese
rango se rechaza con `422 VALIDATION_ERROR` ([§8](#s8)). El campo `acepta_anulacion` indica si
ese servicio admite anulación: si es `false`, anular se rechaza con
`422 REVERSAL_NOT_SUPPORTED`.
### Lo que devuelve el paso 4
```json
{
"success": true,
"data": {
"transaction_id": 48898,
"status": "SUCCESS",
"xid": "RD-b0816c81eb324d7967804f67",
"auth_code": "SBX804f67",
"service": { "id": 1, "nombre": "CLARO" },
"identifier": "8095551235",
"amount": 100,
"currency": "DOP",
"message": "Operación procesada exitosamente.",
"processed_at": "2026-08-12T21:57:20-04:00"
},
"message": "Recarga procesada exitosamente",
"request_id": "req_80b97e9294cf38701472f6d4"
}
```
### Lo que devuelven los pasos 1 y 2
```json
{
"success": true,
"data": {
"empresa_id": 370,
"razon_social": "SANDBOX API v1 - Pruebas automatizadas",
"codigo_empresa": "EMP-SBX01",
"ambiente": "development",
"sandbox": true,
"token": { "nombre": "sandbox-pruebas-v1", "permisos": [] }
},
"message": "Cuenta consultada",
"request_id": "req_dfd463719188d09c1ff89ec4"
}
```
`token.permisos` es una **lista**. Vacía significa que el token no tiene ninguna restricción, es
decir, acceso total ([§3](#s3)).
```json
{
"success": true,
"data": {
"saldo_actual": 98285,
"saldo_disponible": 98285,
"saldo_retenido": 0,
"tipo_credito": "prepago",
"alerta_saldo_bajo": 1000,
"saldo_bajo": false,
"limites": { "diario": 0, "semanal": 0, "mensual": 0 },
"consumo_periodo": { "diario": 2040, "semanal": 2040, "mensual": 2040 },
"actualizado_en": "2026-08-12 22:35:02",
"moneda": "DOP"
},
"message": "Saldo consultado",
"request_id": "req_dda459b27ee5fb7abffdb1ff"
}
```
### Identificadores de una operación
Guarde los cuatro primeros junto a la venta. Cada uno sirve para algo distinto:
| Campo | Qué es | Para qué sirve |
|---|---|---|
| `transaction_id` | El número de la operación dentro de Recarga.do | Consultar el estado: `GET /v1/transactions/{id}` |
| `xid` | El identificador con el que el proveedor ve la operación | Rastreo con el proveedor. Se deriva de su `Idempotency-Key`, por eso un reintento llega con el mismo `xid` y el proveedor puede detectar el duplicado |
| `auth_code` | El código de autorización que devuelve la operadora | Es el que se imprime en el recibo y el que reclama el cliente final. Solo existe si la operación quedó en `SUCCESS` |
| `request_id` | El identificador de esa llamada HTTP | **Es lo primero que le pedirá soporte.** Viene en el cuerpo y en la cabecera `X-Request-Id` |
| `reference` | El identificador que devolvió el proveedor, que hoy coincide con el `xid`. **No viene en la respuesta de la venta:** aparece al consultar la transacción | Cotejo con el proveedor |
### Autenticación, en una línea
```http
Authorization: Bearer .
```
El `api_key` y el `api_secret` separados por un punto. Alternativa equivalente, si su cliente
HTTP se lleva mal con `Authorization`:
```http
X-Api-Key:
X-Api-Secret:
```
**Su empresa la determina el token.** Nunca envíe `empresa_id` en el cuerpo: si lo hace y no
coincide con el de su token, la petición se rechaza con `403`.
### Los campos de una recarga
| Campo | Obligatorio | Notas |
|---|---|---|
| `service_id` | Sí | Entero, tomado de `GET /v1/services` |
| `identifier` | Sí | Cadena de 3 a 100 caracteres. **La API no valida el formato ni normaliza nada.** Envíe solo dígitos —10 posiciones en los teléfonos dominicanos—: un guion, un espacio o el prefijo `+1` se transmiten al proveedor tal cual y provocan un rechazo `422`. Menos de 3 o más de 100 caracteres sí se rechaza antes, con `422 VALIDATION_ERROR` |
| `amount` | Depende | **Solo si el servicio es de monto libre.** Mayor que cero, máximo 2 decimales, dentro del rango (`monto_minimo` / `monto_maximo`) |
| `opcion_id` | Depende | **Solo en paquetes y facturas.** El identificador que devolvió la consulta previa |
| `terminal_id` | No | Referencia de su caja o terminal, hasta 50 caracteres. Se guarda y se devuelve, pero **no se puede filtrar por él** ([§9](#s9)) |
En servicios que no son recarga móvil, el `identifier` es el número de contrato o el **NIC**
(*número de identificación del contrato* que usan las distribuidoras eléctricas).
### No todos los productos se venden igual
Esta es la parte que rompe más integraciones, así que vale la pena leerla despacio.
Una recarga de celular se vende indicando un monto: usted elige cuánto. Pero **un paquete
de datos y una factura no funcionan así**:
- Los **paquetes** disponibles dependen de la línea. Dos números de Claro pueden tener
ofertas distintas, así que no hay un catálogo fijo que usted pueda cachear: hay que
preguntárselo al operador para ese número concreto.
- Las **facturas** de luz, agua o teléfono pospago traen su monto ya fijado por la
distribuidora. Usted no elige cuánto pagar: paga lo que se debe.
Por eso esos productos se venden en **dos pasos**.
Cada servicio declara cuál le corresponde en `GET /v1/servicios`:
```json
{
"id": 50,
"nombre": "Claro Paquetes",
"flujo": "paquetes",
"requiere_consulta": true,
"como_comprar": "Consulte primero POST /v1/consultas para obtener los paquetes de ese número, y envíe el `opcion_id` elegido."
}
```
| `flujo` | Cuántos servicios | Paso 1 | Paso 2 |
|---|---|---|---|
| `monto_libre` | 44 | *(opcional)* consultar el titular | `POST /v1/recargas` con `amount` |
| `paquetes` | 5 | `POST /v1/consultas` → paquetes de ese número | `POST /v1/recargas` con `opcion_id` |
| `facturas` | 9 | `POST /v1/consultas` → facturas de ese contrato | `POST /v1/recargas` con `opcion_id` |
| `seguros` | 1 | `POST /v1/seguros` → código SMS, versión, plan y cotización ([§6-ter](#s6c)) | `POST /v1/recargas` con la placa y `opcion_id` `seguro:{id}` |
#### El paso 1: consultar
```bash
curl -X POST https://api.recarga.do/v1/consultas \
-H "$AUTH" -H "Content-Type: application/json" \
-d '{"service_id":50,"identifier":"8094964444"}'
```
```json
{
"flujo": "paquetes",
"titular": null,
"requiere_seleccion": true,
"opciones": [
{ "opcion_id": "CL-D30", "nombre": "Paquetito Diario 1GB", "precio": 30.00, "vigencia": "1 día", "datos": "1 GB" },
{ "opcion_id": "CL-M700", "nombre": "Mensual Ilimitado", "precio": 700.00, "vigencia": "30 días", "datos": "25 GB" }
]
}
```
La respuesta tiene la **misma forma** para los tres flujos, así que su caja puede escribir
un solo manejador: mire `flujo`, y si `requiere_seleccion` es `true`, muestre `opciones`.
En facturas, `opciones` trae las facturas pendientes y `titular` el nombre del cliente —
útil para que el cajero confirme antes de cobrar. Si el contrato está al día, recibirá
`200` con `sin_deuda: true` y `opciones` vacío: **eso no es un error**, es un cliente que
no debe nada.
#### El paso 2: comprar
```bash
curl -X POST https://api.recarga.do/v1/recargas \
-H "$AUTH" -H "Idempotency-Key: $(uuidgen)" -H "Content-Type: application/json" \
-d '{"service_id":50,"identifier":"8094964444","opcion_id":"CL-M700"}'
```
Fíjese en que **no se envía `amount`**. El precio lo pone el operador, y la API lo vuelve
a consultar antes de cobrar. Si usted envía un `amount` que no coincide, la operación se
rechaza:
```json
{
"error": {
"code": "VALIDATION_ERROR",
"message": "El precio de ese paquete es RD$700.00, no RD$1.00. Omita `amount`: lo fija el operador.",
"details": { "amount": "no_coincide", "precio_real": 700 }
}
}
```
#### Si se equivoca de flujo
No se queda adivinando: el error le dice exactamente qué hacer.
```json
{
"error": {
"code": "VALIDATION_ERROR",
"message": "\"Claro Paquetes\" se vende por paquetes, no por monto libre. Consulte primero POST /v1/consultas...",
"details": {
"opcion_id": "requerido",
"flujo": "paquetes",
"consultar_en": "/v1/consultas",
"como_comprar": "Consulte primero POST /v1/consultas para obtener los paquetes de ese número, y envíe el `opcion_id` elegido."
}
}
}
```
> **El sandbox reproduce esto igual que producción.** Un identificador terminado en `0`
> no existe, en `1` no tiene opciones —línea sin paquetes o contrato al día—, y en `2` el
> proveedor no responde. Pruebe los tres antes de salir a producción.
#### Ver los paquetes y las facturas reales mientras prueba
Por defecto el sandbox devuelve un catálogo de ejemplo. Sirve para programar contra la
forma de los datos, pero los nombres y precios no son los que verá su cajero.
Si necesita los reales, pida que activen la capacidad **`consultas_reales`** sobre su
token. A partir de ahí, `POST /v1/consultas` le devolverá los paquetes que ese número
tiene de verdad y las facturas que ese contrato debe de verdad.
El campo `origen_datos` de cada respuesta le dice qué está viendo:
```json
{ "origen_datos": "proveedor_real", "opciones": [ { "opcion_id": "11685", "nombre": "LIBRE 1 DIA", "precio": 50.00 } ] }
```
| `origen_datos` | Significado |
|---|---|
| `proveedor_real` | Consultado al operador |
| `simulado` | Catálogo de ejemplo del ambiente de pruebas |
Tres cosas que conviene tener claras:
1. **Solo cambia la consulta.** La compra sigue simulada y su saldo sigue siendo
ficticio. Nunca se ejecuta una recarga real desde una cuenta de pruebas: lo reconocerá
porque el `auth_code` empieza por `SBX`.
2. **No viene activada en las credenciales públicas** de esta documentación, y no se
concede por defecto. La consulta de facturas devuelve nombre, dirección y deuda de un
cliente real; abrirla a cualquiera sería publicar datos de terceros.
3. **Tiene su propio límite** —30 por minuto, 2,000 por día— porque cada consulta consume
cuota del operador. Al superarlo la consulta no falla: cae al simulador y lo declara en
`origen_datos`.
---
## 2. NO ES UNA PANTALLA INCRUSTADA
Esto es lo que más confusión genera al empezar.
**Recarga.do se integra por consulta directa a su API.** Su aplicación hace peticiones HTTPS a
`https://api.recarga.do`, recibe JSON, y hace con ese JSON lo que quiera. Nada más.
| No se necesita | Por qué |
|---|---|
| Incrustar un `