{
  "openapi": "3.0.3",
  "info": {
    "title": "WhatsApp Gateway (3 Accounts)",
    "version": "2.3.1",
    "description": "Gateway para tres cuentas aisladas de WhatsApp Web. Cada sesión posee QR, webhook, secret HMAC, checklist de eventos e historial independientes."
  },
  "servers": [
    { "url": "https://35-253-122-129.sslip.io", "description": "Producción HTTPS" },
    { "url": "http://127.0.0.1:8080", "description": "Local" }
  ],
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key" }
    },
    "schemas": {
      "Session": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "example": "main" },
          "status": { "type": "string", "enum": ["STOPPED", "STARTING", "QR_READY", "AUTHENTICATED", "SYNCING", "READY", "RESETTING", "LOGGED_OUT", "DISCONNECTED", "AUTH_FAILURE", "ERROR"] },
          "desiredActive": { "type": "boolean" },
          "hasQr": { "type": "boolean" },
          "qrVersion": { "type": "integer" },
          "qrIssuedAt": { "type": "string", "format": "date-time", "nullable": true },
          "authenticatedAt": { "type": "string", "format": "date-time", "nullable": true },
          "syncProgress": { "type": "integer", "minimum": 0, "maximum": 100, "nullable": true },
          "syncMessage": { "type": "string", "nullable": true },
          "lastError": { "type": "string", "nullable": true },
          "nextRestartAt": { "type": "string", "format": "date-time", "nullable": true }
        },
        "required": ["id", "status", "desiredActive", "hasQr", "qrVersion"]
      },
      "WebhookConfig": {
        "type": "object",
        "properties": {
          "url": { "type": "string", "format": "uri" },
          "active": { "type": "boolean" },
          "secret": { "type": "string", "writeOnly": true },
          "hasSecret": { "type": "boolean", "readOnly": true },
          "sessionId": { "type": "string", "readOnly": true },
          "events": {
            "type": "array",
            "items": { "type": "string" },
            "example": ["message.received", "message.ack", "session.ready"]
          }
        },
        "required": ["url", "active", "events"],
        "description": "Configuración exclusiva de una sesión. Omitir secret conserva el actual; secret vacío lo elimina. Las entregas se firman como sha256=HEX(HMAC_SHA256(secret, body HTTP crudo)) en X-Webhook-Signature."
      },
      "GatewayEvent": {
        "type": "object",
        "properties": {
          "eventId": { "type": "string", "format": "uuid" },
          "eventType": { "type": "string" },
          "sessionId": { "type": "string" },
          "occurredAt": { "type": "string", "format": "date-time" },
          "data": { "type": "object" }
        },
        "required": ["eventId", "eventType", "sessionId", "occurredAt", "data"]
      }
    }
  },
  "paths": {
    "/health": {
      "get": {
        "summary": "Liveness pública",
        "responses": { "200": { "description": "Proceso operativo" } }
      }
    },
    "/v1/health": {
      "get": {
        "summary": "Liveness pública v1",
        "responses": { "200": { "description": "Proceso operativo" } }
      }
    },
    "/v1/readiness": {
      "get": {
        "summary": "Disponibilidad funcional de WhatsApp y storage",
        "responses": {
          "200": { "description": "Sesión READY" },
          "503": { "description": "Sesión o storage todavía no disponible" }
        }
      }
    },
    "/v1/session": {
      "get": {
        "summary": "Estado de la sesión",
        "security": [{ "ApiKeyAuth": [] }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Session" } } } },
          "401": { "description": "Unauthorized" }
        }
      }
    },
    "/v1/session/start": {
      "post": {
        "summary": "Iniciar la sesión de forma idempotente",
        "security": [{ "ApiKeyAuth": [] }],
        "responses": {
          "200": { "description": "Ya estaba ejecutándose" },
          "202": { "description": "Inicio programado" },
          "401": { "description": "Unauthorized" }
        }
      }
    },
    "/v1/session/qr": {
      "get": {
        "summary": "Obtener QR actual como Data URL",
        "security": [{ "ApiKeyAuth": [] }],
        "responses": {
          "200": { "description": "QR actual o estado waiting" },
          "401": { "description": "Unauthorized" }
        }
      }
    },
    "/v1/session/qr/refresh": {
      "post": {
        "summary": "Cerrar la vinculación y generar un QR limpio",
        "security": [{ "ApiKeyAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": { "confirm": { "type": "boolean", "enum": [true] } },
                "required": ["confirm"]
              }
            }
          }
        },
        "responses": {
          "202": { "description": "Nueva autenticación iniciada" },
          "400": { "description": "Falta confirmación" },
          "401": { "description": "Unauthorized" }
        }
      }
    },
    "/v1/sessions": {
      "get": {
        "summary": "Listar las cuentas y el límite configurado",
        "security": [{ "ApiKeyAuth": [] }],
        "responses": { "200": { "description": "Lista de hasta tres cuentas" }, "401": { "description": "Unauthorized" } }
      },
      "post": {
        "summary": "Crear una cuenta aislada",
        "security": [{ "ApiKeyAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": { "id": { "type": "string", "minLength": 2, "maxLength": 40, "pattern": "^[A-Za-z0-9_-]+$", "example": "ventas" } },
                "required": ["id"]
              }
            }
          }
        },
        "responses": { "201": { "description": "Cuenta creada" }, "400": { "description": "ID inválido" }, "409": { "description": "ID existente o límite alcanzado" } }
      }
    },
    "/v1/sessions/{id}": {
      "get": {
        "summary": "Obtener una cuenta",
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "Estado", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Session" } } } }, "404": { "description": "No encontrada" } }
      },
      "delete": {
        "summary": "Eliminar cuenta y credenciales (excepto main)",
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "Eliminada" }, "404": { "description": "No encontrada" }, "409": { "description": "La cuenta principal no se elimina" } }
      }
    },
    "/v1/sessions/{id}/start": {
      "post": {
        "summary": "Iniciar una cuenta de forma idempotente",
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "Ya iniciada" }, "202": { "description": "Inicio programado" }, "404": { "description": "No encontrada" } }
      }
    },
    "/v1/sessions/{id}/qr": {
      "get": {
        "summary": "Obtener el QR actual de una cuenta",
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "QR actual o estado waiting" }, "404": { "description": "No encontrada" } }
      }
    },
    "/v1/sessions/{id}/qr/refresh": {
      "post": {
        "summary": "Destruir la vinculación de una cuenta y generar un QR limpio",
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object", "properties": { "confirm": { "type": "boolean", "enum": [true] } }, "required": ["confirm"] } } }
        },
        "responses": { "202": { "description": "Nueva autenticación iniciada" }, "400": { "description": "Falta confirmación" }, "404": { "description": "No encontrada" } }
      }
    },
    "/v1/sessions/{id}/messages/send": {
      "post": {
        "summary": "Enviar texto o media desde una cuenta específica",
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "Idempotency-Key", "in": "header", "required": false, "schema": { "type": "string", "maxLength": 128 } }
        ],
        "responses": { "200": { "description": "Mensaje enviado" }, "400": { "description": "Payload inválido" }, "404": { "description": "Cuenta no encontrada" }, "409": { "description": "Cuenta no READY" } }
      }
    },
    "/v1/messages/send": {
      "post": {
        "summary": "Enviar texto o media",
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [
          { "name": "Idempotency-Key", "in": "header", "required": false, "schema": { "type": "string", "maxLength": 128 }, "description": "Clave única recomendada para impedir envíos duplicados" }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "sessionId": { "type": "string", "example": "ventas" },
                  "to": { "type": "string" },
                  "text": { "type": "string" },
                  "caption": { "type": "string" },
                  "mediaUrl": { "type": "string", "format": "uri" },
                  "mimeType": { "type": "string" },
                  "fileName": { "type": "string" },
                  "mediaBase64": { "type": "string" }
                },
                "required": ["to"]
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Mensaje enviado" },
          "400": { "description": "Payload inválido" },
          "409": { "description": "Sesión no READY" },
          "401": { "description": "Unauthorized" }
        }
      }
    },
    "/v1/messages": {
      "get": {
        "summary": "Auditoría persistente de mensajes salientes",
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [
          { "name": "status", "in": "query", "schema": { "type": "string", "enum": ["PROCESSING", "SENT", "FAILED", "UNCERTAIN"] } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 500, "default": 100 } }
        ],
        "responses": { "200": { "description": "Historial sanitizado" }, "400": { "description": "Filtro inválido" } }
      }
    },
    "/v1/messages/{commandId}": {
      "get": {
        "summary": "Consultar un comando de envío",
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [{ "name": "commandId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "responses": { "200": { "description": "Comando encontrado" }, "404": { "description": "No encontrado" } }
      }
    },
    "/v1/sessions/{id}/webhook/events": {
      "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
      "get": {
        "summary": "Catálogo/checklist de eventos para configurar una sesión",
        "security": [{ "ApiKeyAuth": [] }],
        "responses": { "200": { "description": "Catálogo, defaults y especificación HMAC" }, "404": { "description": "Sesión no encontrada" } }
      }
    },
    "/v1/sessions/{id}/webhook": {
      "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
      "get": {
        "summary": "Configuración y estadísticas del webhook exclusivo de la sesión",
        "security": [{ "ApiKeyAuth": [] }],
        "responses": { "200": { "description": "Configuración sin revelar secret" }, "404": { "description": "Sesión no encontrada" } }
      },
      "put": {
        "summary": "Configurar URL, secret HMAC, activación y checklist de la sesión",
        "description": "La firma X-Webhook-Signature usa HMAC-SHA256 sobre los bytes exactos del body HTTP. Modificar esta ruta no altera otras sesiones.",
        "security": [{ "ApiKeyAuth": [] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookConfig" } } } },
        "responses": { "200": { "description": "Configuración guardada" }, "400": { "description": "Configuración inválida" }, "404": { "description": "Sesión no encontrada" } }
      },
      "delete": {
        "summary": "Desactivar y limpiar sólo el webhook de la sesión",
        "security": [{ "ApiKeyAuth": [] }],
        "responses": { "200": { "description": "Webhook eliminado" }, "404": { "description": "Sesión no encontrada" } }
      }
    },
    "/v1/sessions/{id}/webhook/test": {
      "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
      "post": {
        "summary": "Enviar un evento de prueba al webhook de la sesión",
        "security": [{ "ApiKeyAuth": [] }],
        "responses": { "202": { "description": "Entrega encolada" }, "404": { "description": "Sesión no encontrada" }, "409": { "description": "Webhook inactivo" } }
      }
    },
    "/v1/sessions/{id}/webhook/deliveries": {
      "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
      "get": {
        "summary": "Historial exclusivo de entregas de la sesión",
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [
          { "name": "status", "in": "query", "schema": { "type": "string", "enum": ["PENDING", "PROCESSING", "RETRYING", "SUCCESS", "DEAD", "CANCELED"] } },
          { "name": "eventType", "in": "query", "schema": { "type": "string" } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 500, "default": 100 } }
        ],
        "responses": { "200": { "description": "Historial" }, "400": { "description": "Filtro inválido" }, "404": { "description": "Sesión no encontrada" } }
      }
    },
    "/v1/sessions/{id}/webhook/deliveries/{deliveryId}": {
      "parameters": [
        { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } },
        { "name": "deliveryId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }
      ],
      "get": {
        "summary": "Detalle de una entrega perteneciente a la sesión",
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [{ "name": "includeEvent", "in": "query", "schema": { "type": "boolean", "default": false } }],
        "responses": { "200": { "description": "Entrega" }, "404": { "description": "Sesión o entrega no encontrada" } }
      }
    },
    "/v1/sessions/{id}/webhook/deliveries/{deliveryId}/replay": {
      "parameters": [
        { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } },
        { "name": "deliveryId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }
      ],
      "post": {
        "summary": "Reencolar una entrega terminal de la misma sesión",
        "security": [{ "ApiKeyAuth": [] }],
        "responses": { "202": { "description": "Replay encolado" }, "404": { "description": "Sesión o entrega no encontrada" }, "409": { "description": "Webhook inactivo o entrega activa" } }
      }
    },
    "/v1/webhook/events": {
      "get": {
        "summary": "Alias del catálogo para main",
        "security": [{ "ApiKeyAuth": [] }],
        "responses": { "200": { "description": "Catálogo agrupado" } }
      }
    },
    "/v1/webhook": {
      "get": {
        "summary": "Alias de la configuración webhook de main",
        "security": [{ "ApiKeyAuth": [] }],
        "responses": { "200": { "description": "OK" } }
      },
      "put": {
        "summary": "Alias para configurar el webhook de main",
        "security": [{ "ApiKeyAuth": [] }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookConfig" } } }
        },
        "responses": {
          "200": { "description": "Configuración guardada" },
          "400": { "description": "Configuración inválida" }
        }
      },
      "delete": {
        "summary": "Desactivar y limpiar el webhook",
        "security": [{ "ApiKeyAuth": [] }],
        "responses": { "200": { "description": "Webhook eliminado" } }
      }
    },
    "/v1/webhook/test": {
      "post": {
        "summary": "Encolar entrega manual de prueba",
        "security": [{ "ApiKeyAuth": [] }],
        "responses": {
          "202": { "description": "Entrega encolada" },
          "409": { "description": "Webhook inactivo" }
        }
      }
    },
    "/v1/webhook/deliveries": {
      "get": {
        "summary": "Historial persistente de entregas",
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [
          { "name": "status", "in": "query", "schema": { "type": "string", "enum": ["PENDING", "PROCESSING", "RETRYING", "SUCCESS", "DEAD", "CANCELED"] } },
          { "name": "eventType", "in": "query", "schema": { "type": "string" } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 500, "default": 100 } }
        ],
        "responses": { "200": { "description": "Historial" }, "400": { "description": "Filtro inválido" } }
      }
    },
    "/v1/webhook/deliveries/{deliveryId}": {
      "get": {
        "summary": "Detalle de una entrega",
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [
          { "name": "deliveryId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } },
          { "name": "includeEvent", "in": "query", "schema": { "type": "boolean", "default": false } }
        ],
        "responses": { "200": { "description": "Entrega" }, "404": { "description": "No encontrada" } }
      }
    },
    "/v1/webhook/deliveries/{deliveryId}/replay": {
      "post": {
        "summary": "Reencolar una entrega terminal",
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [{ "name": "deliveryId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "responses": { "202": { "description": "Replay encolado" }, "404": { "description": "No encontrada" }, "409": { "description": "Webhook inactivo o entrega todavía activa" } }
      }
    },
    "/v1/events": {
      "get": {
        "summary": "Eventos internos recientes y sanitizados",
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 120, "minimum": 1, "maximum": 1000 } }
        ],
        "responses": { "200": { "description": "OK" } }
      }
    }
  }
}
