◈ PULSEIDAPI DE VERIFICACIÓN

API pública de verificación

PulseID expone 4 endpoints REST de solo lectura para verificar, desde cualquier sistema externo, la evidencia fisiológica que emite el hub beta: sellos individuales, eventos certificados completos, anclaje en Bitcoin (OpenTimestamps) y pruebas de conocimiento cero (ZK-BP). Todos son públicos, no requieren API key y devuelven JSON — los identificadores que reciben (hash de sello, proof_ref, certificate ID) son anónimos y no contienen datos personales ni fisiológicos crudos.

En esta página
1. GET /api/verify — verificar un sello por hash2. POST /api/verify — verificar un token PID firmado3. GET /api/verify-proof — verificar un evento certificado completo4. GET /api/ots-verify — estado de anclaje Bitcoin5. GET /api/zkbp-verify — prueba de conocimiento cero6. GET /.well-known/pulseid-verify — descubrimiento para agentes automatizados7. Widget embebible — badge de confianzaConvenciones generales de error
GET/api/verify?s=<sealHash>

Verifica un sello individual (SHA-256 de una operación sellada en el hub beta). Es el endpoint que escanea la cámara de un teléfono al leer un QR de certificado, y el que usa el panel “Bridge API” embebido en /beta. Si la petición trae Accept: text/html (un navegador, no una integración programática) devuelve una tarjeta HTML en vez de JSON — pensado para cuando alguien escanea el QR directamente con la cámara.

Parámetros
NombreTipoRequeridoDescripción
sstring (64 hex)Hash SHA-256 del sello a verificar.
curl
curl "https://pulseids.com/api/verify?s=<sealHash>"
Response 200 (sello válido)
{
  "valid": true,
  "source": "database",
  "certificateId": "a1b2c3d4e5f6a7b8",
  "issuedAt": "2026-07-10T14:32:00.000Z",
  "program": "personal"
}
Response 404 (no encontrado)
{
  "valid": false,
  "error": "Sello no encontrado"
}
Códigos de respuesta
HTTPerror / resultSignificado
400Parámetro s requeridoEl parámetro s falta o no son 64 caracteres hexadecimales.
404Sello no encontradoNo existe ningún sello con ese hash.
502Error consultando la base de datosFallo transitorio de Supabase.
503Servicio de verificación no configuradoFaltan variables de entorno en el despliegue.
500Error interno de verificaciónExcepción no controlada.
CORS abierto (Access-Control-Allow-Origin: *) · sin autenticación · sin rate limit propio en esta rama GET.
POST/api/verify

Verifica un token PID (Pulse Identity Document) firmado con HMAC-SHA256 por un dispositivo registrado — formato header.payload.sig. A diferencia del GET anterior (que solo confirma que un sello existe en base de datos), este recalcula la firma HMAC contra la clave del dispositivo y comprueba caducidad, estado fisiológico (COHERENT) y longitud de la cadena de prueba. Es el endpoint pensado para integraciones B2B que ya poseen un token PID (por ejemplo, recibido de un usuario) y quieren verificarlo por su cuenta.

Parámetros
NombreTipoRequeridoDescripción
tokenstringToken PID completo: header.payload.sig (base64url).
didstring (16 hex)Identificador del dispositivo que firmó el token.
curl
curl -X POST https://pulseids.com/api/verify \
  -H "Content-Type: application/json" \
  -d '{
    "token": "<header>.<payload>.<sig>",
    "did": "a1b2c3d4e5f6a7b8"
  }'
