Todos los cuerpos son JSON en snake_case; los campos en null se
omiten de la respuesta (no aparecen como null, no aparecen). Los montos
siempre son enteros en centavos (amount_minor), nunca flotantes.
| Grupo | Auth | Header adicional |
|---|---|---|
/v1/* | Authorization: Bearer sk_test_… / sk_live_… | Idempotency-Key (UUID) en los POST que mueven dinero — ver detalle por endpoint. |
/public/* | Sin Authorization: el client_secret del intent autentica — en el cuerpo (POST) o en el header X-Winal-Client-Secret (GET; query ?client_secret= legado). | — |
GET /openapi/v1.json): genera un cliente tipado en tu lenguaje
o consúltalo desde tu IDE. Ver Genera tu cliente. Antes de integrar,
revisa Entornos y Base URL y
Autenticación y seguridad.
Paginación
Los endpoints de listado devuelven un envelope {"object": "list", "data": [...]}.
Winal usa dos estilos de paginación según el endpoint; ambos son consistentes en su familia.
Cursor por id (exclusivo) — el flujo de eventos
GET /v1/events pagina con un cursor entero exclusivo: after_id
devuelve solo filas con id > after_id. Avanzas tu cursor al id
más alto que viste y repites; nunca repite ni salta eventos. Ver
Eventos y polling.
| Parámetro | Tipo | Default · tope |
|---|---|---|
after_id | int64, opcional | 0 (desde el primer evento); cursor exclusivo |
limit | int, opcional | 50 · tope 200 (valores mayores se recortan, no fallan) |
Cursor opaco con has_more — listados de reportes
Los listados paginados de Reportes (p. ej.
GET /v1/reports/payments) devuelven además has_more y
next_cursor. Sigue pidiendo con ?cursor=<next_cursor> mientras
has_more sea true; cuando es false, terminaste y
next_cursor se omite. El cursor es opaco: pásalo tal cual, no lo construyas.
{
"object": "list",
"data": [ /* … filas … */ ],
"has_more": true,
"next_cursor": "eyJpZCI6MTI4LCJ0cyI6..."
}
| Parámetro | Tipo | Default · tope |
|---|---|---|
cursor | string opaco, opcional | el next_cursor de la página anterior; un cursor corrupto responde reports.invalid_cursor |
limit | int, opcional | varía por endpoint (típico 20, tope 100; los listados admin usan default 100, tope 500) — siempre se recorta al tope |
Los listados simples (sin cursor) devuelven todo el conjunto del tenant en
data y aceptan ?limit= para acotar; no traen has_more.
Cada endpoint indica su estilo en su fila de la Referencia.
Límites de solicitudes
El API aplica rate limiting por ventana fija de 1 minuto. Al excederlo recibes
429 con el envelope de error estándar y un header Retry-After.
| Tráfico | Se cuenta por | Límite por minuto (default) |
|---|---|---|
/v1/* autenticado | tu llave sk_… | 300 |
/public/* | IP de cliente confiable | 60 |
Exentos (no consumen cupo): /health, /metrics, /status,
/portal, /demo y /js/*. Los límites son configurables por
despliegue, así que trata los valores de arriba como el default, no como un contrato duro.
HTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: application/json
{
"error": {
"type": "rate_limit_error",
"code": "rate_limit_exceeded",
"message": "Se excedió el límite de solicitudes; reintenta más tarde."
}
}
429, respeta siempre el header Retry-After (segundos):
espera ese tiempo y reintenta con backoff. Hoy Winal no emite headers
X-RateLimit-Limit/X-RateLimit-Remaining; no cuentes con ellos —
reintenta guiándote por Retry-After. La idempotencia
(Idempotency-Key) hace que reintentar un POST que mueve dinero sea
seguro, sin doble cargo.
Payment Intents
Crea un intent en requires_payment_method con un client_secret nuevo.
Authorization | requerido |
Idempotency-Key | requerido (UUID) |
Cuerpo
amount_minor | int64, requerido, > 0 |
currency | string ISO 4217, requerido (hoy solo MXN funciona con Sim) |
payment_method_types | string[], opcional — informativo: solo se registra en el historial, no fija el método real ni se guarda en el intent. El método que de verdad se usa es el que mandas en confirm. |
metadata | object<string,string>, opcional |
tip_minor | int64, opcional, no negativo. null/omitido = 0. Caso típico del POS: se omite aquí y se fija después en confirm (ver Reportes → Propinas). |
Errores posibles: payment_intent.invalid_amount, payment_intent.invalid_currency, payment_intent.invalid_tip (ver Errores).
POST /v1/payment_intents
Authorization: Bearer sk_test_...
Idempotency-Key: 6a1e3b2c-...
Content-Type: application/json
{ "amount_minor": 84900, "currency": "MXN" }
{
"id": "5b6b8b3e-1e4b-4c8a-9b2e-6f6a2c9d41a2",
"object": "payment_intent",
"amount_minor": 84900,
"tip_minor": 0,
"total_minor": 84900,
"currency": "MXN",
"status": "requires_payment_method",
"client_secret": "pi_secret_9fZ3kQ7bV1x...",
"livemode": false,
"created_at": "2026-07-05T18:30:00Z",
"updated_at": "2026-07-05T18:30:00Z"
}
Lee un intent. Con ?expand=attempts incluye el historial de intentos de cobro.
Authorization | requerido |
Errores posibles: payment_intent.not_found.
GET /v1/payment_intents/5b6b8b3e-...?expand=attempts
Authorization: Bearer sk_test_...
{
"id": "5b6b8b3e-1e4b-4c8a-9b2e-6f6a2c9d41a2",
"object": "payment_intent",
"amount_minor": 84900,
"tip_minor": 0,
"total_minor": 84900,
"currency": "MXN",
"status": "succeeded",
"client_secret": "pi_secret_9fZ3kQ7bV1x...",
"livemode": false,
"created_at": "2026-07-05T18:30:00Z",
"updated_at": "2026-07-05T18:30:04Z",
"attempts": [
{
"id": "a13fce02-...",
"object": "attempt",
"status": "captured",
"connector_key": "sim",
"method": "card",
"provider_ref": "sim_charge_9c1f...",
"created_at": "2026-07-05T18:30:01Z"
}
]
}
Confirma el cobro: valida ruteo, crea el attempt y encola su ejecución. La
respuesta HTTP siempre llega con status: "processing" — el cobro se
ejecuta después, fuera del request (regla: processing solo se resuelve con
evidencia del proveedor). El resultado final llega por webhook o por un GET
posterior.
Authorization | requerido |
Idempotency-Key | requerido (UUID) |
Cuerpo
payment_token | string, requerido (tok_sim_* en pruebas) |
payment_method | string, requerido: card | spei | codi | dimo | oxxo |
tip_minor | int64, opcional, no negativo. Si se manda, reemplaza la propina que el intent ya tuviera; null/omitido deja la existente sin tocar. La respuesta trae tip_minor y total_minor (= amount_minor + tip_minor) — ver Reportes → Propinas. |
Errores posibles: payment_intent.unauthorized, payment_intent.not_confirmable, payment_intent.no_route, payment_intent.concurrent_modification, payment_intent.invalid_tip.
POST /v1/payment_intents/5b6b8b3e-.../confirm
Authorization: Bearer sk_test_...
Idempotency-Key: 9d2f1a4e-...
Content-Type: application/json
{ "payment_token": "tok_sim_ok", "payment_method": "card", "tip_minor": 5000 }
{
"id": "5b6b8b3e-1e4b-4c8a-9b2e-6f6a2c9d41a2",
"object": "payment_intent",
"amount_minor": 84900,
"tip_minor": 5000,
"total_minor": 89900,
"currency": "MXN",
"status": "processing",
"client_secret": "pi_secret_9fZ3kQ7bV1x...",
"livemode": false,
"created_at": "2026-07-05T18:30:00Z",
"updated_at": "2026-07-05T18:30:00Z",
"attempts": [
{
"id": "a13fce02-...",
"object": "attempt",
"status": "pending",
"connector_key": "sim",
"method": "card",
"created_at": "2026-07-05T18:30:00Z"
}
]
}
Captura un intento previamente autorizado sin capturar (flujo auth/capture del POS,
p. ej. tras confirmar con tok_sim_auth). Sin cuerpo. El intent no cambia de
estado en la respuesta — sigue processing hasta que el proveedor confirme
la captura.
Authorization | requerido |
Idempotency-Key | requerido (UUID) |
Errores posibles: attempt.not_capturable (no hay intento authorized).
POST /v1/payment_intents/5b6b8b3e-.../capture
Authorization: Bearer sk_test_...
Idempotency-Key: 2c3e9f10-...
{
"id": "5b6b8b3e-1e4b-4c8a-9b2e-6f6a2c9d41a2",
"object": "payment_intent",
"amount_minor": 84900,
"tip_minor": 0,
"total_minor": 84900,
"currency": "MXN",
"status": "processing",
"client_secret": "pi_secret_9fZ3kQ7bV1x...",
"livemode": false,
"created_at": "2026-07-05T18:30:00Z",
"updated_at": "2026-07-05T18:31:10Z",
"attempts": [
{ "id": "a13fce02-...", "object": "attempt", "status": "authorized",
"connector_key": "sim", "method": "card",
"provider_ref": "sim_auth_7b2c...", "created_at": "2026-07-05T18:30:00Z" }
]
}
Cancela el intent (si la máquina de estados lo permite). Sin cuerpo. Cancelar un intent ya canceled es un no-op que devuelve 200.
Authorization | requerido |
Idempotency-Key | requerido (UUID) |
Errores posibles: transición inválida si el intent ya está en un estado terminal distinto (succeeded/failed/expired).
POST /v1/payment_intents/5b6b8b3e-.../cancel
Authorization: Bearer sk_test_...
Idempotency-Key: 77aa1c3e-...
{
"id": "5b6b8b3e-1e4b-4c8a-9b2e-6f6a2c9d41a2",
"object": "payment_intent",
"amount_minor": 84900,
"tip_minor": 0,
"total_minor": 84900,
"currency": "MXN",
"status": "canceled",
"client_secret": "pi_secret_9fZ3kQ7bV1x...",
"livemode": false,
"created_at": "2026-07-05T18:30:00Z",
"updated_at": "2026-07-05T18:32:00Z"
}
Refunds
Crea una devolución en requested sobre un intento capturado.
Authorization | requerido |
Idempotency-Key | requerido (UUID) |
Cuerpo
attempt_id | uuid, requerido — el intento a devolver (no el payment_intent). |
amount_minor | int64, opcional. Si se omite, devuelve el saldo restante (cobrado menos devoluciones vivas) — no el monto original del cargo. |
reason | string, opcional |
Errores posibles: ver la tabla de refunds en Errores.
POST /v1/refunds
Authorization: Bearer sk_test_...
Idempotency-Key: f1e2d3c4-...
Content-Type: application/json
{ "attempt_id": "a13fce02-...", "amount_minor": 84900, "reason": "devolución POS" }
{
"id": "d4c5b6a7-...",
"object": "refund",
"attempt_id": "a13fce02-...",
"amount_minor": 84900,
"currency": "MXN",
"status": "requested",
"reason": "devolución POS",
"created_at": "2026-07-05T19:00:00Z"
}
Lee una devolución por su id.
Authorization | requerido |
Errores posibles: refund.not_found.
GET /v1/refunds/d4c5b6a7-...
Authorization: Bearer sk_test_...
{
"id": "d4c5b6a7-...",
"object": "refund",
"attempt_id": "a13fce02-...",
"amount_minor": 84900,
"currency": "MXN",
"status": "succeeded",
"provider_ref": "sim_refund_1a2b...",
"reason": "devolución POS",
"created_at": "2026-07-05T19:00:00Z"
}
Clientes (card-on-file)
Guía narrativa completa (activación asíncrona del método, cobro 1-click) en Clientes.
Crea un cliente. Cuerpo vacío permitido.
Authorization | requerido |
Cuerpo
name | string, opcional |
email | string, opcional |
metadata | object<string,string>, opcional |
{
"id": "7c17884b-b250-4aea-9a7b-582f1dae55db",
"object": "customer",
"name": "Juan Pérez",
"email": "juan.perez@example.mx",
"livemode": false,
"created_at": "2026-07-08T02:18:51.369868+00:00",
"updated_at": "2026-07-08T02:18:51.369868+00:00"
}
Lista o lee un cliente por su id.
Authorization | requerido |
Errores posibles: customer.not_found.
{ "object": "list", "data": [ { "id": "7c17884b-...", "object": "customer", "...": "..." } ] }
Guarda un método de pago tokenizado. Nace pending; el Worker lo activa fuera del request.
Authorization | requerido |
Idempotency-Key | requerido (UUID) |
Cuerpo
payment_token | string, requerido — token de un solo uso (tok_sim_* en pruebas) |
connector | string, opcional |
Errores posibles: payment_method.invalid_token, payment_method.no_route, customer.not_found.
{
"id": "2a3c754f-c77a-4de0-9cf4-29aca3ff333e",
"object": "payment_method",
"customer_id": "7c17884b-b250-4aea-9a7b-582f1dae55db",
"connector": "sim",
"status": "pending",
"livemode": false,
"created_at": "2026-07-08T02:19:31.043184+00:00",
"updated_at": "2026-07-08T02:19:31.043184+00:00"
}
{
"id": "2a3c754f-c77a-4de0-9cf4-29aca3ff333e",
"object": "payment_method",
"customer_id": "7c17884b-b250-4aea-9a7b-582f1dae55db",
"connector": "sim",
"brand": "visa",
"last4": "1764",
"status": "active",
"livemode": false,
"created_at": "2026-07-08T02:19:31.043184+00:00",
"updated_at": "2026-07-08T02:19:31.367513+00:00"
}
Lista los métodos guardados del cliente (incluye detached).
Authorization | requerido |
Transición terminal a detached. Idempotente: repetir sobre uno ya detached no falla.
Authorization | requerido |
Errores posibles: payment_method.not_found.
{
"id": "2a3c754f-c77a-4de0-9cf4-29aca3ff333e",
"object": "payment_method",
"customer_id": "7c17884b-b250-4aea-9a7b-582f1dae55db",
"connector": "sim",
"brand": "visa",
"last4": "1764",
"status": "detached",
"livemode": false,
"created_at": "2026-07-08T02:19:31.043184+00:00",
"updated_at": "2026-07-08T02:19:46.231193+00:00"
}
Cobro 1-click: manda payment_method_id en POST
/v1/payment_intents/{id}/confirm en vez de payment_token +
payment_method — ver Clientes para el ejemplo
completo. Errores posibles propios de esa variante:
payment_method.not_chargeable, payment_method.concurrent_modification.
Webhook Endpoints
Registra un endpoint para recibir entregas. El secret solo se devuelve aquí.
Authorization | requerido |
Idempotency-Key es opcional aquí y, si lo
mandas, se ignora.
Cuerpo
url | string HTTPS, requerido |
events | string[], opcional — lista blanca de event_type; vacío/omitido = todo el catálogo (ver Webhooks) |
POST /v1/webhook_endpoints
Authorization: Bearer sk_test_...
Content-Type: application/json
{ "url": "https://tu-servidor.mx/webhooks/winal" }
{
"id": "c1a9f2e0-...",
"object": "webhook_endpoint",
"url": "https://tu-servidor.mx/webhooks/winal",
"secret": "whsec_8Kx9..."
}
Lista los endpoints del tenant. Nunca incluye secretos.
Authorization | requerido |
{
"object": "list",
"data": [
{
"id": "c1a9f2e0-...",
"object": "webhook_endpoint",
"url": "https://tu-servidor.mx/webhooks/winal",
"active": true,
"created_at": "2026-07-05T18:00:00Z"
}
]
}
Deshabilita el endpoint (baja lógica: deja de recibir entregas nuevas). No es un borrado
físico — el ledger y el historial son append-only por diseño, y esta fila sigue existiendo
con active: false.
Authorization | requerido |
Errores posibles: webhook_endpoint.not_found.
{ "id": "c1a9f2e0-...", "object": "webhook_endpoint", "deleted": true }
Checkout público (/public)
Diseñados para llamarse directo desde el navegador del pagador (así es como los usa
winal.js): sin Authorization, el client_secret del
intent autentica. La proyección de respuesta es reducida —
nunca incluye client_secret, attempts,
provider_ref ni metadata, y tampoco livemode
(a diferencia de la respuesta autenticada de /v1).
Cuerpo
client_secret | string, requerido |
payment_token | string, requerido |
payment_method | string, requerido |
tip_minor | int64, opcional, no negativo — igual semántica que en /v1/payment_intents/{id}/confirm (reemplaza la propina existente si se manda). |
Si el id no existe o el client_secret no coincide, la
respuesta es siempre el mismo 404 genérico — nunca revela cuál de los dos
falló (defensa contra fuerza bruta).
POST /public/payment_intents/5b6b8b3e-.../confirm
Content-Type: application/json
{
"client_secret": "pi_secret_9fZ3kQ7bV1x...",
"payment_token": "tok_sim_ok",
"payment_method": "card",
"tip_minor": 3000
}
{
"id": "5b6b8b3e-1e4b-4c8a-9b2e-6f6a2c9d41a2",
"object": "payment_intent",
"amount_minor": 84900,
"tip_minor": 3000,
"total_minor": 87900,
"currency": "MXN",
"status": "processing"
}
Query
client_secret | requerido |
Usado por el polling interno de winal.js cada 2 s hasta un estado terminal.
GET /public/payment_intents/5b6b8b3e-...
X-Winal-Client-Secret: pi_secret_9fZ3kQ7bV1x...
{
"id": "5b6b8b3e-1e4b-4c8a-9b2e-6f6a2c9d41a2",
"object": "payment_intent",
"amount_minor": 84900,
"tip_minor": 0,
"total_minor": 84900,
"currency": "MXN",
"status": "requires_action",
"next_action": {
"type": "bank_transfer",
"clabe": "646180473921058317",
"beneficiary": "SIM SPEI",
"expires_at": "2026-07-06T18:30:00Z"
}
}
Eventos
Detalle narrativo, patrón de polling y el shape completo del envelope en Eventos y polling.
Stream de polling sobre las entregas de webhook del tenant — alternativa a recibir HTTP
entrante, pensado para desarrollo local y para el CLI oficial
winal listen. Solo devuelve filas si el tenant tiene al menos un
webhook_endpoint activo registrado.
Authorization | requerido |
Query
after_id | int64, opcional (default 0) — cursor exclusivo: devuelve id > after_id. |
limit | int, opcional (default 50, tope 200; valores mayores se recortan). |
GET /v1/events?after_id=0&limit=50
Authorization: Bearer sk_test_...
{
"object": "list",
"data": [
{
"id": 15,
"object": "event",
"event_id": "5e332767-ae86-42e3-b395-cc325de90f2b",
"event_type": "payment_intent.succeeded",
"delivered": false,
"created_at": "2026-07-07T22:55:49Z",
"data": {
"event_id": "5e332767-ae86-42e3-b395-cc325de90f2b",
"event_type": "payment_intent.succeeded",
"created_at": "2026-07-07T22:55:48Z",
"api_version": "2026-07-01",
"livemode": false,
"data": {
"id": "f5f3fc01-8d3b-4abd-881e-52664a1d9c44",
"object": "payment_intent",
"status": "succeeded",
"amount_minor": 10000,
"tip_minor": 1500,
"total_minor": 11500,
"currency": "MXN",
"metadata": { "payment_link_id": "pl_smoke_fosos" }
}
}
}
]
}
Facturas (CFDI)
Guía narrativa completa (PUE vs. PPD, complemento de pagos) en Facturación CFDI.
Timbra un CFDI de ingreso, método de pago PUE, sobre un payment_intent ya succeeded.
Authorization | requerido |
Idempotency-Key | requerido (UUID) |
Cuerpo
payment_intent_id | uuid, requerido |
receptor | objeto requerido: rfc, nombre, uso_cfdi, regimen_fiscal, cp (todos requeridos) |
Errores posibles: invoice.invalid_body, invoice.invalid_receptor, invoice.pac_error (ver Errores).
POST /v1/invoices
Authorization: Bearer sk_test_...
Idempotency-Key: 1a2b3c4d-...
Content-Type: application/json
{
"payment_intent_id": "a55fc12f-0af3-4858-ba84-52a3e7240f52",
"receptor": {
"rfc": "XAXX010101000",
"nombre": "Publico en general",
"uso_cfdi": "S01",
"regimen_fiscal": "616",
"cp": "06600"
}
}
{
"id": "...",
"object": "invoice",
"status": "stamped",
"payment_intent_id": "a55fc12f-0af3-4858-ba84-52a3e7240f52",
"uuid_fiscal": "...",
"serie": "A", "folio": "1",
"total_minor": 55000, "base_minor": 47414, "iva_minor": 7586,
"currency": "MXN",
"receptor": { "...": "..." },
"pac": "facturama",
"metodo_pago": "PUE",
"parcialidades": 0,
"created_at": "...", "updated_at": "..."
}
Emite un CFDI método de pago PPD por un total acordado, SIN cobro previo — se liquidará después con uno o más REP.
Authorization | requerido |
Idempotency-Key | requerido (UUID) |
Cuerpo
total_minor | int64, requerido, > 0 (incluye IVA) |
currency | string ISO 4217, opcional (default MXN) |
receptor | objeto requerido, mismos 5 campos que en POST /v1/invoices |
descripcion | string, opcional |
Errores posibles: invoice.invalid_body, invoice.invalid_receptor, invoice.invalid_currency, invoice.pac_error.
POST /v1/invoices/ppd
Authorization: Bearer sk_test_...
Idempotency-Key: 2b3c4d5e-...
Content-Type: application/json
{
"total_minor": 348000,
"currency": "MXN",
"receptor": {
"rfc": "XAXX010101000",
"nombre": "Publico en General",
"uso_cfdi": "S01",
"regimen_fiscal": "616",
"cp": "06600"
},
"descripcion": "Servicios profesionales - anticipo PPD"
}
{
"id": "5cee05bd-de0c-4961-98eb-e0cacfc6aae8",
"object": "invoice",
"status": "stamped",
"total_minor": 348000,
"base_minor": 300000,
"iva_minor": 48000,
"currency": "MXN",
"metodo_pago": "PPD",
"saldo_insoluto_minor": 348000,
"parcialidades": 0,
"pac": "facturama",
"created_at": "2026-07-07T23:02:30Z",
"updated_at": "2026-07-07T23:02:31Z"
}
Registra un pago sobre una factura PPD ya stamped y timbra su complemento de pagos 2.0 (REP).
Authorization | requerido |
Idempotency-Key | requerido (UUID) |
Cuerpo
payment_intent_id | uuid, requerido — cobro ya succeeded que liquida (parte de) el saldo insoluto |
Errores posibles: invoice.invalid_body, invoice.not_found, invoice.not_stamped, invoice.pac_error.
POST /v1/invoices/5cee05bd-.../payments
Authorization: Bearer sk_test_...
Idempotency-Key: 3c4d5e6f-...
Content-Type: application/json
{ "payment_intent_id": "a55fc12f-0af3-4858-ba84-52a3e7240f52" }
{
"id": "...",
"object": "invoice_payment",
"invoice_id": "5cee05bd-de0c-4961-98eb-e0cacfc6aae8",
"payment_intent_id": "a55fc12f-0af3-4858-ba84-52a3e7240f52",
"parcialidad": 1,
"monto_minor": 55000,
"saldo_anterior_minor": 348000,
"saldo_insoluto_minor": 293000,
"currency": "MXN",
"status": "stamped",
"rep_uuid": "...",
"created_at": "...", "updated_at": "..."
}
Lista los REP timbrados contra una factura PPD.
Authorization | requerido |
{ "object": "list", "data": [] }
Cobranza (Receivables)
Guía narrativa completa (Payment Link automático, marcado como paid, auto-CFDI)
en Cobranza.
Crea una cuenta por cobrar; internamente crea un Payment Link de un solo uso.
Authorization | requerido |
Idempotency-Key | requerido (UUID) |
Cuerpo
customer_name / customer_email | string, requeridos |
customer_phone | string, opcional — requerido solo para whatsapp_link |
customer_rfc / customer_uso_cfdi / customer_regimen_fiscal / customer_cp | opcionales, pero van juntos o ninguno (auto-CFDI al pagarse) |
concepto | string, requerido |
amount_minor | int64, requerido, > 0 |
currency | string ISO 4217, requerido |
due_date | datetime ISO-8601, requerido |
Errores posibles: receivable.missing_fields, receivable.invalid_amount, receivable.invalid_currency, receivable.invalid_date, receivable.incomplete_fiscal_receptor.
{
"id": "0ec348fb-2341-4f31-b870-9cc4d3087b29",
"object": "receivable",
"customer_name": "María López",
"customer_email": "maria.lopez@example.mx",
"customer_phone": "5215512345678",
"amount_minor": 150000,
"currency": "MXN",
"concepto": "Mensualidad julio 2026 - plan Pro",
"due_date": "2026-07-20T00:00:00+00:00",
"status": "open",
"payment_link_id": "27011835-fd92-44dd-8d6c-549c00536703",
"payment_link_url": "/pay/QOECDPCH2HC",
"created_at": "2026-07-08T02:19:53.743499+00:00"
}
Lista o lee una cuenta por cobrar. status es derivado: open | paid | overdue | canceled.
Authorization | requerido |
Errores posibles: receivable.not_found.
Arma la URL https://wa.me/... con el mensaje de cobro pre-redactado.
Authorization | requerido |
Errores posibles: receivable.no_phone (sin customer_phone capturado).
{ "url": "https://wa.me/5215512345678?text=Hola%20Mar%C3%ADa..." }
Estado de cuenta agregado de un cliente: sus cuentas y los totales abierto/vencido/pagado.
Authorization | requerido |
{
"customer_email": "maria.lopez@example.mx",
"receivables": [ { "...": "..." } ],
"total_open_minor": 150000,
"total_overdue_minor": 0,
"total_paid_minor": 0
}
Antigüedad de saldos por cliente, en 4 cubos contables estándar.
Authorization | requerido |
{
"object": "list",
"data": [
{
"customer_email": "aging@prueba.mx",
"customer_name": "Prueba Aging",
"currency": "MXN",
"bucket0_to30_minor": 30000,
"bucket31_to60_minor": 0,
"bucket61_to90_minor": 0,
"bucket90_plus_minor": 0,
"total_minor": 30000
}
]
}
Conciliación bancaria
Guía narrativa completa (presets, mapeo de columnas, tipos de excepción) en Conciliación bancaria.
Importa el CSV del estado de cuenta bancario. Multipart (file) o cuerpo
crudo. Sin Idempotency-Key — su idempotencia real es el hash del
archivo.
Authorization | requerido |
Campos (multipart o query)
bank | string, requerido |
period_start / period_end | yyyy-MM-dd, requeridos |
tolerance_days | int, opcional |
preset | bbva | banorte | santander, o usa el mapeo explícito |
date_column / description_column / credit_column / debit_column | int (0-based), requeridos si no hay preset |
reference_column | int, opcional |
has_header | bool, opcional (default true) |
Errores posibles: ver la tabla completa en Conciliación bancaria.
{
"id": "271a21c8-be04-4d6e-909a-8f1394fd333a",
"object": "bank_statement",
"bank": "bbva",
"period_start": "2026-07-01",
"period_end": "2026-07-08",
"filename": "estado_bbva.csv",
"lines_total": 2,
"matched": 0,
"partial": 0,
"unmatched": 1,
"exceptions": 2,
"already_imported": false
}
Lista los estados de cuenta importados, o las líneas de uno (filtro opcional matched/unmatched/partial).
Authorization | requerido |
Errores posibles: bank_statement.invalid_match_status.
{
"id": 3,
"object": "bank_statement_line",
"value_date": "2026-07-01",
"description": "SPEI RECIBIDO ANTECH",
"reference": "REF001",
"credit_minor": 50000,
"currency": "MXN",
"match_status": "unmatched"
}
Autofactura pública
Guía narrativa completa (receipt_code, anti-enumeración) en
Facturación CFDI → Autofactura.
Sin Authorization — la llave es receipt_code + rfc.
Cuerpo
receipt_code | string, requerido — formato W-XXXXX, viene en cada payment_intent |
rfc | string, requerido — formato SAT |
nombre / uso_cfdi / regimen_fiscal / cp | string, requeridos |
email | string, opcional (captura sin efecto en el timbrado hoy) |
Errores posibles: autofactura.invalid_body, autofactura.invalid_rfc, autofactura.not_available (404), autofactura.receipt_not_found (404), más los errores de invoice.* del PAC reenviados tal cual.
{
"error": {
"type": "invalid_request_error",
"code": "invoice.pac_error",
"message": "El PAC rechazó el timbrado (CFDI 492400bb-... quedó en 'error'): ",
"doc_url": "https://winal.com.mx/docs/errores.html#err-invoice.pac_error",
"request_id": "0HNMSIOBNVS9K:00000001"
}
}
Descarga del CFDI ya timbrado; ambos parámetros deben coincidir con el CFDI.
Errores posibles: invoice.not_stamped, autofactura.receipt_not_found.
Reportes
Guía narrativa con el significado de cada campo en Reportes.
Cuánto le ahorró/recuperó Winal al tenant en el período: ruteo consciente de costo, retry cross-conector y dunning de suscripciones.
Authorization | requerido |
Query
from / to | ISO-8601, opcionales (default: últimos 30 días) |
Errores posibles: reports.invalid_period.
{
"object": "reporting.savings",
"routing_saved_minor": 0,
"retry_recovered_minor": 0,
"dunning_recovered_minor": 0,
"total_minor": 0,
"by_connector": [],
"period_from": "2026-06-07T23:02:03Z",
"period_to": "2026-07-07T23:02:03Z"
}
Pólizas contables del período, formato CONTPAQi o Aspel-COI, listas para importar.
Authorization | requerido |
Query
from / to | ISO-8601, opcionales (default: últimos 30 días) |
format | requerido, sin default: contpaqi | aspel_coi |
Errores posibles: reports.invalid_period, reports.invalid_format.
GET /v1/reports/polizas?from=2026-07-01T00:00:00Z&to=2026-07-08T00:00:00Z&format=contpaqi
Authorization: Bearer sk_test_...
P 20260706 3 1 1 0 Poliza diario Winal 2026-07-06
M 108-001 1 0 150.00 0 0.00 charge: charge:attempt_852d...
Corte de caja del turno: desglose por método/conector (cobrado, propinas, operaciones), totales y devoluciones. Ver Reportes → Multisucursal para el detalle narrativo.
Authorization | requerido |
Query
from / to | ISO-8601, ambos requeridos (sin default: no hay "turno" sin rango explícito) |
branch / register | opcionales, igualdad exacta contra metadata.branch/metadata.register del intent — no validan contra ningún catálogo. |
Errores posibles: reports.invalid_period, reports.invalid_currency.
{
"object": "reporting.cash_cut",
"currency": "MXN",
"period_from": "2026-07-01T00:00:00Z",
"period_to": "2026-07-08T00:00:00Z",
"by_method": [
{ "key": "card", "charged_minor": 256490, "tip_minor": 6500, "total_minor": 262990, "operation_count": 13 }
],
"by_connector": [
{ "key": "sim", "charged_minor": 281490, "tip_minor": 6500, "total_minor": 287990, "operation_count": 14 }
],
"totals": { "charged_minor": 281490, "tip_minor": 6500, "total_minor": 287990, "operation_count": 14 },
"refunds": { "count": 0, "amount_minor": 0 },
"by_branch": [
{ "key": "(sin sucursal)", "charged_minor": 1589124, "tip_minor": 9500, "total_minor": 1598624, "operation_count": 27 },
{ "key": "centro", "charged_minor": 800, "tip_minor": 0, "total_minor": 800, "operation_count": 1 }
]
}
Onboarding de sub-comercios
Guía narrativa completa (flujo draft → submit → aprobado, campos y documentos) en Onboarding. Requisito para Winal Connect.
Alta mínima de una solicitud, en draft.
Authorization | requerido |
Cuerpo
legal_name | string, requerido |
person_type | "fisica" | "moral", requerido |
contact_email | string, requerido |
| resto de campos (ver Onboarding) | opcionales al crear; completos exige submit |
Errores posibles: onboarding_application.invalid_legal_name, invalid_person_type, invalid_contact_email.
{
"id": "75deacfb-ff0d-476e-8292-f9471941694a",
"object": "onboarding_application",
"status": "draft",
"legal_name": "Tienda Docs SA de CV",
"person_type": "moral",
"contact_email": "docs@example.mx",
"created_at": "2026-07-08T06:48:56.880311+00:00",
"updated_at": "2026-07-08T06:48:56.880311+00:00",
"documents": []
}
Patch parcial (campos null/omitidos no se tocan), solo mientras la solicitud sea editable (draft/needs_info). Mismo cuerpo que POST, más documents[] (ver Onboarding para los 4 tipos requeridos).
Authorization | requerido |
Errores posibles: onboarding_application.not_found, onboarding_application.transition_conflict, onboarding_application.invalid_document.
{
"id": "75deacfb-...", "object": "onboarding_application", "status": "draft",
"rfc": "TDS900101AB1", "clabe_masked": "**** **** **** 0004",
"documents": [ { "document_type": "ine", "reference": "INE-DOC-001", "reference_hash": "a1b2c3…", "created_at": "..." }, "..." ]
}
Dispara la verificación (síncrona en fase 0): completitud → formato de RFC/CLABE → check de listas (simulado). Resuelve a approved, needs_info o rejected en la misma llamada.
Authorization | requerido |
Errores posibles: onboarding_application.missing_fields, missing_documents, transition_conflict.
{
"id": "75deacfb-...", "object": "onboarding_application", "status": "approved",
"status_reason": "Verificación automática aprobada: RFC y CLABE con formato válido, sin coincidencias en listas.",
"resolved_by": "system:auto_verification",
"submitted_at": "2026-07-08T06:49:17.36Z", "resolved_at": "2026-07-08T06:49:17.36Z"
}
Lista (?status=, ?limit= opcionales; sin documents) o lee una solicitud por id (con documents[]).
Authorization | requerido |
Errores posibles: onboarding_application.not_found.
La resolución manual (aprobar/rechazar/pedir información) es una operación de portal → Onboarding
para operadores de Winal, montada bajo /admin/tenants/{tenantId}/onboarding/applications/... —
no se documenta aquí como endpoint de tu integración.
Winal Connect
Guía narrativa completa (el modelo, las dos formas de marcar un split, la dispersión) en Winal Connect.
Liga un sub-comercio con una solicitud de Onboarding ya approved.
Authorization | requerido |
Cuerpo
onboarding_application_id | string (uuid), requerido |
connector_key | string, opcional (default "stp"; usa "sim" en pruebas) |
Errores posibles: connect.invalid_application_id, connect.application_not_found, connect.application_not_approved, connect.account_conflict.
{
"id": "0d0de738-0133-4625-97e2-9c285ae20f9e",
"object": "connect_account",
"onboarding_application_id": "0732573a-5567-4b2a-87b6-8721654c5060",
"settlement_clabe_masked": "**** **** **** 0004",
"sub_merchant_name": "Sub Comercio Sim SA de CV",
"status": "active",
"connector_key": "sim",
"livemode": false,
"created_at": "2026-07-08T06:49:58.292982+00:00"
}
Lista (?limit=) o lee una cuenta Connect. La CLABE del sub nunca se expone completa.
Authorization | requerido |
Errores posibles: connect.account_not_found.
Split manual post-cobro (multi sub-comercio). application_fee_minor + Σ splits[].amount_minor debe ser exactamente charge_amount_minor.
Authorization | requerido |
Idempotency-Key | requerido (UUID) — compromete dinero |
Cuerpo
payment_intent_id | string (uuid), requerido |
charge_amount_minor | int64, requerido, > 0 |
application_fee_minor | int64, requerido (puede ser 0) |
currency | ISO 4217, requerido |
splits | [{connect_account_id, amount_minor}], al menos uno |
Errores posibles: connect.invalid_charge, connect.missing_allocations, connect.invalid_allocation, connect.split_mismatch, connect.account_not_found.
{
"id": "53fb6fd5-92c8-4e13-93a1-f01aefb21ce8",
"object": "connect_transfer",
"payment_intent_id": "aaab3de3-3cfe-4d55-9bbd-24a89977abf7",
"charge_amount_minor": 100000,
"application_fee_minor": 10000,
"currency": "MXN",
"status": "split",
"created_at": "2026-07-08T06:50:09.078051+00:00",
"splits": [
{ "id": "09702eba-...", "connect_account_id": "0d0de738-...", "amount_minor": 90000, "status": "dispersing", "payout_id": "fa888dfc-..." }
]
}
Lista (?limit=) o lee un transfer, con sus splits[].
Authorization | requerido |
Errores posibles: connect.transfer_not_found.
Payouts
Guía narrativa completa (la máquina de estados, STP vs. Sim) en Payouts.
Ordena una dispersión SPEI a una CLABE. Sin custodia (ADR-0001): sale de la cuenta del propio comercio.
Authorization | requerido |
Idempotency-Key | requerido (UUID) |
Cuerpo
clabe | string, 18 dígitos con dígito de control válido, requerido |
beneficiary_name | string, requerido |
beneficiary_rfc | string, opcional |
amount_minor | int64, requerido, > 0 |
currency | ISO 4217, requerido (solo MXN) |
concepto | string, requerido |
reference | string, opcional (se genera si falta) |
connector_key | opcional (default "stp"; usa "sim" en pruebas) |
Errores posibles: payout.missing_fields, payout.invalid_amount, payout.invalid_clabe, payout.unsupported_currency.
{
"id": "b4e5115a-e401-48b6-9b4d-e80b7a915748",
"object": "payout",
"clabe": "646180157000000004",
"beneficiary_name": "Proveedor Docs SA de CV",
"amount_minor": 250000,
"currency": "MXN",
"concepto": "pago de prueba docs",
"reference": "2441786",
"status": "processing",
"connector_key": "sim",
"livemode": false,
"created_at": "2026-07-08T06:46:43.157657+00:00"
}
Lista (?limit=, default 100, máx. 500) o lee un payout — provider_ref/tracking_key/failure_reason aparecen cuando el Worker ya ejecutó la orden.
Authorization | requerido |
Errores posibles: payout.not_found.
Billers (pago de servicios)
Guía narrativa completa en Pago de servicios.
Catálogo global (sin variación por tenant). ?category= opcional.
Authorization | requerido |
{ "object": "list", "data": [
{ "object": "biller", "code": "cfe", "name": "CFE (Comisión Federal de Electricidad)",
"category": "luz", "reference_label": "Número de servicio (10 a 12 dígitos)", "active": true },
"... (agua_cdmx, telcel_recarga, telmex, izzi)"
] }
Consulta de adeudo — NO mueve dinero, no exige Idempotency-Key.
Authorization | requerido |
Cuerpo: { "reference": "string, requerido" }
Errores posibles: biller.missing_reference, biller.not_found, biller.invalid_reference, biller.reference_not_found.
{
"object": "biller_inquiry", "biller_code": "cfe", "biller_name": "CFE (Comisión Federal de Electricidad)",
"reference": "1234567890", "amount_due_minor": 118700, "currency": "MXN",
"service_holder_name": "Cliente simulado (ref. 1234567890)", "due_date": "2026-07-18T06:47:41.85Z"
}
Confirma el pago del servicio ante el biller.
Authorization | requerido |
Idempotency-Key | requerido (UUID) — validado por este endpoint mismo |
Cuerpo
biller_code | string, requerido |
reference | string, requerido |
amount_minor | int64, opcional — si viene, debe igualar el adeudo vigente |
payment_intent_id | uuid, opcional — solo correlación, sin FK real |
Errores posibles: service_payment.missing_fields, service_payment.amount_mismatch, service_payment.idempotency_conflict.
{
"id": "3f490a8e-630e-4a4b-ba4e-81be735c0c14", "object": "service_payment",
"biller_code": "cfe", "reference": "1234567890", "amount_minor": 118700, "currency": "MXN",
"status": "paid", "provider_ref": "SIMBILL-7188545cf02c4532b9681371fbe806b3",
"created_at": "2026-07-08T06:47:46.86Z", "updated_at": "2026-07-08T06:47:46.86Z"
}
{ "id": "...", "object": "service_payment", "status": "failed",
"failure_reason": "El biller 'Telmex' rechazó la confirmación del pago para la referencia '...' (simulado)." }
Lista (?limit=) o lee un pago de servicio.
Authorization | requerido |
Errores posibles: service_payment.not_found.
Terminales (card-present)
El alta y administración de terminales SmartPOS (número de serie, modelo, sucursal, estado
active/inactive/lost) es una operación de
portal → Terminales, no un endpoint público de /v1 — tu integración solo necesita
el id de la terminal ya activa, para mandarlo como metadata.terminal_id al
cobrar card_present o CoDi en mostrador. Ver Métodos
de pago → Card-present y CoDi en mostrador.