Guía de consumo API HearthingX
Documentación para desarrolladores e integradores que consumen la API biométrica sin la UI propia de HearthingX. Cubre autenticación B2B, flujo de voz en vivo, endpoints REST, WebSocket, Postman y el cliente de referencia Node.js.
DENY del motor PAD (detección de replay / no-vivo).
1. Autenticación B2B (API Key)
Obtener una API Key
Opción dashboard (recomendada): inicie sesión en /dashboard → sección API Keys — Integradores B2B. La clave completa solo se muestra una vez al crearla.
Opción CLI:
python scripts/create_api_key.py --name "Partner Sandbox" --scopes integrate,audit:read --env sandbox
Formato de la key: hx_sandbox_<32 caracteres> o hx_live_... en producción.
Enviar la key en cada petición
Use uno de estos métodos en el header HTTP (o en el handshake WebSocket):
Authorization: Bearer hx_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
X-API-Key: hx_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Scopes
| Scope | Permite |
|---|---|
integrate | Sesión, environment, dispositivo y biometría por WebSocket (stream) |
audit:read | /api/v1/audit/*, estado de dispositivos |
admin | Crear/revocar API Keys (/api/v1/admin/api-keys) |
El scope integrate implica acceso de auditoría. admin implica todos los scopes.
Cuándo se exige API Key
| Ruta | API Key |
|---|---|
/api/v1/* | Siempre |
/api/* (sesión, ambiente, dispositivo, métricas) | Si HEARTHINGX_REQUIRE_API_KEY=1 |
/api/ws/stream | Si HEARTHINGX_WS_REQUIRE_API_KEY=1 |
/health, /openapi.json | No |
2. Variables y configuración
Variables del servidor (operador)
| Variable | Default | Descripción |
|---|---|---|
HEARTHINGX_REQUIRE_API_KEY | 0 | 1 = exige API Key en todas las rutas /api/* |
HEARTHINGX_WS_REQUIRE_API_KEY | igual que global | 1 = exige API Key en WebSocket |
HEARTHINGX_CORS_ORIGINS | * | Orígenes permitidos para clientes web |
SYSTEM_MODE | BALANCED | Umbrales PAD: STRICT | BALANCED | COMPAT |
Variables Postman (colección)
| Variable | Ejemplo | Descripción |
|---|---|---|
base_url | http://localhost:8000 | URL base del servidor API |
api_key | hx_sandbox_... | API Key del integrador |
session_id | (auto) | Se rellena tras POST /api/session/init; va en environment, WS y auditoría |
challenge_id | (auto) | ID del reto de voz PAD-02 (se envía en el stream WebSocket) |
challenge_value | (auto) | Frase que el usuario debe decir |
challenge_t0_ms, challenge_t1_ms | (auto) | Timestamps de reacción humana del reto (el cliente WebSocket los gestiona automáticamente) |
device_signature | mic:test-device | Identificador para consulta de estado de dispositivo |
Variables cliente Node.js
| Variable / Flag | Default | Descripción |
|---|---|---|
HEARTHINGX_BASE_URL / --base-url | http://localhost:8000 | URL del API |
HEARTHINGX_API_KEY / --api-key | — | API Key obligatorio |
--device-id / HEARTHINGX_DEVICE_ID | -1 | Opcional — micrófono físico PortAudio. -1 = predeterminado del sistema (no hace falta pasarlo). Use list-devices solo si tiene varios micrófonos |
--hw-id | UUID aleatorio | ID biométrico estable; mismo valor en enroll y verify (no es el micrófono) |
list-devices | — | Comando: lista micrófonos de entrada (sin API Key) |
--duration | 10 | Segundos de captura de voz |
--countdown | 4 | Segundos de cuenta atrás antes de hablar |
--ambient-ms | 800 | Duración grabación ambiente S1 |
--mic-warmup-ms | 700 | Precalentamiento del micrófono |
--silence-lead-ms | 750 | Silencio prefonación antes de hablar (E_PREFON) |
3. Flujo de integración recomendado
Resumen visual (producción — WebSocket en vivo)
2. POST /api/environment?session_id=... → ok (WAV ambiente S1, sin voz)
3. WS /api/ws/stream?session_id=...
← ACK { status: "ready" }
→ ClientHello (JSON)
← ACK { status: "ok", server_nonce }
→ tramas binarias (seq 0, 1, 2… cada ~250 ms)
→ end_of_stream (JSON)
← final_result { decision: ALLOW|DENY }
4. [solo enrollment] POST /api/enroll/confirm { session_id }
5. GET /api/v1/audit/auth-logs?session_id=...
Enrolamiento vs verificación
| Aspecto | Enrollment | Verify |
|---|---|---|
flow en session/init | "enrollment" | "verify" |
| Prerequisito | Ninguno (primera vez) | Voz ya enrolada con el mismo hw_id |
| Paso extra tras ALLOW | POST /api/enroll/confirm | Ninguno |
| Cliente Node | node src/index.js enroll ... | node src/index.js verify ... |
3.1 ¿Qué es el challenge (PAD-02)?
En cada sesión biométrica el servidor genera un reto de voz aleatorio. El usuario debe decir la frase indicada en el micrófono. Esto reduce replay de audio grabado y ataques con WAV estático.
| Campo | Dónde aparece | Para qué sirve |
|---|---|---|
challenge_id | Respuesta de session/init o GET /api/challenge | Identificador único del reto; se envía en el stream WebSocket para que el servidor valide que usó el reto correcto (anti-replay) |
challenge.value | Respuesta de session/init | Frase que el usuario debe pronunciar (ej. Di: 7 4 2). El cliente Node la muestra en pantalla antes de la cuenta atrás |
challenge_t0_ms | Metadatos del reto | Timestamp en milisegundos cuando el usuario vio el reto en pantalla |
challenge_t1_ms | Metadatos del reto | Timestamp cuando el usuario empezó a hablar. El servidor valida reacción humana (~180–2000 ms entre t0 y t1) |
En WebSocket (flujo productivo y único de biometría): el cliente muestra la frase, el usuario habla en vivo y se envía challenge_id en ClientHello y end_of_stream. El cliente gestiona la temporización del reto automáticamente; no hace falta calcular t0/t1 manualmente. La verificación y el enrolamiento de voz se realizan exclusivamente por esta vía (Ruta A).
// Respuesta típica POST /api/session/init
{
"session_id": "a1b2c3d4-...",
"app_session_token": "...",
"expires_at": 1730000000,
"challenge": {
"challenge_id": "ch_abc123",
"challenge_type": "text",
"value": "Di: 7 4 2"
}
}
3.2 Consumo paso a paso (sin perderse)
Siga este orden. Cada paso indica método, headers, cuerpo y qué guardar para el siguiente.
Authorization: Bearer {{api_key}} o X-API-Key: {{api_key}}
Glosario rápido de variables
| Variable | Origen | Significado |
|---|---|---|
api_key | Dashboard / operador | Credencial B2B del integrador |
session_id | session/init | ID de la sesión biométrica actual; va en query de environment y WebSocket |
hw_id | Usted lo define | Identidad biométrica estable (mismo valor en enroll y verify). No es el micrófono físico |
device-id (Node) | list-devices | Opcional — índice del micrófono PortAudio. Si no lo pasa, usa el predeterminado del SO (-1) |
challenge_id | session/init | Enlaza la voz con el reto mostrado al usuario |
app_session_token | session/init | Token de sesión de app (logout); no sustituye a session_id en el flujo biométrico |
Ruta A — Flujo productivo (cliente Node + WebSocket)
Flujo biométrico completo recomendado para obtener ALLOW. El cliente Node ejecuta automáticamente los pasos 0–5; aquí se documenta cada petición con su cuerpo para que pueda replicarla en su propio SDK.
Paso 0 — Validar API Key
Headers
Authorization: Bearer {{api_key}}
X-API-Key: {{api_key}}
Cuerpo de solicitud
No aplica — petición GET sin body.
Respuesta esperada (200)
{
"status": "ok",
"name": "Partner Sandbox",
"scopes": ["integrate", "audit:read"],
"environment": "sandbox"
}
Paso 1 — Iniciar sesión biométrica
Headers
Content-Type: application/json
Authorization: Bearer {{api_key}}
Cuerpo de solicitud (JSON)
{
"captcha_token": "mock-token",
"flow": "verify",
"device_info": {
"hw_id": "mi-voz-001",
"model": "MiApp",
"os": "windows",
"app_version": "1.0.0"
}
}
Primera vez: use "flow": "enrollment". El campo device_info.hw_id es obligatorio y debe ser el mismo en enroll y verify.
Respuesta esperada (200)
{
"session_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"app_session_token": "token-url-safe...",
"expires_at": 1730003600,
"challenge": {
"challenge_id": "ch_abc123",
"challenge_type": "text",
"value": "Di: 7 4 2"
}
}
→ Guarde session_id, challenge.challenge_id y muestre challenge.value al usuario antes de hablar.
Paso 2 — Subir muestra ambiental (S1)
Headers
Authorization: Bearer {{api_key}}
Content-Type: multipart/form-data
Cuerpo de solicitud (multipart)
| Campo | Tipo | Descripción |
|---|---|---|
archivo | file (WAV) | Audio de ~300–800 ms sin voz humana (ruido ambiente). El cliente Node lo graba del micrófono antes del stream. |
Respuesta esperada (200)
{ "status": "ok", "message": "Ambiente registrado" }
Paso 3 — Stream de voz en vivo (WebSocket)
Headers (handshake)
Authorization: Bearer {{api_key}}
X-API-Key: {{api_key}}
Secuencia de mensajes
- Servidor → ACK inicial (sin body de solicitud del cliente):
{ "type": "ACK", "status": "ready", "session_id": "{{session_id}}" } - Cliente → ClientHello (JSON):
{ "type": "ClientHello", "device_info": { "hw_id": "mi-voz-001", "model": "MiApp", "os": "windows" }, "client_nonce": "550e8400-e29b-41d4-a716-446655440000", "geohash_init": "dr5reg000", "timestamp_start": 1730000000000, "gps_available": false, "audio_layout": "block", "client_version": "sdk-1.0.0", "challenge_id": "ch_abc123" } - Servidor → ACK handshake:
{ "type": "ACK", "status": "ok", "server_nonce": "base64-16-bytes", "server_recv_ms": 1730000000000 } - Cliente → Tramas binarias PCM (~250 ms cada una). Sin JSON — ver §17 Formato de trama.
- Cliente → Fin de stream (JSON):
{ "type": "end_of_stream", "timestamp_end": 1730000005000, "total_frames": 42, "geohash_final": "dr5reg000", "challenge_id": "ch_abc123" } - Servidor → Veredicto:
{ "type": "final_result", "decision": "ALLOW", "message": "Identidad verificada", "reason_code": null, "jwt_token": "eyJ..." }
→ Guarde decision y jwt_token si la integración lo requiere.
Paso 4 — Confirmar enrollment (solo enrollment + ALLOW)
Headers
Content-Type: application/json
Authorization: Bearer {{api_key}}
Cuerpo de solicitud (JSON)
{
"session_id": "{{session_id}}"
}
Respuesta esperada (200)
{ "status": "confirmed", "subject_id": "uuid-del-sujeto" }
En flujo verify este paso no aplica.
Paso 5 — Consultar auditoría
Headers
Authorization: Bearer {{api_key}}
Cuerpo de solicitud
No aplica — parámetros en la URL.
Respuesta esperada (200)
{
"items": [
{
"session_id": "{{session_id}}",
"decision": "ALLOW",
"reason_code": null,
"endpoint": "/api/ws/stream",
"success": true
}
],
"total": 1
}
--device-id es opcional. Comando mínimo:
node src/index.js verify --base-url ... --api-key ... --hw-id mi-voz-001
Ruta B — Integración REST con Postman
Valida API Key, sesión, ambiente, dispositivo, confirmación de enrolamiento y auditoría. No realiza biometría de voz: el enrolamiento y la verificación reales de voz viva se realizan por WebSocket en la Ruta A.
Paso 0 — Health check
Carpeta Postman: 00 Health
Cuerpo de solicitud
No aplica — sin autenticación.
Respuesta esperada (200)
{ "status": "ok" }
Headers
X-API-Key: {{api_key}}
Cuerpo de solicitud
No aplica.
Respuesta esperada (200)
{
"status": "ok",
"name": "Partner Sandbox",
"scopes": ["integrate"]
}
Paso 1 — Iniciar sesión
Carpeta Postman: 01 Sesión → POST /api/session/init (verify) o (enrollment)
Headers
Content-Type: application/json
Authorization: Bearer {{api_key}}
Cuerpo de solicitud (JSON) — verify
{
"captcha_token": "mock-token",
"flow": "verify",
"device_info": {
"hw_id": "postman-demo-hw-001",
"model": "Postman",
"os": "integration-test"
}
}
Cuerpo de solicitud (JSON) — enrollment
{
"captcha_token": "mock-token",
"flow": "enrollment",
"device_info": {
"hw_id": "postman-demo-hw-001",
"model": "Postman",
"os": "integration-test"
}
}
Respuesta esperada (200)
{
"session_id": "...",
"challenge": {
"challenge_id": "...",
"value": "Di: 3 8 1"
}
}
→ La colección guarda automáticamente session_id, challenge_id, challenge_value, challenge_t0_ms y challenge_t1_ms.
Paso 2 — Subir ambiente (S1)
Carpeta Postman: 02 Ambiente → POST /api/environment
Headers
Authorization: Bearer {{api_key}}
Content-Type: multipart/form-data
Cuerpo de solicitud (multipart)
| Campo | Tipo | Valor |
|---|---|---|
archivo | file | Seleccione un WAV corto (~300–800 ms) sin voz humana |
Respuesta esperada (200)
{ "status": "ok" }
Paso 3 — Registro de dispositivo (opcional)
Carpeta Postman: 03 Dispositivo
Cuerpo de solicitud (JSON)
{
"deviceId": "postman-mic-01",
"label": "Postman integration"
}
Cuerpo de solicitud
No aplica — consulta de estado por URL.
Paso 4 — Confirmar enrollment (cierre del enrolamiento por WebSocket)
Persiste el enrolamiento capturado en vivo por WebSocket (Ruta A). Se invoca tras completar la captura de voz del stream de enrolamiento; no procesa audio por sí mismo.
Carpeta Postman: 01 Sesión → POST /api/enroll/confirm
Cuerpo de solicitud (JSON)
{
"session_id": "{{session_id}}"
}
Paso 6 — Auditoría y telemetría
Carpetas Postman: 04 Auditoría, 07 Telemetría
Cuerpo de solicitud
No aplica.
Cuerpo de solicitud (JSON) — telemetría de errores del integrador
{
"error_code": "WS_HANDSHAKE_FAILED",
"error_phase": "streaming",
"message": "Ejemplo integrador Postman",
"session_id": "{{session_id}}",
"hw_id": "postman-demo-hw-001",
"device_model": "Postman",
"os": "integration-test",
"app_version": "postman-1.0"
}
4. Guía Postman
Importar la colección
- Descargue la colección con el botón siguiente.
- En Postman → Import → Upload Files → seleccione el archivo JSON descargado.
- Abra la colección → pestaña Variables → configure
base_urlyapi_key→ Save.
Variables de colección (detalle)
| Variable | Cuándo se rellena | Para qué sirve |
|---|---|---|
base_url, api_key | Manual (obligatorias) | URL del API y credencial B2B |
session_id | Tras 01 Sesión → session/init | Query en environment, enroll/confirm y filtros de auditoría |
challenge_id | Tras session/init o GET /api/challenge | Enlaza el audio con el reto PAD-02 |
challenge_value | Tras session/init | Frase que debe decir el usuario (referencia humana) |
challenge_t0_ms, challenge_t1_ms | Auto en scripts de la colección | Timestamps de reacción humana del reto PAD-02 (los gestiona el cliente WebSocket en el flujo real) |
device_signature | Manual o tras register | Consulta GET /api/v1/devices/{device_signature}/status |
Orden de ejecución en Postman
Recorra las carpetas en este orden la primera vez. Los pasos detallados con headers, cuerpos y respuestas están en la Ruta B (§3.2). Para biometría productiva use la Ruta A.
| # | Carpeta | Request | Qué obtiene / hace |
|---|---|---|---|
| 0 | 00 Health | GET /health, GET /api/v1/health | Servidor activo y API Key válida |
| 1 | 01 Sesión | POST /api/session/init | session_id + challenge; variables auto |
| 2 | 02 Ambiente | POST /api/environment | Sube WAV ambiente S1 (sin voz) |
| 3 | 03 Dispositivo | register, status | Opcional — registro lógico de micrófono |
| 4 | 01 Sesión | POST /api/enroll/confirm | Cierra el enrolamiento capturado por WebSocket (Ruta A) |
| 5 | 04 Auditoría | auth-logs, devices, subjects… | ALLOW/DENY y trazabilidad |
| — | 05 Métricas | latency, /health/pad | Observabilidad del operador |
| — | 07 Telemetría | POST /api/client-errors | Errores del cliente integrador |
OpenAPI: en Postman puede importar como enlace {{base_url}}/openapi.json (sustituya {{base_url}} por la URL de su entorno). La UI Swagger en /docs puede verse en blanco por políticas de seguridad; use esta guía o la colección descargable.
5. Cliente de referencia Node.js
Cliente de demostración para audio en vivo por WebSocket: captura de micrófono real, prefonación, cuenta atrás y envío de tramas forenses. Incluye flujos enroll y verify.
npm install en la carpeta del cliente.
Requisitos
- Node.js 18+
- Micrófono
- PortAudio en el sistema (para
naudiodon) - API Key sandbox y API en marcha
Instalación tras descargar
cd hearthingx-node-live-stream
npm install
naudiodon está en optionalDependencies: npm install ya lo intenta instalar.
Si el micrófono falla, compruebe con npm ls naudiodon o ejecute npm install naudiodon para ver el error de compilación (p. ej. falta PortAudio en Windows).
Selección de micrófono (--device-id opcional)
deviceId = -1). Solo necesita --device-id o HEARTHINGX_DEVICE_ID si tiene varios micrófonos y quiere elegir uno distinto (USB, Bluetooth, etc.).
Para listar dispositivos disponibles (comando local, no requiere API Key):
node src\index.js list-devices
Ejemplo con micrófono concreto (opcional):
node src\index.js verify ^
--base-url http://localhost:8000 ^
--api-key hx_sandbox_SU_KEY ^
--hw-id mi-voz-001 ^
--device-id 2
--hw-id = identidad biométrica en HearthingX (obligatorio y estable entre enroll/verify).
--device-id = hardware de captura PortAudio (opcional; default predeterminado del SO).
En Windows también puede fijar el predeterminado en Configuración → Sonido → Entrada y omitir --device-id.
Parámetros del cliente (referencia)
| Parámetro | Obligatorio | Default | Descripción |
|---|---|---|---|
--api-key | Sí | — | API Key del integrador |
--base-url | No | http://localhost:8000 | URL del API |
--hw-id | Recomendado | UUID aleatorio | ID biométrico; mismo en enroll y verify |
--device-id | No | -1 (SO) | Micrófono PortAudio; omitir = predeterminado |
--duration | No | 10 | Segundos de captura de voz |
--countdown | No | 4 | Cuenta atrás antes de hablar |
Ejecución paso a paso
npm installen la carpeta del cliente descargado.- Opcional:
node src\index.js list-devicessi necesita un micrófono distinto al predeterminado. - Primera vez — enrollment:
node src\index.js enroll --base-url ... --api-key ... --hw-id mi-voz-001 - Siguientes veces — verify con el mismo
--hw-id. - El cliente valida health → session/init → muestra challenge → ambiente → WebSocket → auditoría.
# 1. Enrolar (primera vez) — sin --device-id (micrófono predeterminado)
node src\index.js enroll ^
--base-url http://localhost:8000 ^
--api-key hx_sandbox_SU_KEY ^
--hw-id mi-voz-001 ^
--duration 10 --countdown 5
# 2. Verificar (mismo hw-id)
node src\index.js verify ^
--base-url http://localhost:8000 ^
--api-key hx_sandbox_SU_KEY ^
--hw-id mi-voz-001 ^
--duration 10 --countdown 5
6. Endpoints — Health (públicos)
Auth: ninguna
Query: detail=1 opcional — añade active_sessions, pending_enrollments
Respuesta 200:
{ "status": "ok" }
Auth: ninguna — estado del motor PAD (RawNet, latencia, umbral)
{
"status": "ok",
"pad_model_loaded": true,
"engine_status": "ready",
"last_inference_latency_ms": 42.5
}
7. Endpoints — Sesión
Auth: API Key si HEARTHINGX_REQUIRE_API_KEY=1
Content-Type: application/json
Cuerpo (JSON)
| Campo | Tipo | Req. | Descripción |
|---|---|---|---|
device_info | object | Sí | Debe incluir hw_id (string no vacío) |
device_info.hw_id | string | Sí | Identificador estable del dispositivo/usuario |
device_info.model | string | No | Modelo del dispositivo cliente |
device_info.os | string | No | Sistema operativo |
captcha_token | string | Sí | Token reCAPTCHA (sandbox: mock-token) |
flow | string | No | "enrollment" (default) o "verify" |
Ejemplo request:
{
"captcha_token": "mock-token",
"flow": "verify",
"device_info": {
"hw_id": "partner-hw-001",
"model": "Postman",
"os": "integration-test"
}
}
Respuesta 200:
{
"session_id": "a24d601c-e4bc-45f2-8e84-311f0a56c231",
"app_session_token": "urlsafe-token...",
"expires_at": 1730000000,
"challenge": {
"challenge_id": "uuid",
"challenge_type": "phoneme",
"value": "Mi voz es mi firma de identidad"
}
}
Errores: 400 MISSING_HW_ID · 403 reCAPTCHA rechazado
Invalida app_session_token. Body: { "app_session_token": "..." } o header Authorization: Bearer <token>.
8. Endpoints — Ambiente (S1)
Content-Type: multipart/form-data
Debe ejecutarse antes de enviar tramas binarias por WebSocket.
| Campo | Tipo | Req. | Descripción |
|---|---|---|---|
archivo | file (WAV) | Sí | ~300–800 ms de ruido ambiente sin voz humana |
session_id | query | Sí | UUID de la sesión activa |
lat, lon | form float | No | Coordenadas GPS opcionales |
Respuesta 200: { "ok": true, "samples": 12800, "sample_rate": 16000 }
Errores: S1_SILENT · S1_CONTAINS_VOICE · buffer demasiado corto
9. Endpoints — Challenge
Reto cognitivo de voz (frase que el usuario debe decir). TTL: 5 minutos.
{
"challenge_id": "uuid",
"challenge_type": "phoneme",
"value": "Di: uno dos tres"
}
El cliente WebSocket asocia el challenge_id a la captura de voz y prueba la reacción humana dentro de la ventana cognitiva (~180–2000 ms). No requiere gestión manual de timestamps.
10. Endpoint — Confirmación de enrolamiento
Solo flujo enrollment por WebSocket. Tras final_result con ALLOW:
{ "session_id": "uuid-de-la-sesion" }
Respuesta: { "success": true, "subject_id": "...", "message": "..." }
Errores: 404 sin enrollment pendiente · 409 DEVICE_ALREADY_ENROLLED · 410 expirado (TTL 5 min)
11. Endpoints — Dispositivo
{ "deviceId": "mic-partner-01", "label": "Micrófono integración" }
Respuesta: { "registered": true, "device_id": 42 } — firma almacenada como mic:{deviceId}
Scope: audit:read
Respuesta: { "status": "enrolled"|"not_enrolled"|"error", "is_active": true }
Telemetría de errores del cliente integrador.
| Campo | Tipo | Req. | Descripción |
|---|---|---|---|
error_code | string | Sí | Ej. WS_HANDSHAKE_FAILED, SEQUENCE_GAP |
error_phase | string | No | preflight, streaming, biometric_engine, … |
session_id | string | No | Correlación con la sesión |
hw_id | string | No | Hardware ID del cliente |
extra | object | No | Datos adicionales |
12. Endpoints — Métricas
Query: endpoint = enroll | verify | procesar_audio · limit int
13. Partner API v1 — Health
Auth: API Key obligatorio — primer paso de certificación del integrador.
{
"success": true,
"client_id": "0316453b-32d7-469f-9f8c-be0f5777a87e",
"name": "Partner Sandbox",
"environment": "sandbox",
"scopes": ["audit:read", "integrate"]
}
14. Partner API v1 — Auditoría
Todos requieren scope audit:read (o integrate / admin).
| Método | Ruta | Query params | Descripción |
|---|---|---|---|
| GET | /api/v1/audit/auth-logs |
limit (1–500), offset, decision, success (0|1), session_id, failure_code |
Intentos de autenticación ALLOW/DENY con telemetría LSCE |
| GET | /api/v1/audit/devices |
limit, offset |
Dispositivos registrados |
| GET | /api/v1/audit/subjects |
limit, offset |
Sujetos biométricos TRL9 |
| GET | /api/v1/audit/session-chain |
limit, offset |
Cadena LSCE (micro-bloques) |
| GET | /api/v1/audit/identities |
limit, offset |
Vista unificada sujeto + dispositivo + geo |
| GET | /api/v1/audit/ga-logs |
limit (1–500) |
Logs GA-AUD-TPL-v1 + P95 agregado |
Campos relevantes en auth-logs
| Campo | Descripción |
|---|---|
decision | ALLOW, DENY, OPEN, ERROR |
session_id | UUID de la sesión biométrica |
reason_code / failure_code | Código máquina del resultado |
latency_ms | Latencia del intento |
client_clock_skew_ms | Desfase de reloj cliente vs servidor |
temporal_coherence_fail | 1 si falló coherencia temporal (TCL) |
sequence_fail | 1 si hubo gap en secuencia de tramas |
15. Partner API v1 — Administración de keys
Scope admin requerido. Uso interno / operadores.
| Método | Ruta | Body / Params | Descripción |
|---|---|---|---|
| POST | /api/v1/admin/api-keys |
name, scopes[], environment |
Crea key; devuelve api_key plano una sola vez |
| GET | /api/v1/admin/api-keys |
— | Lista keys (sin valor plano) |
| DELETE | /api/v1/admin/api-keys/{client_id} |
— | Revoca una key |
16. WebSocket — Protocolo de streaming
URL: ws://host:8000/api/ws/stream?session_id=... (o wss:// en producción)
Auth: API Key en headers del upgrade si está habilitado
Tipos de mensaje: JSON texto (handshake, control) + tramas binarias (audio forense)
Mensajes JSON del protocolo
Servidor → Cliente: ACK (conexión)
{ "type": "ACK", "status": "ready", "session_id": "uuid" }
Cliente → Servidor: ClientHello
| Campo | Tipo | Req. | Descripción |
|---|---|---|---|
type | string | Sí | "ClientHello" |
device_info.hw_id | string | Sí | Debe coincidir con session/init |
client_nonce | string | Sí | UUID único por intento (anti-replay) |
timestamp_start | int | No | Epoch ms del inicio del stream |
audio_layout | string | No | "block" | "interleaved" | "auto" |
geohash_init | string | No | Geohash inicial (9 chars) |
gps_available | bool | No | Si hay GPS |
client_version | string | No | Versión del SDK cliente |
challenge_id | string | No | Del challenge de la sesión |
{
"type": "ClientHello",
"device_info": { "hw_id": "partner-hw-001", "model": "MyApp", "os": "android" },
"client_nonce": "550e8400-e29b-41d4-a716-446655440000",
"geohash_init": "dr5reg000",
"timestamp_start": 1730000000000,
"gps_available": false,
"audio_layout": "block",
"client_version": "sdk-1.0.0",
"challenge_id": "uuid-del-challenge"
}
Servidor → Cliente: ACK (handshake OK)
{
"type": "ACK",
"status": "ok",
"server_nonce": "base64-16-bytes",
"server_recv_ms": 1730000000000,
"server_send_ms": 1730000000001
}
Cliente → Servidor: end_of_stream
{
"type": "end_of_stream",
"timestamp_end": 1730000005000,
"total_frames": 42,
"geohash_final": "dr5reg000",
"challenge_id": "uuid"
}
Servidor → Cliente: final_result (veredicto contractual)
{
"type": "final_result",
"contractual_verdict": true,
"decision": "ALLOW",
"decision_layer": "sovereign",
"result": "PASS",
"message": "Identidad verificada",
"reason_code": null,
"jwt_token": "eyJ...",
"subject_id": "uuid",
"pad_status": "LIVE",
"chain_hash": "...",
"jitter_class": "LOW"
}
Otros mensajes WS
| type | Dirección | Descripción |
|---|---|---|
audio_validation | S→C | Feedback no contractual (status: scanning) |
hot_pad_alert | S→C | Alerta de seguridad PAD (security_halt: true) |
error | S→C | Error no terminal o de cierre |
ping / pong | C↔S | Keepalive |
abort_session | C→S | Abortar sin finalizar |
17. Formato de trama binaria (audio forense)
Cada trama se envía como mensaje binario por WebSocket, típicamente cada 250 ms.
| Offset | Tamaño | Endian | Campo |
|---|---|---|---|
| 0 | 4 bytes | BE | sequence — uint32, empieza en 0, monotónico |
| 4 | 8 bytes | BE | frame_ts_ms — uint64, epoch ms del cliente |
| 12 | 9 bytes | ASCII | geohash — relleno con null |
| 21 | N bytes | LE | PCM int16 — dual branch A/B |
Layout PCM (audio_layout)
| Valor | Estructura |
|---|---|
block | Todas las muestras de rama A, luego todas las de rama B (recomendado cliente Node) |
interleaved | A₀, B₀, A₁, B₁, … |
auto | El servidor elige según correlación |
Tamaño esperado por trama: 16 000 bytes PCM (8 000 muestras × 2 ramas × 2 bytes) a 16 kHz. Con un solo micrófono, duplique la misma señal en A y B.
// Pseudocódigo construcción de trama
buffer.writeUInt32BE(sequence, 0);
buffer.writeBigUInt64BE(BigInt(timestamp_ms), 4);
buffer.write(geohash.padEnd(9, '\0'), 12, 'ascii');
buffer.write(pcm_int16_le_dual_block, 21);
18. Códigos de error frecuentes
Autenticación HTTP
| Código | HTTP | Significado |
|---|---|---|
API_KEY_REQUIRED | 401 | Falta header Bearer o X-API-Key |
API_KEY_INVALID | 401 | Key incorrecta o revocada |
SCOPE_DENIED | 403 | Scope insuficiente |
Sesión y ambiente
| Código | HTTP | Significado |
|---|---|---|
MISSING_HW_ID | 400 | Falta device_info.hw_id |
S1_REQUIRED | WS | Falta POST /api/environment antes de tramas binarias |
S1_CONTAINS_VOICE | 400 | Voz humana detectada en buffer ambiente |
S1_SILENT | 400 | Silencio digital (ceros) en ambiente |
WebSocket y streaming
| Código | Significado |
|---|---|
NONCE_REUSED | Replay detectado — client_nonce ya usado |
CLOCK_SKEW_REJECTED | Reloj cliente >5 min desfasado del servidor |
SEQUENCE_GAP | Secuencia de tramas no monotónica o con huecos |
TCL_CHAIN_FAIL | Fallo de coherencia temporal (cadena TCL) |
E_PREFON | Voz detectada en prefonación o silencio sintético |
PAD_DENY | Motor PAD rechazó el audio (no-vivo / replay) |
IDENTITY_NOT_RECOGNIZED | Verify: voz no coincide con plantilla enrolada |
Enrollment
| Código | HTTP | Significado |
|---|---|---|
DEVICE_ALREADY_ENROLLED | 409 | El dispositivo ya tiene identidad |
IDENTITY_ALREADY_ENROLLED | 409 | La voz ya está registrada |
19. Checklist de certificación integrador
- API Key sandbox recibida y probada en
GET /api/v1/health POST /api/session/initconhw_idestablePOST /api/environmentsin voz humana (~300–800 ms)- WebSocket: ACK
ready→ ClientHello → ACKok - Tramas binarias con secuencia 0,1,2… y intervalo ~250 ms
- Prefonación: silencio real del micrófono (no ceros sintéticos)
end_of_streamenviado al terminar capturafinal_resultrecibido condecision- [Enrollment]
POST /api/enroll/confirmtras ALLOW GET /api/v1/audit/auth-logs?session_id=...muestra el intento- Entendido: WAV estático REST ≠ producto de voz viva en producción
20. Seguridad y buenas prácticas
- No incluya API Keys en repositorios, URLs ni logs de cliente.
- Use
wss://y TLS en producción. - Rote keys periódicamente con
DELETE /api/v1/admin/api-keys/{client_id}. - Configure
HEARTHINGX_REQUIRE_API_KEY=1en entornos expuestos. - El pipeline PAD, Punto Cero e identidad no se desactiva por tener API Key.
- Mantenga
hw_idestable por usuario/dispositivo; no genere uno nuevo en cada verify.
Colección Postman Cliente Node.js
Esquema OpenAPI de su entorno: {{base_url}}/openapi.json
HearthingX API — Documentación de consumo para integradores B2B. Generado para partners sandbox y producción.