Response 200 (result: VALID)
{
  "result": "VALID",
  "valid": true,
  "device_label": "Amazfit Balance",
  "payload": {
    "sub": "...",
    "iat": 1752345600000,
    "exp": 1752347400000,
    "state": "COHERENT",
    "uv": 1,
    "proof": { "chain": 4 },
    "pid": "..."
  },
  "verified_at": "2026-07-12T10:00:00.000Z",
  "message": "Identidad fisiológica verificada. Cadena criptográfica: 4 eslabones."
}
Response 200 (result: INVALID_STATE, ejemplo de fallo de dominio)
{
  "result": "INVALID_STATE",
  "valid": false,
  "payload": { "...": "..." },
  "verified_at": "2026-07-12T10:00:00.000Z",
  "message": "Estado fisiológico insuficiente: BASELINE. Se requiere COHERENT."
}
Valores posibles de “result”
resultvalidCausa
VALIDtrueFirma, caducidad, estado y cadena de prueba correctos.
MALFORMEDfalseBody inválido, token sin 3 partes, o header con typ/alg inesperado.
EXPIREDfalseEl token superó su exp (TTL de 30 minutos).
UNKNOWN_DEVICEfalseEl did no está registrado en PulseID.
INVALID_SIGfalseLa firma HMAC no coincide con la clave del dispositivo.
INVALID_STATEfalseEstado fisiológico distinto de COHERENT, o cadena de prueba vacía.
RATE_LIMITEDfalseMás de 60 verificaciones/min desde la misma IP (ver abajo).
Códigos de respuesta
HTTPerror / resultSignificado
400MALFORMEDBody JSON inválido, o faltan los campos token/did.
405Method not allowedMétodo distinto de POST/OPTIONS.
429RATE_LIMITEDLímite de 60 peticiones/min por IP superado — header Retry-After indica cuándo reintentar.
Nota de diseño: los fallos de verificación de dominio (token caducado, firma inválida, estado insuficiente…) devuelven HTTP 200 con valid: false — no son errores de transporte, léase siempre el campo result/valid, no solo el status code.

Rate limit: 60 peticiones/min por IP (clave verify en el limitador). CORS abierto (Access-Control-Allow-Origin: *) — antes de 2026-07-12 este POST estaba restringido a PUBLIC_BASE_URL, lo que bloqueaba llamadas desde el navegador de un integrador externo aunque la operación fuera de solo lectura y sin cookies; se alineó con el resto de la familia.
GET/api/verify-proof?proof_ref=<ref>

Verifica un evento certificado completo: recalcula el hash del payload y la firma HMAC, comprueba caducidad, y devuelve una vista pública (publicView) con banderas explícitas de qué se expone (safe_mode, physiological_raw_exposed…). Es el endpoint más completo de los cuatro — a diferencia de GET /api/verify, no solo confirma existencia en base de datos, sino que revalida la evidencia criptográfica en cada petición.

Parámetros
NombreTipoRequeridoDescripción
proof_refstringReferencia de prueba del evento certificado.
curl
curl "https://pulseids.com/api/verify-proof?proof_ref=<proof_ref>"
Response 200 (verification_status: valid)
{
  "ok": true,
  "proof_ref": "abc123",
  "session_id": "sess_...",
  "issuer": "amazfit_balance",
  "channel": "amazfit_balance_watchface",
  "verification_status": "valid",
  "state": "COHERENT",
  "hr_band": "60-70",
  "issued_at": "2026-07-10T14:32:00.000Z",
  "verified_at": "2026-07-12T10:00:00.000Z",
  "expires_at": "2026-08-10T14:32:00.000Z",
  "device_ref_public": "a1b2c3d4...ab12",
  "payload_hash_public": "9f8e7d6c...34ff",
  "signature_public": "3c2b1a09...ff01",
  "signature_alg": "HMAC-SHA256",
  "pdf_available": true,
  "pdf_url": "https://.../certificado.pdf",
  "verify_url": "https://pulseids.com/v/abc123",
  "safe_mode": true,
  "physiological_raw_exposed": false,
  "internal_metrics_exposed": false,
  "token_full_exposed": false,
  "error": null
}
Response 404 (no encontrado)
{
  "ok": false,
  "verification_status": "not_found",
  "error": "not_found",
  "pdf_available": false
}
Códigos de respuesta
HTTPerror / resultSignificado
400missing_proof_refFalta el parámetro proof_ref.
404not_foundNo existe ningún evento certificado con ese proof_ref.
500verification_lookup_failed / verification_failedFallo de Supabase o excepción no controlada.
verification_status también puede valer tampered (el hash del payload no coincide), invalid_signature o expired — en esos casos ok es false pero el HTTP sigue siendo 200, igual que en el POST de /api/verify. CORS abierto · sin autenticación · sin rate limit propio.
GET/api/ots-verify?seal=<certificateId>

