{"openapi":"3.1.0","info":{"title":"QuickPay · API","version":"0.1.0","description":"API de QuickPay: enlaces de pago, cobro por lote, webhooks y, para aliados, la API directa de cobros y pagos.\n\n## Autenticación\nTodas las llamadas usan la cabecera `x-switch-key` con el formato `<comercio>:<api_key>`.\nCada comercio tiene llaves de **prueba** (`sk_test_…`, ambiente de ensayo, no mueven dinero real) y de **producción** (`sk_live_…`).\n\n## Idempotencia\nEnvía siempre un `idempotency_key` (UUID) único por operación. Reintentar con la misma llave nunca duplica el cobro/pago: devuelve el resultado original.\n\n## Estados asíncronos\nLa liquidación interbancaria es asíncrona. Una operación puede quedar `PENDIENTE` y confirmarse luego como `LIQUIDADA` o `RECHAZADA`. Usa `/consultar` (o configura un webhook) para conocer el estado final. Ante `DESCONOCIDA`, consulta antes de reintegrar — nunca reenvíes la operación original.\n\n## Códigos de rechazo\nEl campo `motivo_rechazo` está normalizado (mismo conjunto para todos los bancos).\n\n## Integración con IA (MCP)\nAdemás de esta API REST, QuickPay ofrece un **servidor MCP** para que asistentes de IA (por ejemplo Claude) usen QuickPay como herramienta — sin programar una integración.\n\n- **Endpoint:** `https://mcp.pagaconquickpay.com/mcp` (transporte Streamable HTTP).\n- **Autenticación:** cabecera `x-switch-key` con `<comercio>:<api_key>` (las mismas llaves de esta API).\n- **Herramientas disponibles** (seguras, no mueven dinero directamente):\n  - `listar_bancos` — bancos desde los que tus clientes pueden pagarte (código y nombre).\n  - `consultar_estado` — estado de una transacción por su id.\n  - `crear_link_cobro` — genera un link de cobro para que una persona pague.\n\nConéctalo desde cualquier cliente compatible con MCP configurando esa URL y tu `x-switch-key`."},"servers":[{"url":"https://pay.pagaconquickpay.com","description":"Producción"}],"security":[{"switchKey":[]}],"tags":[{"name":"Cobros","description":"Debitar al pagador (débito inmediato con OTP)"},{"name":"Links de cobro","description":"Enlaces de pago alojados (hosted checkout); el link se paga exactamente una vez"},{"name":"Pagos","description":"Enviar dinero a un beneficiario"},{"name":"Consultas","description":"Estado de una operación y catálogos"},{"name":"Webhooks","description":"Suscripciones a eventos del comercio (entrega firmada HMAC-SHA256). Aislado por tenant: cada comercio gestiona solo los suyos."},{"name":"Configuración","description":"Configuración del comercio (lectura). La edición de la config money-critical es solo-admin."},{"name":"Onboarding","description":"Alta del comercio (self-signup público) y activación (KYC + aprobación, operada por el administrador de QuickPay)."},{"name":"WhatsApp Flows (Kit T2)","description":"Endpoint data-exchange: tu propio WABA/Flow delega el paso de pago a QuickPay."}],"paths":{"/switch/v1/cobrar/token":{"post":{"tags":["Cobros"],"summary":"Solicitar OTP al banco del pagador","description":"Paso previo a un cobro con token. El banco del pagador emite un código (válido ~120s).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenRequest"}}}},"responses":{"200":{"description":"Token solicitado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Resultado"}}}},"401":{"description":"No autorizado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}}}}},"/switch/v1/cobrar":{"post":{"tags":["Cobros"],"summary":"Cobrar (debitar al pagador)","description":"Ejecuta un cobro por débito inmediato. Incluye el `otp` capturado del pagador. Lee el estado en la respuesta (no por el código HTTP).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CobrarRequest"}}}},"responses":{"200":{"description":"Resultado del cobro","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Resultado"}}}},"400":{"description":"Validación","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"401":{"description":"No autorizado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}}}}},"/switch/v1/links":{"post":{"tags":["Links de cobro"],"summary":"Crear link de cobro","description":"Genera un enlace de pago alojado (hosted checkout) para que una persona pague el monto indicado. No mueve dinero al crearse. El pagador abre la `url`, ingresa su instrumento (banco, teléfono/cuenta, cédula) y paga con OTP en una página servida por QuickPay — **sin exponer tu API key**. Expira a las 24h por defecto (configurable con `expira_horas` o `expira_en`). El cobro es **money-safe: el link se paga EXACTAMENTE UNA VEZ** (reintentos no doble-debitan). Suscríbete al evento `link.pagado` para enterarte al instante.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkCobroRequest"}}}},"responses":{"201":{"description":"Link creado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkCreado"}}}},"400":{"description":"Validación","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"401":{"description":"No autorizado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"403":{"description":"Comercio suspendido (`comercio_suspendido`)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}}}},"get":{"tags":["Links de cobro"],"summary":"Listar links de cobro","description":"Lista los links del comercio (más recientes primero). Los filtros se combinan (todos deben cumplirse); pagina con `limit`/`offset`. Fechas sobre la del pago o, si no se pagó, la de creación: [desde, hasta). Un valor inválido en `desde`, `hasta`, `min`, `max`, `con_propina`, `mode` u `origen` responde 400 con `parametro`; un `estado` desconocido se ignora.","parameters":[{"name":"estado","in":"query","required":false,"description":"Uno o varios separados por coma: pendiente, cobrando, procesando, revision, pagado, expirado, cancelado.","schema":{"type":"string","example":"pagado,procesando"}},{"name":"desde","in":"query","required":false,"description":"ISO 8601 o epoch en milisegundos (incluido).","schema":{"type":"string","example":"2026-10-08T04:00:00Z"}},{"name":"hasta","in":"query","required":false,"description":"ISO 8601 o epoch en milisegundos (excluido).","schema":{"type":"string","example":"2026-10-09T04:00:00Z"}},{"name":"q","in":"query","required":false,"description":"Busca en concepto, nombre de quien pagó, referencia bancaria del pago, id y codigo_verificacion. Cada palabra debe aparecer.","schema":{"type":"string","maxLength":80}},{"name":"min","in":"query","required":false,"description":"Monto mínimo del enlace en Bs (incluido, sin propina).","schema":{"type":"number","minimum":0}},{"name":"max","in":"query","required":false,"description":"Monto máximo del enlace en Bs (incluido, sin propina).","schema":{"type":"number","minimum":0}},{"name":"con_propina","in":"query","required":false,"schema":{"type":"boolean"}},{"name":"mode","in":"query","required":false,"schema":{"type":"string","enum":["test","live"]}},{"name":"origen","in":"query","required":false,"schema":{"type":"string","enum":["api","panel","mi_pagina","boton","whatsapp"]}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"maximum":200}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"Links del comercio","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Link"}}}}},"400":{"description":"Filtro inválido (`validation`, con `parametro`)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"401":{"description":"No autorizado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}}}}},"/switch/v1/lotes":{"post":{"tags":["Links de cobro"],"summary":"Crear un LOTE de cobros (N links de una vez)","description":"Para cobrarle a una lista de personas: manda hasta 500 items `{monto, concepto, referencia_externa?, cliente?{nombre,telefono}, idempotency_key?}` y recibes un lote con un link por item (misma `url` y `wa_deeplink` de un link normal). Todo-o-nada: si un item no valida, no se crea ninguno. No mueve dinero. `idempotency_key` del lote: el mismo (comercio, key) devuelve el MISMO lote. Cada link nace con su propia llave de idempotencia, así que repetir la petición nunca duplica cobros. Los links expiran a las 24h por defecto (`expira_horas`, 1..720).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["items"],"properties":{"nombre":{"type":"string","maxLength":140},"idempotency_key":{"type":"string","maxLength":64},"expira_horas":{"type":"integer","minimum":1,"maximum":720,"default":24},"items":{"type":"array","minItems":1,"maxItems":500,"items":{"type":"object","required":["monto","concepto"],"properties":{"monto":{"type":"number","exclusiveMinimum":0},"concepto":{"type":"string","maxLength":140},"referencia_externa":{"type":"string","maxLength":64},"cliente":{"type":"object","properties":{"nombre":{"type":"string","maxLength":120},"telefono":{"type":"string","maxLength":20}}},"idempotency_key":{"type":"string","maxLength":64}}}}}}}}},"responses":{"201":{"description":"Lote con sus items (link_id, url, wa_deeplink, estado) y resumen","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Lote creado"}}}},"400":{"description":"Validación","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"401":{"description":"No autorizado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"403":{"description":"Comercio suspendido (`comercio_suspendido`)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"409":{"description":"idempotency_key de item ya usada","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}}}},"get":{"tags":["Links de cobro"],"summary":"Listar lotes","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"maximum":200}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"Lotes con su resumen por estado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Lotes del comercio"}}}},"401":{"description":"No autorizado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}}}}},"/switch/v1/lotes/{id}":{"get":{"tags":["Links de cobro"],"summary":"Estado de un lote","description":"Devuelve el lote, cada item con su link y estado (`pendiente`/`cobrando`/`procesando`/`revision`/`pagado`/`expirado`/`cancelado`) y un resumen: `por_estado`, `monto_total`, `monto_pagado`, `pagados`, `abiertos`, `completo`. Para enterarte al instante de cada pago usa el webhook `link.pagado`.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Lote con items y resumen","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Lote"}}}},"401":{"description":"No autorizado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"404":{"description":"No encontrado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}}}}},"/switch/v1/lotes/{id}/cancelar":{"post":{"tags":["Links de cobro"],"summary":"Cancelar lo que sigue pendiente de un lote","description":"Cancela SOLO los items todavía `pendiente`. Un item en `cobrando`/`procesando`/`revision`/`pagado` nunca se cancela desde aquí (puede haber un OTP o un débito en vuelo).","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"`cancelados`, `no_cancelables` y el lote actualizado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Resultado"}}}},"401":{"description":"No autorizado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"404":{"description":"No encontrado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}}}}},"/switch/v1/links/{id}":{"get":{"tags":["Links de cobro"],"summary":"Estado de un link de cobro","description":"Devuelve el estado actual de un link (`pendiente`/`pagado`/`expirado`/`cancelado`) y, si fue pagado, el `txn_id` de la transacción que lo liquidó. Tenant-scoped: solo devuelve links del comercio autenticado.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Estado del link","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Link"}}}},"401":{"description":"No autorizado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"404":{"description":"No encontrado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}}}}},"/switch/v1/verificar/{codigo}":{"get":{"tags":["Links de cobro"],"summary":"Verificar un comprobante por su código","description":"Público (sin llave). Devuelve `valido: true` con monto, moneda, comercio, fecha y hora (UTC y Caracas), referencia bancaria enmascarada (últimos 4), `prueba` (true si el pago fue de prueba y no movió dinero), el concepto del cobro, la referencia del pedido si vino de un lote, y cuántas veces se ha verificado el código (contando esta consulta) con la fecha de la primera. Nunca devuelve datos del pagador. Código inexistente: 404 `no_encontrado`. Límite: 20 consultas por minuto por IP (429 si se excede).","security":[],"parameters":[{"name":"codigo","in":"path","required":true,"schema":{"type":"string","example":"QP-7K3M9-XQ4TA"}}],"responses":{"200":{"description":"Comprobante válido","content":{"application/json":{"schema":{"type":"object","properties":{"valido":{"type":"boolean"},"estado":{"type":"string","example":"válido"},"monto":{"type":"number"},"moneda":{"type":"string"},"comercio":{"type":"string"},"fecha_hora":{"type":"string","format":"date-time"},"fecha_hora_caracas":{"type":"string"},"referencia_bancaria":{"type":"string","nullable":true,"example":"****1234"},"prueba":{"type":"boolean"},"mensaje":{"type":"string"},"concepto":{"type":"string","nullable":true},"referencia_externa":{"type":"string","nullable":true},"verificaciones":{"type":"integer","nullable":true,"description":"Veces que se ha verificado el código, contando esta consulta"},"primera_verificacion":{"type":"string","format":"date-time","nullable":true},"primera_verificacion_caracas":{"type":"string","nullable":true}}}}}},"404":{"description":"No encontramos un comprobante con ese código","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"429":{"description":"Demasiadas consultas","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}}}}},"/switch/v1/links/{id}/cancelar":{"post":{"tags":["Links de cobro"],"summary":"Cancelar un link de cobro","description":"Cancela el link SOLO si sigue `pendiente` (atómico). Si alguien ya está pagando (`cobrando`/`procesando`/`revision`) o ya pagó, responde 409 `no_cancelable` y no toca nada. Tenant-scoped.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"`{ id, estado: \"cancelado\" }`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Cancelado"}}}},"401":{"description":"No autorizado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"404":{"description":"No encontrado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"409":{"description":"`no_cancelable`: el link ya no está pendiente (incluye su `estado`)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}}}}},"/switch/v1/links/{id}/simular":{"post":{"tags":["Links de cobro"],"summary":"Simular el resultado de un link de PRUEBA","description":"Sólo para links creados con una llave `sk_test_` (si el link es de producción: 403 `solo_modo_prueba`). `resultado`: `pagado` (marca el link pagado y dispara `link.pagado`, y `link.acreditado` simulado si hay liquidación inmediata; referencia `SIMULADO`), `rechazado` (el link sigue `pendiente`, sin cambios) o `expirado` (vence al instante). 409 `no_simulable` si el link ya no está pendiente. En la página de pago de un link de prueba, además, la clave `11111111` simula un rechazo y `22222222` un pago que queda `procesando` y se aprueba solo a los ~30 s.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["resultado"],"properties":{"resultado":{"type":"string","enum":["pagado","rechazado","expirado"]}}}}}},"responses":{"200":{"description":"`{ id, estado, resultado, simulado: true }`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Simulado"}}}},"400":{"description":"Validación","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"401":{"description":"No autorizado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"403":{"description":"`solo_modo_prueba`: el link es de producción","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"404":{"description":"No encontrado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"409":{"description":"`no_simulable`","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}}}}},"/switch/v1/pagar":{"post":{"tags":["Pagos"],"summary":"Pagar (enviar dinero)","description":"Envía dinero a un beneficiario por teléfono o cuenta. El riel lo elige `via`: `credito_inmediato` (CCE, **riel por defecto**; admite teléfono o cuenta de cualquier banco, sin afiliación a pago móvil, liquida en el acto → LIQUIDADA) o `pago_movil` (P2P por teléfono, auxiliar opt-in, instantáneo → LIQUIDADA). A cuenta siempre es crédito inmediato.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PagarRequest"}}}},"responses":{"200":{"description":"Resultado del pago","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Resultado"}}}},"400":{"description":"Validación","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"401":{"description":"No autorizado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}}}}},"/switch/v1/consultar":{"post":{"tags":["Consultas"],"summary":"Consultar estado de una operación","description":"Devuelve el estado actualizado (PENDIENTE/LIQUIDADA/RECHAZADA) de una operación previa.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsultarRequest"}}}},"responses":{"200":{"description":"Estado actual","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Resultado"}}}},"401":{"description":"No autorizado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"404":{"description":"No encontrada (o de otro modo que la llave)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}}}}},"/switch/v1/webhooks":{"get":{"tags":["Webhooks"],"summary":"Listar webhooks del comercio","description":"Lista los endpoints de webhook del comercio autenticado (más recientes primero). El secreto NO se devuelve.","responses":{"200":{"description":"Webhooks del comercio","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Webhook"}}}}},"401":{"description":"No autorizado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}}}},"post":{"tags":["Webhooks"],"summary":"Crear webhook","description":"Registra un endpoint `https` y los `eventos` a recibir. Cada entrega se firma con `x-quicklink-signature: sha256=<hex>` (HMAC-SHA256 sobre el **cuerpo crudo**) usando el `secret`. El `secret` se devuelve **una sola vez** en esta respuesta (show-once); guárdalo para verificar firmas.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookCrearRequest"}}}},"responses":{"201":{"description":"Webhook creado (incluye `secret` una sola vez)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookConSecreto"}}}},"400":{"description":"Validación","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"401":{"description":"No autorizado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}}}}},"/switch/v1/webhooks/{id}":{"get":{"tags":["Webhooks"],"summary":"Detalle de un webhook","description":"Devuelve un webhook del comercio (sin secreto). 404 si no le pertenece.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Webhook","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Webhook"}}}},"401":{"description":"No autorizado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"404":{"description":"No encontrado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}}}},"patch":{"tags":["Webhooks"],"summary":"Editar un webhook","description":"Actualiza `url`, `eventos` y/o `estado` (`activo`/`inactivo`). 404 si no le pertenece.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEditarRequest"}}}},"responses":{"200":{"description":"Webhook actualizado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Webhook"}}}},"400":{"description":"Validación","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"401":{"description":"No autorizado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"404":{"description":"No encontrado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}}}},"delete":{"tags":["Webhooks"],"summary":"Eliminar un webhook","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Eliminado","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"id":{"type":"string"}}}}}},"401":{"description":"No autorizado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"404":{"description":"No encontrado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}}}}},"/switch/v1/webhooks/{id}/deliveries":{"get":{"tags":["Webhooks"],"summary":"Entregas de un webhook","description":"Últimas 50 entregas (observabilidad): estado, reintentos y próximo intento. 404 si no le pertenece. Con una sk_test_ sólo lista las entregas de avisos de prueba (data.mode = test).","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Entregas","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDelivery"}}}}},"401":{"description":"No autorizado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"404":{"description":"No encontrado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}}}}},"/switch/v1/config":{"get":{"tags":["Configuración"],"summary":"Configuración del comercio","description":"Devuelve la configuración visible del comercio autenticado: comisiones, IGTF, frecuencia de liquidación, `kyc_tier`, `estado` y `mode`. **No** expone datos crudos sensibles (cuenta de liquidación real, límites anti-estructuración). La edición de la config money-critical es solo-admin.","responses":{"200":{"description":"Configuración del comercio","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MerchantConfigView"}}}},"401":{"description":"No autorizado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"503":{"description":"Config no disponible","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}}}},"put":{"tags":["Configuración"],"summary":"Editar config (no permitido para el comercio)","description":"El comercio **no** edita su propia config money-critical: siempre responde `403`. La cambia el administrador de QuickPay.","responses":{"403":{"description":"Config solo-admin","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}}}}},"/switch/v1/comercios/registrar":{"post":{"tags":["Onboarding"],"summary":"Registrar comercio (self-signup público)","description":"Alta pública de un comercio. **No requiere `x-switch-key`.** Nace en estado `pendiente` con `kyc_tier` 0 y emite **una** llave de prueba `sk_test_…` (devuelta una sola vez). Para operar en LIVE, el comercio debe completar KYC y ser aprobado por el administrador.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegistrarComercioRequest"}}}},"responses":{"201":{"description":"Comercio creado (incluye `sk_test` una sola vez)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ComercioRegistrado"}}}},"400":{"description":"Validación","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"409":{"description":"Código duplicado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}}}}},"/switch/v1/flows/cobro/{flowId}":{"post":{"tags":["WhatsApp Flows (Kit T2)"],"summary":"Endpoint data-exchange del Flow de cobro","description":"Punto de **data-exchange** de WhatsApp Flows (esquema oficial de Meta). Tu Flow apunta aquí en vez de llamar directo al banco. El cuerpo es el **sobre cifrado** de Meta (`encrypted_flow_data` / `encrypted_aes_key` / `initial_vector` en base64; RSA-OAEP + AES-128-GCM, IV invertido) y la respuesta es **texto plano** `base64(cipher+tag)` cifrada con la misma AES key. `REQUEST_TOKEN` pide el OTP, `CONFIRM_PAYMENT` cobra. El tenant/cobrador salen del **binding** (`flowId` + `flow_secret` en el `flow_token` o header `x-flow-secret`), nunca del payload. No usa `x-switch-key`.","security":[],"parameters":[{"name":"flowId","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Id del binding comercio↔flow (lo devuelve el alta del binding)."},{"name":"x-flow-secret","in":"header","required":false,"schema":{"type":"string"},"description":"Alternativa a poner el flow_secret como prefijo del flow_token. Se compara en tiempo constante."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowEncryptedRequest"}}}},"responses":{"200":{"description":"Respuesta cifrada de Meta (texto plano base64(cipher+tag))","content":{"text/plain":{"schema":{"type":"string"}}}},"400":{"description":"Sobre mal formado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"421":{"description":"No se pudo descifrar / re-handshake de la AES key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"432":{"description":"Firma de Meta inválida (x-hub-signature-256)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"500":{"description":"Private key del Flow no configurada / error de cripto","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}}}}}},"components":{"securitySchemes":{"switchKey":{"type":"apiKey","in":"header","name":"x-switch-key","description":"Formato `<comercio>:<api_key>` (sk_test_… o sk_live_…)"}},"schemas":{"CobrarRequest":{"type":"object","properties":{"idempotency_key":{"type":"string","format":"uuid"},"monto":{"type":"object","properties":{"valor":{"type":"number","exclusiveMinimum":true,"minimum":0},"moneda":{"type":"string","minLength":3,"maxLength":3,"default":"VES"}},"required":["valor"],"additionalProperties":false},"concepto":{"type":"string","maxLength":120},"canal":{"type":"string","enum":["boton_pago","billetera_digital","pos","merchant","vpos","whatsapp"],"default":"boton_pago"},"origen_ip":{"type":"string"},"banco_preferido":{"type":"string"},"cobrador":{"type":"object","properties":{"tipo":{"type":"string","enum":["cuenta","telefono"]},"valor":{"type":"string","minLength":1},"banco":{"type":"string","pattern":"^\\d{4}$"},"identificacion":{"type":"string","minLength":1},"tipo_doc":{"type":"string","enum":["V","E","J","P","G"]},"nombre":{"type":"string","minLength":1,"maxLength":40}},"required":["tipo","valor","banco","identificacion","nombre"],"additionalProperties":false},"pagador":{"type":"object","properties":{"tipo":{"type":"string","enum":["cuenta","telefono"]},"valor":{"type":"string","minLength":1},"banco":{"type":"string","pattern":"^\\d{4}$"},"identificacion":{"type":"string","minLength":1},"tipo_doc":{"type":"string","enum":["V","E","J","P","G"]},"nombre":{"type":"string","minLength":1,"maxLength":40}},"required":["tipo","valor","banco","identificacion","nombre"],"additionalProperties":false},"otp":{"type":"string"},"telefono_pagador":{"type":"string","pattern":"^0?4\\d{9}$"}},"required":["idempotency_key","monto","concepto","cobrador","pagador"],"additionalProperties":false},"TokenRequest":{"type":"object","properties":{"idempotency_key":{"type":"string","format":"uuid"},"monto":{"type":"object","properties":{"valor":{"type":"number","exclusiveMinimum":true,"minimum":0},"moneda":{"type":"string","minLength":3,"maxLength":3,"default":"VES"}},"required":["valor"],"additionalProperties":false},"concepto":{"type":"string","maxLength":120},"canal":{"type":"string","enum":["boton_pago","billetera_digital","pos","merchant","vpos","whatsapp"],"default":"boton_pago"},"origen_ip":{"type":"string"},"banco_preferido":{"type":"string"},"cobrador":{"type":"object","properties":{"tipo":{"type":"string","enum":["cuenta","telefono"]},"valor":{"type":"string","minLength":1},"banco":{"type":"string","pattern":"^\\d{4}$"},"identificacion":{"type":"string","minLength":1},"tipo_doc":{"type":"string","enum":["V","E","J","P","G"]},"nombre":{"type":"string","minLength":1,"maxLength":40}},"required":["tipo","valor","banco","identificacion","nombre"],"additionalProperties":false},"pagador":{"type":"object","properties":{"tipo":{"type":"string","enum":["cuenta","telefono"]},"valor":{"type":"string","minLength":1},"banco":{"type":"string","pattern":"^\\d{4}$"},"identificacion":{"type":"string","minLength":1},"tipo_doc":{"type":"string","enum":["V","E","J","P","G"]},"nombre":{"type":"string","minLength":1,"maxLength":40}},"required":["tipo","valor","banco","identificacion","nombre"],"additionalProperties":false},"telefono_pagador":{"type":"string","pattern":"^0?4\\d{9}$"}},"required":["idempotency_key","monto","concepto","cobrador","pagador"],"additionalProperties":false},"PagarRequest":{"type":"object","properties":{"idempotency_key":{"type":"string","format":"uuid"},"monto":{"type":"object","properties":{"valor":{"type":"number","exclusiveMinimum":true,"minimum":0},"moneda":{"type":"string","minLength":3,"maxLength":3,"default":"VES"}},"required":["valor"],"additionalProperties":false},"concepto":{"type":"string","maxLength":120},"canal":{"type":"string","enum":["boton_pago","billetera_digital","pos","merchant","vpos","whatsapp"],"default":"boton_pago"},"origen_ip":{"type":"string"},"banco_preferido":{"type":"string"},"ordenante":{"type":"object","properties":{"tipo":{"type":"string","enum":["cuenta","telefono"]},"valor":{"type":"string","minLength":1},"banco":{"type":"string","pattern":"^\\d{4}$"},"identificacion":{"type":"string","minLength":1},"tipo_doc":{"type":"string","enum":["V","E","J","P","G"]},"nombre":{"type":"string","minLength":1,"maxLength":40}},"required":["tipo","valor","banco","identificacion","nombre"],"additionalProperties":false},"beneficiario":{"type":"object","properties":{"tipo":{"type":"string","enum":["cuenta","telefono"]},"valor":{"type":"string","minLength":1},"banco":{"type":"string","pattern":"^\\d{4}$"},"identificacion":{"type":"string","minLength":1},"tipo_doc":{"type":"string","enum":["V","E","J","P","G"]},"nombre":{"type":"string","minLength":1,"maxLength":40}},"required":["tipo","valor","banco","identificacion","nombre"],"additionalProperties":false},"proposito":{"type":"string","enum":["transferencia","nomina","proveedor"],"default":"transferencia"},"via":{"type":"string","enum":["credito_inmediato","pago_movil"],"default":"credito_inmediato"}},"required":["idempotency_key","monto","concepto","ordenante","beneficiario"],"additionalProperties":false},"ConsultarRequest":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"],"additionalProperties":false},"LinkCobroRequest":{"type":"object","required":["monto","concepto"],"properties":{"monto":{"type":"number","description":"Monto a cobrar (bruto que ve el pagador)"},"moneda":{"type":"string","default":"VES","description":"ISO-4217"},"concepto":{"type":"string","maxLength":140},"expira_horas":{"type":"integer","minimum":1,"maximum":720,"default":24,"description":"TTL en horas (default 24h). Ignorado si envías `expira_en`."},"expira_en":{"type":"string","format":"date-time","description":"Fecha/hora ISO-8601 de expiración (gana sobre `expira_horas`). Debe ser futura."},"success_url":{"type":"string","maxLength":400,"description":"https://. A dónde vuelve el pagador al pagar (con `estado` y `referencia`)."},"failure_url":{"type":"string","maxLength":400,"description":"https://. A dónde vuelve el pagador si el pago no puede continuar: el link venció (`estado=expirado`), lo cancelaste (`cancelado`) o el pagador elige \"Cancelar y volver\" tras un rechazo (`rechazado`)."}}},"LinkCreado":{"type":"object","properties":{"id":{"type":"string"},"url":{"type":"string","description":"URL del hosted checkout para compartir con el pagador"},"monto":{"type":"number"},"moneda":{"type":"string"},"concepto":{"type":"string"},"estado":{"type":"string","enum":["pendiente"]},"expires_at":{"type":"string","format":"date-time"},"mode":{"type":"string","enum":["test","live"]}}},"Link":{"type":"object","properties":{"id":{"type":"string"},"url":{"type":"string"},"comercio":{"type":"string"},"monto":{"type":"number"},"moneda":{"type":"string"},"concepto":{"type":"string","nullable":true},"estado":{"type":"string","enum":["pendiente","cobrando","procesando","revision","pagado","expirado","cancelado"]},"expires_at":{"type":"string","format":"date-time","nullable":true},"txn_id":{"type":"string","nullable":true,"description":"id de la transacción que pagó el link (si estado=pagado)"},"created_at":{"type":"string","format":"date-time"},"paid_at":{"type":"string","format":"date-time","nullable":true},"propina":{"type":"number","example":0,"description":"Propina que agregó quien pagó (Bs; 0 si no agregó). `monto` no la incluye."},"total":{"type":"number","example":450,"description":"Lo debitado: monto + propina."},"codigo_verificacion":{"type":"string","nullable":true,"example":"QP-7K3M9-XQ4TA","description":"Código del comprobante (sólo si estado=pagado). Se verifica en /verificar."},"pagador":{"type":"object","description":"Lo que se sabe de quien pagó. No trae cédula, teléfono ni nombre.","properties":{"alta_cliente":{"type":"boolean","example":false,"description":"true si quien paga marcó \"Quiero ser cliente de <comercio> y recibir sus novedades\" en la página de pago."}}}}},"Resultado":{"type":"object","properties":{"id":{"type":"string"},"estado":{"type":"string","enum":["ACEPTADA","PENDIENTE","LIQUIDADA","RECHAZADA","ERROR","DESCONOCIDA","ANULADA"]},"referencia_banco":{"type":"string","nullable":true},"endtoend":{"type":"string","nullable":true,"default":null},"motivo_rechazo":{"type":"string","enum":["SALDO_INSUFICIENTE","OTP_INVALIDO","CUENTA_NO_EXISTE","CUENTA_BLOQUEADA","CUENTA_NO_DEBITO","CUENTA_NO_CREDITO","MONTO_INVALIDO","EXCEDE_LIMITE","BENEFICIARIO_NO_COINCIDE","NO_AFILIADO_SERVICIO","BANCO_NO_DISPONIBLE","DUPLICADA","FUERA_DE_HORARIO","CANCELADA_POR_PAGADOR","PENDIENTE_BCV","RECHAZO_TECNICO","FIRMA_AUTH","NO_SOPORTADO","BLOQUEO_RIESGO","NETO_NEGATIVO","KYC_INSUFICIENTE","COMERCIO_NO_ACTIVO","SIN_CUENTA_LIQUIDACION","ANULACION_RECHAZADA","SIN_DEBITO_EN_EXTRACTO","DESCONOCIDO"],"nullable":true,"default":null},"intentos":{"type":"integer","minimum":0,"default":1},"simulado":{"type":"boolean"}},"required":["id","estado","referencia_banco"],"additionalProperties":false},"WebhookEventos":{"type":"array","minItems":1,"items":{"type":"string","enum":["transaccion.aprobada","transaccion.rechazada","transaccion.liquidada","link.pagado","link.acreditado","link.devuelto"]},"description":"Eventos a recibir."},"WebhookCrearRequest":{"type":"object","required":["url","eventos"],"properties":{"url":{"type":"string","format":"uri","description":"Endpoint `https` que recibe los POST firmados."},"eventos":{"$ref":"#/components/schemas/WebhookEventos"}}},"WebhookEditarRequest":{"type":"object","minProperties":1,"properties":{"url":{"type":"string","format":"uri"},"eventos":{"$ref":"#/components/schemas/WebhookEventos"},"estado":{"type":"string","enum":["activo","inactivo"]}},"description":"Indica al menos uno de `url`, `eventos` o `estado`."},"Webhook":{"type":"object","properties":{"id":{"type":"string"},"comercio":{"type":"string"},"url":{"type":"string","format":"uri"},"eventos":{"$ref":"#/components/schemas/WebhookEventos"},"estado":{"type":"string","enum":["activo","inactivo"]},"created_at":{"type":"string","format":"date-time"}}},"WebhookConSecreto":{"allOf":[{"$ref":"#/components/schemas/Webhook"},{"type":"object","properties":{"secret":{"type":"string","description":"Secreto HMAC. Se devuelve **una sola vez** (show-once)."}}}]},"WebhookDelivery":{"type":"object","properties":{"id":{"type":"string"},"evento":{"type":"string"},"txn_id":{"type":"string","nullable":true},"estado":{"type":"string"},"status":{"type":"integer","nullable":true,"description":"Código HTTP de la respuesta de tu endpoint."},"ok":{"type":"boolean"},"intentos":{"type":"integer"},"max_intentos":{"type":"integer"},"next_attempt_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time"}}},"MerchantConfigView":{"type":"object","properties":{"comision":{"type":"object","properties":{"cobrar":{"type":"object","properties":{"modo":{"type":"string"},"pct":{"type":"number"},"fija":{"type":"number"}}},"pagar":{"type":"object","properties":{"modo":{"type":"string"},"pct":{"type":"number"},"fija":{"type":"number"}}}}},"igtf":{"type":"object","properties":{"aplica":{"type":"boolean"},"pct":{"type":"number"},"modo":{"type":"string"}}},"settlement":{"type":"object","properties":{"frecuencia":{"type":"string"},"cuenta_configurada":{"type":"boolean"}}},"kyc_tier":{"type":"integer"},"estado":{"type":"string"},"mode":{"type":"string","enum":["test","live"]}}},"RegistrarComercioRequest":{"type":"object","required":["codigo","nombre","rif","contacto"],"properties":{"codigo":{"type":"string","pattern":"^[a-z0-9_-]{2,64}$","description":"Identificador único del comercio (slug)."},"nombre":{"type":"string","maxLength":120},"rif":{"type":"string","maxLength":20},"contacto":{"type":"string","maxLength":160,"description":"Correo o teléfono de contacto."}}},"ComercioRegistrado":{"type":"object","properties":{"codigo":{"type":"string"},"estado":{"type":"string","enum":["pendiente"]},"sk_test":{"type":"string","description":"Llave de prueba `sk_test_…`. Se devuelve **una sola vez**."}}},"FlowEncryptedRequest":{"type":"object","required":["encrypted_flow_data","encrypted_aes_key","initial_vector"],"properties":{"encrypted_flow_data":{"type":"string","description":"AES-128-GCM(flow_data) + tag, en base64."},"encrypted_aes_key":{"type":"string","description":"AES key efímera envuelta con RSA-OAEP(SHA-256), en base64."},"initial_vector":{"type":"string","description":"IV de 16 bytes en base64 (la respuesta usa su versión invertida byte a byte)."}}}}}}