Convenciones
Base URL
https://api.pay.ingalca.comTodos los endpoints viven bajo /v1/. La URL es la misma para test y live; el entorno lo define la API key (ver entornos).
Estructura de respuesta
Éxito:
{ "data": { ... }}Listado:
{ "data": [...], "meta": { "next_cursor": "eyJpZCI6MTIzfQ", "prev_cursor": null, "per_page": 25 }}Error:
{ "error": { "code": "VALIDATION_FAILED", "message": "...", "details": { /* opcional */ } }}IDs públicos
Cada recurso tiene un ID público con prefijo:
| Recurso | Prefijo | Ejemplo |
|---|---|---|
| Payment | pay_ | pay_abc123def456 |
| Card | card_ | card_xyz789abc012 |
| Customer | cust_ | cust_qrs456tuv789 |
| Webhook | evt_ | evt_a1b2c3d4e5f6 |
Los IDs no son enumerables (random base32-like). Asumí que pueden cambiar de longitud en el futuro — no los parsees.
Paginación
Todos los listados usan cursor pagination. NO hay ?page=N.
GET /v1/payments?cursor=eyJpZCI6MTIzfQ&per_page=50La respuesta trae meta.next_cursor (más resultados) y meta.prev_cursor (página anterior). Si next_cursor es null, llegaste al final.
Filtros
Los listados aceptan filtros como query params. Filtros vacíos se ignoran.
GET /v1/payments?status=confirmed&provider=dinelco&from=2026-05-01&to=2026-05-31Cada endpoint documenta sus filtros disponibles.
Fechas
Todas las fechas en respuestas son ISO 8601 con timezone:
2026-05-02T14:32:11+00:00Para filtros, aceptamos YYYY-MM-DD (interpretado como Paraguay) o ISO 8601 completo.
Montos
Los montos van en enteros representando la unidad mínima de la moneda. Para PYG (sin decimales), el monto es directamente la cantidad de guaraníes:
{ "amount": 150000, "currency": "PYG"}Esto son ₲ 150.000.
Hoy soportamos solo PYG. Si en el futuro agregamos USD, los amount van a ser en centavos (amount: 1000 = $10.00).
Idempotencia
Los POST que crean un cobro son idempotentes por reference. La reference que enviás es la clave de idempotencia: identifica de forma única a la operación dentro de tu cuenta y entorno (test/live).
Aplica a:
POST /v1/cards/{card_id}/charge— cobro tokenizado.POST /v1/payments— creación de pago.
Cómo funciona
- Reenvío del mismo request (misma
reference, mismo body) → devolvemos el pago original (mismoid), sin ejecutar un segundo cobro. La respuesta es200(en vez de201) para que distingas un replay de un cobro nuevo. - Misma
referencecon un body distinto (otroamount,card_id,currencyoprovider) →409 REFERENCE_ALREADY_USED. No cobramos: es la señal de que reusaste unareferencepara otra operación. - Es seguro ante concurrencia: dos requests simultáneos con la misma
referenceresuelven a un solo cobro.
El replay devuelve el resultado ORIGINAL — incluso si falló
Si el cobro original terminó failed o rejected, un reenvío con la misma reference te devuelve ese resultado fallido — no reintenta el cobro. Es la semántica estándar de idempotencia.
Para reintentar de verdad una operación fallida, usá una reference nueva. Una reference = una operación, para siempre.
Por qué reintentar es seguro
Ante un timeout de red podés reenviar el mismo request sin miedo a un doble cobro: si el primero llegó y cobró, el reenvío te devuelve ese pago; si no llegó, el reenvío lo procesa por primera vez.
Ojo con el replay: los campos del pago son los del original
En una respuesta 200 (replay), todos los campos del pago son los del cobro original — no de esta llamada. Concretamente:
id,status,amount,metadata,customer→ los del pago que ya existía.created_at,confirmed_at,failed_at→ timestamps de cuando pasó de verdad, hace horas o días.provider_data→ los datos del provider en el cobro original (mismosoperation_number,authorization_number, etc.).
Impacto práctico para tu UI: si tu app pinta “pago confirmado el X” y estás en modo replay, X no es ahora — es cuando el pago se cobró originalmente. Distinguí el 200 del 201 para no mostrarle al operador “pago recién confirmado” cuando en realidad ya fue confirmado hace rato. Y para no marcarle al cliente que le acabaste de cobrar cuando el cobro ya sucedió y notificaste hace tiempo.
Headers comunes
| Header | Para qué |
|---|---|
Authorization | Bearer token (tu API key). Obligatorio en /v1/*. |
Content-Type | application/json para POST/PUT con body JSON. |
Idempotency-Key | No se usa. La idempotencia es por reference (ver Idempotencia). |
HTTP status codes
| Code | Significado |
|---|---|
| 200 | OK |
| 201 | Recurso creado |
| 204 | OK sin body |
| 400 | Request mal formado |
| 401 | API key inválida o ausente |
| 403 | API key válida pero sin permisos para esta acción |
| 404 | Recurso no existe (o pertenece a otro tenant) |
| 409 | Conflicto (ej. reference ya usada para otra operación) |
| 422 | Validación falló (mirar error.details.fields) |
| 429 | Rate limit superado |
| 500 | Error interno (deberíamos verlo nosotros antes que vos) |
| 502 | Error del provider externo (Dinelco / Bancard) |
| 503 | Servicio temporalmente no disponible |