Estado del anclaje Bitcoin (OpenTimestamps) de un certificado: si el hash de la sesión llegó a incluirse en un bloque de Bitcoin vía los calendar servers de OpenTimestamps, y desde cuándo. No revalida criptografía en cada llamada — lee el recibo (ots_receipt) ya persistido por seal-anchor.js / el reintento horario ots-anchor-retry.mjs.

Parámetros
NombreTipoRequeridoDescripción
sealstring (16 hex)certificate_id del sello (pulseid_beta_certificates).
curl
curl "https://pulseids.com/api/ots-verify?seal=<certificateId>"
Response 200 (anclado)
{
  "ok": true,
  "sealId": "a1b2c3d4e5f6a7b8",
  "issuedAt": "2026-07-10T14:32:00.000Z",
  "anchored": true,
  "otsAnchoredAt": "2026-07-10T15:10:00.000Z",
  "downloadUrl": "/api/ots-download?seal=a1b2c3d4e5f6a7b8"
}
Response 200 (pendiente de anclar)
{
  "ok": true,
  "sealId": "a1b2c3d4e5f6a7b8",
  "issuedAt": "2026-07-10T14:32:00.000Z",
  "anchored": false,
  "otsAnchoredAt": null,
  "downloadUrl": null
}
Códigos de respuesta
HTTPerror / resultSignificado
400invalid_seal_idseal no son 16 caracteres hexadecimales.
404seal_not_foundNo existe ningún certificado con ese certificate_id.
503service_unavailableSupabase no configurado en el despliegue.
500db_errorFallo consultando Supabase.
Si anchored: true, downloadUrl apunta a GET /api/ots-download?seal=…, que devuelve el fichero .ots crudo verificable de forma independiente en opentimestamps.org o con el cliente oficial ots — sin necesidad de confiar en PulseID. Página de verificación visual equivalente: /ots?seal=…. CORS abierto · sin autenticación · sin rate limit propio.
GET/api/zkbp-verify?cert=<certificateId>

Prueba de conocimiento cero (ZK-BP, Pedersen + Sigma sobre secp256k1) de que la coherencia fisiológica de una sesión superó el umbral exigido por su programa de cumplimiento, sin revelar el valor real. A diferencia de /api/ots-verify, aquí la verificación criptográfica completa se re-ejecuta en cada petición (barata, sin llamadas externas) en vez de fiarse de un flag persistido.

Parámetros
NombreTipoRequeridoDescripción
certstring (16 hex)certificate_id del sello (pulseid_beta_certificates).
curl
curl "https://pulseids.com/api/zkbp-verify?cert=<certificateId>"
Response 200 (prueba disponible y verificada)
{
  "ok": true,
  "certificateId": "a1b2c3d4e5f6a7b8",
  "issuedAt": "2026-07-10T14:32:00.000Z",
  "program": "personal",
  "threshold": 0.75,
  "proto": "zkbp-secp256k1-v1",
  "available": true,
  "verified": true,
  "commitment": "02ab3c...",
  "chainHash": "9f8e7d6c...",
  "proof": {
    "proto": "zkbp-secp256k1-v1",
    "commitment": "02ab3c...",
    "bitsD": [ "/* rango-proof, ver _shared/zkbp.js */" ],
    "bitsV": [ "/* rango-proof, ver _shared/zkbp.js */" ]
  }
}
Response 200 (certificado sin ZK-BP asociado)
{
  "ok": true,
  "certificateId": "a1b2c3d4e5f6a7b8",
  "issuedAt": "2026-07-10T14:32:00.000Z",
  "program": "personal",
  "available": false,
  "verified": false
}
Códigos de respuesta
HTTPerror / resultSignificado
400invalid_certificate_idcert no son 16 caracteres hexadecimales.
404certificate_not_foundNo existe ningún certificado con ese certificate_id.
503service_unavailableSupabase no configurado en el despliegue.
500db_errorFallo consultando Supabase.
commitment, chainHash y proof se sirven completos a propósito: no revelan el valor real (ese es el punto de una prueba de conocimiento cero), y permiten que un tercero reverifique por su cuenta sin confiar en este endpoint. Página de verificación visual equivalente: /zk?cert=…. CORS abierto · sin autenticación · sin rate limit propio.
GET/.well-known/pulseid-verify?ref=<identificador>

