{"openapi":"3.0.0","info":{"title":"144 Pay API","version":"1.1.0","description":"API REST de la pasarela 144 Pay. Soporta dos productos sobre USDT (Polygon, BSC):\n\n- **Cobros**: el comercio crea un cobro y recibe una dirección de depósito desechable. Al confirmarse el pago, los fondos se barren a la hot wallet y se distribuyen según la configuración de splits.\n- **Dispersiones**: el comercio define una lista de destinatarios y montos. La pasarela genera una wallet desechable, espera el depósito y ejecuta un contrato `Disperse` que envía a todos los destinatarios en una sola transacción on-chain (~50% menos gas que N transferencias).\n\n## Webhooks\nLa pasarela notifica los cambios de estado mediante un POST al `webhook_url` del comercio. El cuerpo va firmado con HMAC-SHA256 en el header `X-Signature` (formato `sha256=<hex>`); verifícalo con el `webhook_secret` del comercio.\n\n**Eventos de cobros**: `payment.completed`, `payment.underpaid`, `payment.overpaid`, `payment.expired`, `payment.failed`.\n\n**Eventos de dispersiones**: `dispersion.deposit_received`, `dispersion.completed`, `dispersion.underpaid`, `dispersion.failed`, `dispersion.expired`.\n\nLos webhooks son at-least-once: tu endpoint debe tolerar entregas duplicadas."},"tags":[{"name":"Cobros","description":"Creación y consulta de cobros"},{"name":"Dispersiones","description":"Payouts batch: envía a N destinatarios en una sola tx on-chain vía contrato Disperse (~50% menos gas)"},{"name":"Sistema","description":"Estado del servicio"}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"X-API-Key","description":"API key del comercio, con prefijo pk_live_"}},"schemas":{"Payment":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"pi_3f9a..."},"reference":{"type":"string"},"status":{"type":"string","enum":["PENDING","AWAITING_PAYMENT","DETECTED","CONFIRMING","CONFIRMED","OVERPAID","UNDERPAID","SWEEPING","SWEPT","DISTRIBUTING","COMPLETED","EXPIRED","FAILED"],"description":"Estado del cobro"},"chain":{"type":"string","enum":["polygon","bsc"],"description":"Red de la blockchain","example":"polygon"},"token":{"type":"string","enum":["USDT"],"description":"Símbolo del token","example":"USDT"},"depositAddress":{"type":"string","description":"Dirección desechable donde enviar el pago"},"expectedAmount":{"type":"string","description":"Monto esperado, unidades humanas"},"expectedAmountRaw":{"type":"string","description":"Monto esperado, unidad mínima del token"},"receivedAmount":{"type":"string","description":"Monto recibido hasta ahora, unidades humanas"},"expiresAt":{"type":"string","format":"date-time"},"createdAt":{"type":"string","format":"date-time"},"transactions":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","enum":["DEPOSIT","GAS_FUNDING","SWEEP","PAYOUT","FEE_PAYOUT","DISPERSE_APPROVE","DISPERSE_BATCH","DISPERSE_FEE_SWEEP"]},"txHash":{"type":"string","nullable":true},"amount":{"type":"string","description":"Monto en unidad mínima del token"},"status":{"type":"string","enum":["PENDING","MINED","CONFIRMED","FAILED","DROPPED"]},"confirmations":{"type":"integer"}},"required":["type","txHash","amount","status","confirmations"]}}},"required":["id","reference","status","chain","token","depositAddress","expectedAmount","expectedAmountRaw","receivedAmount","expiresAt","createdAt"]},"ErrorResponse":{"type":"object","properties":{"success":{"type":"boolean","enum":[false]},"data":{"nullable":true},"error":{"type":"object","properties":{"code":{"type":"string","example":"VALIDATION_ERROR"},"message":{"type":"string","example":"el monto debe ser mayor que cero"}},"required":["code","message"]}},"required":["success","data","error"]},"CreatePaymentRequest":{"type":"object","properties":{"reference":{"type":"string","minLength":1,"maxLength":128,"description":"Identificador de la orden en el sistema del comercio","example":"order-8821"},"chain":{"type":"string","enum":["polygon","bsc"],"description":"Red de la blockchain","example":"polygon"},"token":{"type":"string","enum":["USDT"],"description":"Símbolo del token","example":"USDT"},"amount":{"type":"string","pattern":"^\\d+(\\.\\d+)?$","description":"Monto a cobrar, en unidades humanas del token","example":"150.00"}},"required":["reference","chain","token","amount"]},"PaymentList":{"type":"array","items":{"$ref":"#/components/schemas/Payment"}},"Accepted":{"type":"object","properties":{"accepted":{"type":"boolean"}},"required":["accepted"]},"Dispersion":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"reference":{"type":"string","nullable":true},"status":{"type":"string","enum":["PENDING","AWAITING_DEPOSIT","DETECTED","CONFIRMING","UNDERPAID","OVERPAID","CONFIRMED","FUNDING_GAS","APPROVING","DISPERSING","COMPLETED","FAILED","EXPIRED"],"description":"Estado de la dispersión"},"chain":{"type":"string","enum":["polygon","bsc"],"description":"Red de la blockchain","example":"polygon"},"token":{"type":"string","enum":["USDT"],"description":"Símbolo del token","example":"USDT"},"depositAddress":{"type":"string","description":"Dirección desechable donde el usuario debe enviar totalToDeposit"},"totalAmount":{"type":"string","description":"Suma de los montos a dispersar a los destinatarios (humano)"},"totalAmountRaw":{"type":"string"},"feeAmount":{"type":"string","description":"Comisión de la pasarela (humano)"},"feeAmountRaw":{"type":"string"},"totalToDeposit":{"type":"string","description":"Monto que el user debe depositar = totalAmount + feeAmount (humano)"},"totalToDepositRaw":{"type":"string"},"receivedAmount":{"type":"string","description":"Monto recibido hasta ahora (humano)"},"destinations":{"type":"array","items":{"type":"object","properties":{"address":{"type":"string"},"amount":{"type":"string","description":"Monto en unidades humanas"},"amountRaw":{"type":"string","description":"Monto en unidad mínima del token"},"position":{"type":"integer"}},"required":["address","amount","amountRaw","position"]}},"txHashApprove":{"type":"string","nullable":true},"txHashDisperse":{"type":"string","nullable":true},"txHashFeeSweep":{"type":"string","nullable":true},"expiresAt":{"type":"string","format":"date-time"},"createdAt":{"type":"string","format":"date-time"}},"required":["id","reference","status","chain","token","depositAddress","totalAmount","totalAmountRaw","feeAmount","feeAmountRaw","totalToDeposit","totalToDepositRaw","receivedAmount","destinations","txHashApprove","txHashDisperse","txHashFeeSweep","expiresAt","createdAt"]},"DispersionDestinationInput":{"type":"object","properties":{"address":{"type":"string","pattern":"^0x[0-9a-fA-F]{40}$","description":"Dirección destinataria (checksum o lowercase)","example":"0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb1"},"amount":{"type":"string","pattern":"^\\d+(\\.\\d+)?$","description":"Monto a enviar a esta dirección, en unidades humanas del token","example":"50.00"}},"required":["address","amount"]},"CreateDispersionRequest":{"type":"object","properties":{"reference":{"type":"string","minLength":1,"maxLength":128,"description":"Identificador opcional del merchant para esta dispersión","example":"payout_2026_05_23_001"},"chain":{"type":"string","enum":["polygon","bsc"],"description":"Red de la blockchain","example":"polygon"},"token":{"type":"string","enum":["USDT"],"description":"Símbolo del token","example":"USDT"},"destinations":{"type":"array","items":{"$ref":"#/components/schemas/DispersionDestinationInput"},"minItems":1,"maxItems":200,"description":"Lista de destinatarios (min 1, max 200 por batch)"}},"required":["chain","token","destinations"]},"DispersionList":{"type":"array","items":{"$ref":"#/components/schemas/Dispersion"}},"AccessRequestResponse":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string"},"createdAt":{"anyOf":[{"type":"string"},{"type":"string"}]}},"required":["id","status","createdAt"]},"AccessRequestInput":{"type":"object","properties":{"email":{"type":"string","format":"email","example":"alice@example.com"},"company":{"type":"string","maxLength":200},"useCase":{"type":"string","minLength":5,"maxLength":2000,"description":"Para qué quiere usar la pasarela (cobros, dispersiones, ambos)","example":"Quiero pagar a 200 afiliados cada mes en USDT vía Polygon"},"estimatedMonthlyVolume":{"type":"string","maxLength":100,"example":"$50k USDT/mes"},"source":{"type":"string","maxLength":100,"description":"De dónde nos conociste (ej: twitter, google, referido por X)"},"website":{"type":"string"}},"required":["email","useCase"]}},"parameters":{}},"paths":{"/api/v1/health":{"get":{"tags":["Sistema"],"summary":"Liveness check","responses":{"200":{"description":"El servicio está vivo","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["ok"]},"timestamp":{"type":"string","format":"date-time"}},"required":["status","timestamp"]}}}}}}},"/api/v1/payments":{"post":{"tags":["Cobros"],"summary":"Crear un cobro","description":"Genera un cobro y devuelve una dirección de depósito desechable. Enviar el header `Idempotency-Key` evita duplicar el cobro si se reintenta.","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePaymentRequest"}}}},"responses":{"201":{"description":"Cobro creado","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Payment"},"error":{"nullable":true}},"required":["success","data","error"]}}}},"400":{"description":"Solicitud inválida","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"get":{"tags":["Cobros"],"summary":"Listar cobros","security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"integer","minimum":1,"maximum":100,"default":20},"required":false,"name":"limit","in":"query"},{"schema":{"type":"string","format":"uuid"},"required":false,"name":"cursor","in":"query"},{"schema":{"type":"string","enum":["PENDING","AWAITING_PAYMENT","DETECTED","CONFIRMING","CONFIRMED","OVERPAID","UNDERPAID","SWEEPING","SWEPT","DISTRIBUTING","COMPLETED","EXPIRED","FAILED"],"description":"Estado del cobro"},"required":false,"name":"status","in":"query"}],"responses":{"200":{"description":"Lista de cobros","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/PaymentList"},"error":{"nullable":true}},"required":["success","data","error"]}}}}}}},"/api/v1/payments/{id}":{"get":{"tags":["Cobros"],"summary":"Consultar un cobro","security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Detalle del cobro","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Payment"},"error":{"nullable":true}},"required":["success","data","error"]}}}},"404":{"description":"Cobro no encontrado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/payments/{id}/webhook-retry":{"post":{"tags":["Cobros"],"summary":"Reintentar la entrega del webhook","security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"responses":{"202":{"description":"Reintento encolado","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Accepted"},"error":{"nullable":true}},"required":["success","data","error"]}}}},"404":{"description":"Cobro no encontrado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/dispersions":{"post":{"tags":["Dispersiones"],"summary":"Crear una dispersión (payout batch)","description":"Crea una dispersión: el merchant envía N destinatarios+montos y el gateway devuelve una wallet desechable. Cuando el user deposita totalToDeposit en esa wallet, el gateway ejecuta el contrato Disperse y envía a todos los destinatarios en una sola transacción (~50% menos gas vs N transferencias). El header `Idempotency-Key` evita duplicar la dispersión si se reintenta.","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDispersionRequest"}}}},"responses":{"201":{"description":"Dispersión creada","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Dispersion"},"error":{"nullable":true}},"required":["success","data","error"]}}}},"400":{"description":"Solicitud inválida","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Disperse no desplegado en la chain solicitada","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"get":{"tags":["Dispersiones"],"summary":"Listar dispersiones","security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"integer","minimum":1,"maximum":100,"default":20},"required":false,"name":"limit","in":"query"},{"schema":{"type":"string","format":"uuid"},"required":false,"name":"cursor","in":"query"},{"schema":{"type":"string","enum":["PENDING","AWAITING_DEPOSIT","DETECTED","CONFIRMING","UNDERPAID","OVERPAID","CONFIRMED","FUNDING_GAS","APPROVING","DISPERSING","COMPLETED","FAILED","EXPIRED"],"description":"Estado de la dispersión"},"required":false,"name":"status","in":"query"}],"responses":{"200":{"description":"Lista de dispersiones","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/DispersionList"},"error":{"nullable":true}},"required":["success","data","error"]}}}}}}},"/api/v1/dispersions/{id}":{"get":{"tags":["Dispersiones"],"summary":"Consultar una dispersión","security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Detalle de la dispersión","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Dispersion"},"error":{"nullable":true}},"required":["success","data","error"]}}}},"404":{"description":"Dispersión no encontrada","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/access-requests":{"post":{"tags":["Acceso"],"summary":"Solicitar acceso al producto","description":"Endpoint público (sin API key) para que cualquier persona registre interés en usar la pasarela. Un admin contacta al solicitante para completar el onboarding manualmente. No garantiza acceso automático.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessRequestInput"}}}},"responses":{"201":{"description":"Solicitud registrada","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/AccessRequestResponse"},"error":{"nullable":true}},"required":["success","data","error"]}}}},"400":{"description":"Solicitud inválida","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}