{
  "openapi": "3.1.0",
  "info": {
    "title": "PulseID — API pública de verificación de presencia",
    "version": "1.0.0",
    "description": "Superficie pública de verificación de PulseID: solo lectura, JSON, CORS abierto, sin API key (límite de uso por IP). No expone datos personales ni fisiológicos crudos. No se atesta identidad legal: se atesta continuidad de la misma señal fisiológica de origen desde un dispositivo enrolado. Servicio operativo en beta. Descubrimiento completo: https://pulseids.com/.well-known/pulseid-verify · MCP: https://pulseids.com/mcp",
    "contact": { "url": "https://pulseids.com/agentes" }
  },
  "servers": [{ "url": "https://pulseids.com" }],
  "paths": {
    "/.well-known/pulseid-verify": {
      "get": {
        "operationId": "verifyAny",
        "summary": "Autodetecta el tipo de identificador y verifica (recomendado para agentes)",
        "parameters": [
          {
            "name": "ref",
            "in": "query",
            "required": false,
            "schema": { "type": "string" },
            "description": "Identificador a verificar: 16 hex (certificate_id), 64 hex (seal_hash) o referencia alfanumérica 3-128 (proof_ref). Sin `ref`, devuelve el manifiesto autodescriptivo."
          }
        ],
        "responses": {
          "200": { "description": "Manifiesto (sin ref) o resultado de verificación con `verified`, metadatos y `human_readable_summary`." },
          "400": { "description": "Formato de identificador no reconocido; el cuerpo lista los formatos aceptados." },
          "404": { "description": "El identificador no existe." },
          "429": { "description": "Límite de uso por IP superado." }
        }
      }
    },
    "/api/verify": {
      "get": {
        "operationId": "verifySeal",
        "summary": "Verifica un sello por su hash SHA-256",
        "parameters": [
          { "name": "s", "in": "query", "required": true, "schema": { "type": "string", "pattern": "^[0-9a-f]{64}$" } }
        ],
        "responses": {
          "200": { "description": "Sello encontrado: emisor anónimo, fecha, programa." },
          "404": { "description": "No existe un sello con ese hash." },
          "429": { "description": "Límite de uso por IP superado." }
        }
      }
    },
    "/api/zkbp-verify": {
      "get": {
        "operationId": "verifyZkProof",
        "summary": "Reverifica la prueba de conocimiento cero de un certificado",
        "description": "Prueba de rango (Pedersen + Sigma sobre secp256k1): la coherencia de la sesión superó el umbral del programa, sin revelar el valor real.",
        "parameters": [
          { "name": "cert", "in": "query", "required": true, "schema": { "type": "string", "pattern": "^[0-9a-f]{16}$" } }
        ],
        "responses": {
          "200": { "description": "Resultado con `verified` (la prueba se reverifica criptográficamente en cada petición)." },
          "404": { "description": "Certificado inexistente." },
          "429": { "description": "Límite de uso por IP superado." }
        }
      }
    },
    "/api/verify-proof": {
      "get": {
        "operationId": "verifyCertifiedEvent",
        "summary": "Revalida un evento certificado (firma HMAC + hash del payload)",
        "parameters": [
          { "name": "proof_ref", "in": "query", "required": true, "schema": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]{2,127}$" } }
        ],
        "responses": {
          "200": { "description": "Vista pública con banderas explícitas de qué se expone." },
          "404": { "description": "Referencia inexistente." },
          "429": { "description": "Límite de uso por IP superado." }
        }
      }
    },
    "/api/ots-verify": {
      "get": {
        "operationId": "verifyBitcoinAnchor",
        "summary": "Estado del anclaje Bitcoin (OpenTimestamps) de un certificado",
        "parameters": [
          { "name": "seal", "in": "query", "required": true, "schema": { "type": "string", "pattern": "^[0-9a-f]{16}$" } }
        ],
        "responses": {
          "200": { "description": "Estado del anclaje; si está anclado, el recibo .ots es descargable y verificable de forma independiente." },
          "404": { "description": "Certificado inexistente." },
          "429": { "description": "Límite de uso por IP superado." }
        }
      }
    }
  }
}