Punto de entrada único pensado para un crawler o un agente automatizado (incluidos LLMs con herramientas web) que aterriza en pulseids.com sin contexto previo y no puede — o no quiere — interpretar el HTML de esta misma página. Sigue el patrón .well-known/ (como security.txt o ai-plugin.json en otros sitios): llamado sin parámetros devuelve un manifiesto JSON plano y autodescriptivo de qué es PulseID y qué identificadores acepta; llamado con ?ref= detecta automáticamente el formato del identificador (no hace falta saber de antemano si es un certificate_id, un hash de sello o un proof_ref) y hace de proxy hacia el endpoint real correspondiente — reutilizando exactamente la misma lógica de verificación que /api/verify, /api/verify-proof y /api/zkbp-verify, sin duplicarla.

Parámetros
NombreTipoRequeridoDescripción
refstringnoIdentificador a verificar. Omitido → devuelve el manifiesto. certificate_id (16 hex), seal_hash (64 hex) o proof_ref (alfanumérico, sin puntos) → devuelve el resultado de verificación.
curl — manifiesto (sin parámetros)
curl "https://pulseids.com/.well-known/pulseid-verify"
Response 200 (manifiesto, forma abreviada)
{
  "name": "PulseID",
  "description": "PulseID emite identidad fisiológica certificable...",
  "identifier_types": [
    { "type": "certificate_id", "format": "16 hex", "real_endpoint": { "url": ".../api/zkbp-verify?cert=..." } },
    { "type": "seal_hash", "format": "64 hex", "real_endpoint": { "url": ".../api/verify?s=..." } },
    { "type": "proof_ref", "format": "alfanumérico, sin puntos", "real_endpoint": { "url": ".../api/verify-proof?proof_ref=..." } }
  ]
}
curl — auto-detección (cualquiera de los 3 formatos)
curl "https://pulseids.com/.well-known/pulseid-verify?ref=<identificador>"
Response 200 (ref = certificate_id, 16 hex)
{
  "identifier_type": "certificate_id",
  "ok": true,
  "verified": true,
  "certificate_id": "a1b2c3d4e5f6a7b8",
  "issued_at": "2026-07-10T14:32:00.000Z",
  "program": "personal",
  "human_readable_summary": "Verificado: el certificado a1b2c3d4e5f6a7b8 (programa personal) prueba mediante conocimiento cero que la coherencia fisiológica de la sesión superó el umbral requerido, sin revelar el valor real.",
  "source_endpoint": "https://pulseids.com/api/zkbp-verify?cert=a1b2c3d4e5f6a7b8"
}
Response 400 (formato no reconocido)
{
  "ok": false,
  "error": "unrecognized_identifier_format",
  "human_readable_summary": "No se reconoce el formato del identificador enviado en `ref`. Se aceptan 3 formatos...",
  "accepted_formats": [ "..." ]
}
El campo human_readable_summary está en español y pensado para que un LLM lo cite directamente sin tener que interpretar el resto de campos estructurados. Nota: certificate_id (ZK-BP) comparte formato (16 hex) con el seal de /api/ots-verify — el estado de anclaje en Bitcoin no está cubierto por la auto-detección de ?ref=, llama a /api/ots-verify directamente si buscas específicamente eso. CORS abierto · sin autenticación · sin rate limit propio (mismo criterio que sus hermanos de solo lectura).
SCRIPT/pulseid-badge.js

Script embebible sin dependencias que pinta un badge "✓ Verificado por PulseID" en cualquier web de terceros, consultando GET /api/verify-proof en vivo (sin iframe, sin backend propio del anfitrión). Pensado para que empresas certificadas muestren su estado de verificación fuera de PulseID — widget de confianza de la criba de impacto, distribución sin coste de adquisición.

