# 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 `