03 — Transacciones
Una transacción representa un cobro capturado por Yape, Plin o BIM en uno de
los dispositivos vinculados al negocio. Requiere el alcance transactions:read
para consultar y transactions:write para confirmar o disputar.
Ciclo de vida
CAPTURED ──confirm──► CONFIRMED
│ │
dispute dispute
│ │
└────► DISPUTED ◄─────┘
CAPTURED: el parser extrajo la transacción de una notificación real. Estado inicial siempre.CONFIRMED: un operador (o tu integración, contransactions:write) verificó que el cobro corresponde a una venta.DISPUTED: se marcó como sospechosa o incorrecta, antes o después de confirmarla.VOIDED: anulada — no se puede transicionar aVOIDEDvía API pública, es un estado interno.
Confirmar una transacción que ya no está en CAPTURED, o disputar una que
ya está DISPUTED o VOIDED, responde 409 CONFLICT con el estado y la nota
actuales en details (ver confirm).
GET /v1/transactions
Lista paginada por cursor, con filtros compuestos.
curl "https://api.yallego.app/v1/transactions?wallet_code=YAPE&min_amount=20&limit=20" \
-H "Authorization: Bearer yk_live_..."
| Parámetro | Tipo | Descripción |
|---|---|---|
from / to | ISO 8601 | Rango sobre occurred_at |
wallet_code | string | YAPE, PLIN_BBVA, PLIN_INTERBANK, BIM |
device_id | uuid | Filtra por dispositivo de captura |
status | enum | CAPTURED, CONFIRMED, DISPUTED, VOIDED |
min_amount / max_amount | decimal | Rango de monto |
search | string | Nombre del remitente, código de seguridad o monto exacto (ver abajo) |
disputed_from / disputed_to | ISO 8601 | Rango sobre disputed_at (cuándo se reportó) |
disputed_by | uuid | Usuario que reportó (ver GET /v1/transactions/disputers) |
limit | int | 1–100, por defecto 50 |
cursor | string | Ver paginación |
search siempre busca en sender_name, sin distinguir tildes ni mayúsculas.
Además:
- Si son exactamente 3 dígitos (
247), también trae los cobros con esesecurity_code. - Si es un monto (
18.5,18.50,18,50,S/ 18.50,1,250.00), también trae los cobros de eseamountexacto.
Las coincidencias se suman: search=120 trae el cobro con código 120 y el de
S/ 120.00. Los demás filtros se aplican encima.
{
"data": [
{
"id": "9f8c2a1e-...",
"wallet": { "code": "YAPE", "display_name": "Yape" },
"sender_name": "JUAN CARLOS PEREZ R.",
"amount": "35.50",
"currency": "PEN",
"security_code": "247",
"approval_code": null,
"status": "CAPTURED",
"occurred_at": "2026-05-14T18:32:07.412Z",
"confirmed_at": null,
"confirmed_by": null,
"confirmed_by_name": null,
"disputed_at": null,
"disputed_by": null,
"disputed_by_name": null,
"note": null,
"device": { "id": "...", "label": "Celular caja principal" }
}
],
"pagination": { "has_more": true, "next_cursor": "eyJpZCI6...", "limit": 20 }
}
confirmed_byydisputed_bysonnullcuando quien confirmó o reportó fue una integración por clave de API en vez de un usuario del panel — la clave de API no tiene unuser_idque atribuir.*_by_namees el nombre de ese usuario. En cobros reportados antes de que existiera el registro,disputed_atpuede sernull.
La respuesta trae además time_zone, la zona horaria del negocio
(America/Lima), para mostrar occurred_at en su hora local.
Un wallet_code que no existe no es un error: la lista sale vacía. Un status
fuera del enum, un limit fuera de 1–100 o un parámetro mal formado responden
400 VALIDATION_ERROR con el mensaje genérico y el parámetro en
details.fields (ver errores de validación).
Paginación
Las transacciones vienen de la más reciente a la más antigua (occurred_at).
Para la página siguiente, repite la consulta con cursor=<pagination.next_cursor>
y los mismos filtros; cuando has_more es false, next_cursor es null. Un
cursor que no salió de esta ruta responde 400 VALIDATION_ERROR («El cursor de
paginación no es válido.»).
GET /v1/transactions/{id}
Detalle completo de una transacción individual. 404 NOT_FOUND si no existe,
pertenece a otro tenant o el id no es un UUID (lo mismo en confirm y
dispute).
POST /v1/transactions/{id}/confirm
curl -X POST https://api.yallego.app/v1/transactions/{id}/confirm \
-H "Authorization: Bearer yk_live_..." \
-H "Content-Type: application/json" \
-d '{"note": "Verificado contra el pedido #4821"}'
note es opcional, hasta 500 caracteres (se guarda sin espacios al inicio ni
al final). El cuerpo también es opcional: sin cuerpo equivale a {}. Un campo
que no sea note responde 400 VALIDATION_ERROR. Dispara el evento
transaction.confirmed a tus webhooks suscritos; el evento se registra junto
con el cambio de estado, así que sale aunque haya una caída justo después.
Acepta Idempotency-Key: si se corta la conexión, repite la solicitud con la
misma clave y recibes la respuesta original en vez de un 409. Ver
10 — Idempotencia.
Si la transacción ya no está en CAPTURED, el 409 CONFLICT dice cómo quedó.
Compara note con la tuya para saber si la confirmación fue tuya:
{
"error": {
"code": "CONFLICT",
"message": "Solo una transacción capturada puede confirmarse.",
"details": { "status": "CONFIRMED", "note": "Verificado contra el pedido #4821" },
"request_id": "b7f3a2e1-..."
}
}
POST /v1/transactions/{id}/dispute
Misma forma que confirm, incluida Idempotency-Key. Procede desde
CAPTURED o desde CONFIRMED: un cobro ya confirmado también se puede reportar.
Guarda disputed_at y disputed_by y dispara transaction.disputed. Una
transacción VOIDED o ya DISPUTED no puede volver a disputarse: 409 CONFLICT
con details.status y details.note.
GET /v1/transactions/counts
Cuántos cobros esperan una acción, de todo el historial: CAPTURED (por
confirmar) y DISPUTED (reportados). Útil para un contador en tu propio
sistema sin paginar el listado.
{
"by_status": { "CAPTURED": 5, "DISPUTED": 3 },
"disputed": {
"this_week": 2,
"this_month": 3,
"amount_this_month": "92.50",
"week_start": "2026-10-12T05:00:00.000Z",
"month_start": "2026-10-01T05:00:00.000Z",
"time_zone": "America/Lima"
}
}
disputed cuenta los cobros que siguen reportados según cuándo se reportaron
(disputed_at), en la zona horaria del negocio; la semana empieza el lunes.
week_start y month_start sirven tal cual como disputed_from para listar
esos mismos cobros.
GET /v1/transactions/disputers
Quiénes reportaron los cobros que siguen reportados, con cuántos reportes cada
uno, ordenados por nombre. Úsalo para ofrecer el filtro disputed_by. Los
reportes hechos con una clave de API no tienen persona y no aparecen.
{ "data": [{ "user_id": "5d1e…", "name": "Rosa Quispe", "count": 2 }] }
GET /v1/transactions/summary
Totales agregados — útil para un dashboard propio sin traer cada transacción individual.
curl "https://api.yallego.app/v1/transactions/summary?from=2026-05-01T00:00:00Z&to=2026-05-14T23:59:59Z" \
-H "Authorization: Bearer yk_live_..."
{
"period": { "from": "2026-05-01T00:00:00Z", "to": "2026-05-14T23:59:59Z" },
"totals": { "count": 1247, "amount": "42817.50", "currency": "PEN", "average": "34.34" },
"by_wallet": [
{ "wallet_code": "YAPE", "count": 981, "amount": "33420.00" },
{ "wallet_code": "PLIN_BBVA", "count": 266, "amount": "9397.50" }
],
"by_day": [{ "date": "2026-05-14", "count": 87, "amount": "2984.00" }],
"by_hour": [{ "hour": "2026-05-14T12", "count": 14, "amount": "512.00" }],
"by_device": [
{ "device_id": "6f1c…", "label": "Caja principal", "count": 1102, "amount": "38120.00" }
]
}
- Sin
fromnito, el resumen cubre los últimosdaysdías (de 1 a 30; 14 por omisión) contados en la zona del negocio, yperiod.daysdice cuántos. by_dayyby_hourusan la zona horaria del negocio (period.time_zone).hourtiene la formaAAAA-MM-DDTHHporque el rango puede cubrir varios días.- Las series sin prefijo incluyen todos los estados. Cada una tiene una variante
received_*(received_totals,received_by_wallet,received_by_day,received_by_hour,received_by_device) que cuenta solo los cobros recibidos:CAPTUREDyCONFIRMED. Los reportados (DISPUTED) y anulados (VOIDED) quedan fuera.confirmed_totalsyconfirmed_by_daycuentan soloCONFIRMED, yby_statusreparte el período por estado. by_deviceviene ordenado de mayor a menor monto.- El rango puede cubrir hasta 366 días. Si
fromes posterior ato, o el rango es más largo, la respuesta es400 VALIDATION_ERROR.
POST /v1/transactions/export
Devuelve un CSV en la misma respuesta (no es una exportación asíncrona: no hay
un trabajo que consultar después), con los mismos filtros que el listado —
pensado para descargas puntuales, no para integraciones que necesitan datos
estructurados (usa GET /v1/transactions con cursor para eso).
curl -X POST "https://api.yallego.app/v1/transactions/export?from=2026-05-01T00:00:00Z&status=CONFIRMED" \
-H "Authorization: Bearer yk_live_..." \
-o transacciones.csv
id,billetera,remitente,monto,moneda,codigo_seguridad,estado,ocurrio_en,dispositivo
9f8c2a1e-...,Yape,JUAN CARLOS PEREZ R.,35.50,PEN,247,CONFIRMED,2026-05-14T18:32:07.412Z,Celular caja principal
- Hasta 10 000 filas, de la más reciente a la más antigua. Si los filtros
abarcan más, el archivo trae las 10 000 más recientes sin avisar: acota con
fromytoy exporta por tramos. - Acepta los filtros del listado;
limitno tiene efecto ycursorexporta desde esa posición en adelante. - UTF-8, separado por comas, una fila por línea. Un valor con comas, comillas o
saltos de línea va entre comillas; uno que empieza con
=,+,-o@lleva un apóstrofo delante para que una hoja de cálculo no lo tome como fórmula. Viene conContent-Disposition: attachment; filename="transacciones-{comercio}.csv".
Con Idempotency-Key, repetir la descarga durante 24 h devuelve el mismo
archivo; los mismos filtros con otra clave generan uno nuevo.
Además del límite del plan, cada clave puede exportar hasta 10 veces por
minuto; la siguiente responde 429 con details.scope: "route" (ver
09 — Límites de tasa).