Embeber (un badge)
<span data-pulseid-badge="<proof_ref>"></span>
<script src="https://pulseids.com/pulseid-badge.js" defer></script>
Varios badges en la misma página
<span data-pulseid-badge="<proof_ref_1>"></span>
<span data-pulseid-badge="<proof_ref_2>"></span>
<script src="https://pulseids.com/pulseid-badge.js" defer></script>
Parámetros
NombreTipoRequeridoDescripción
data-pulseid-badgestringproof_ref del evento certificado a verificar (mismo identificador que consume /api/verify-proof).
data-pulseid-theme"dark" | "light"noTema del chip. Por defecto "dark" — no depende del CSS del sitio anfitrión.
data-pulseid-size"sm" | "md"noTamaño del chip. Por defecto "md".
Tres estados visuales: cargando (punto pulsante), verificado (✓ verde, enlaza a verify_url) y no verificado (✕ rojo, con el motivo — caducado, firma inválida, no encontrado...). Construido con createElement/textContent, nuncainnerHTMLcon datos de red. Degrada a un chip "No se pudo verificar" en vez de lanzar excepciones si la petición falla. API programática: window.PulseIDBadge.mount(el, proofRef) para montar tras el load inicial (SPAs). Fuente y documentación completa (comentario de cabecera) en public/pulseid-badge.js.
SCRIPT/pulseid-badge-wc.js

Misma verificación en vivo contra GET /api/verify-proof, empaquetada como Custom Element (<pulseid-badge>) con Shadow DOM real en vez de escaneo de [data-pulseid-badge] sobre el DOM global. Pensado para integradores que construyen su propia web a base de componentes: el Shadow DOM aísla el CSS del badge del sitio anfitrión (y viceversa) y el elemento se monta/desmonta con el ciclo de vida estándar de Custom Elements, sin necesidad de re-escanear la página.

Embeber (Web Component)
<script src="https://pulseids.com/pulseid-badge-wc.js" defer></script>
<pulseid-badge proof-ref="<proof_ref>" theme="dark" size="md"></pulseid-badge>
Varios badges en la misma página
<pulseid-badge proof-ref="<proof_ref_1>"></pulseid-badge>
<pulseid-badge proof-ref="<proof_ref_2>"></pulseid-badge>
<script src="https://pulseids.com/pulseid-badge-wc.js" defer></script>
Parámetros
NombreTipoRequeridoDescripción
proof-refstringproof_ref del evento certificado a verificar (mismo identificador que consume /api/verify-proof).
theme"dark" | "light"noTema del chip. Por defecto "dark" — no depende del CSS del sitio anfitrión.
size"sm" | "md"noTamaño del chip. Por defecto "md".
Mismos tres estados visuales y mismo criterio de seguridad que pulseid-badge.js (nunca innerHTML con datos de red, siempre createElement/textContent, ahora escritos dentro del Shadow Root del elemento). Cada instancia de <pulseid-badge> tiene su propio Shadow DOM aislado, así que varios badges en la misma página no comparten estilos ni estado entre sí. Cambiar proof-ref en caliente (el.setAttribute('proof-ref', otroRef)) re-dispara la verificación automáticamente vía attributeChangedCallback; cambiar solo theme o size repinta con el último resultado ya obtenido sin volver a golpear la red. No sustituye a pulseid-badge.js — ambos scripts coexisten como dos formas de integración para casos de uso distintos (HTML plano vs. arquitectura basada en componentes). Fuente y documentación completa (comentario de cabecera) en public/pulseid-badge-wc.js.
Convenciones generales
Formato de respuesta

Los cuatro endpoints no comparten un envelope único — es un rasgo de que evolucionaron por separado, no un contrato de API versionado. GET /api/verify usa { valid, ... }; los otros tres usan { ok, ... }. Si integras contra varios, no asumas el mismo campo de éxito en todos — compruébalo endpoint por endpoint contra las tablas de esta página.

Autenticación y cuotas

Ninguno de los cuatro requiere API key hoy — son de lectura pública sobre identificadores anónimos. El único con rate limit propio es POST /api/verify (60/min por IP). Si vas a integrar tráfico de volumen (verificación en cada request de tu backend, por ejemplo), cachea por proof_ref/certificate_id en tu lado — estos identificadores son inmutables una vez emitidos.

Estabilidad

Estos son los contratos ya en producción, documentados tal cual — no hay todavía un endpoint unificado versionado (tipo /api/v1/verify) que envuelva a los cuatro. Si tu integración necesita ese nivel de estabilidad a largo plazo, contacta antes de depender de la forma exacta de la respuesta.

Patente P202531243 · pulseids.com