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.

API Key B2B Stream en vivo (PAD) Partner API v1
Producto de máxima seguridad: el flujo recomendado es streaming en vivo por WebSocket con micrófono real. Subir un WAV estático por REST puede recibir 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

ScopePermite
integrateSesión, environment, dispositivo y biometría por WebSocket (stream)
audit:read/api/v1/audit/*, estado de dispositivos
adminCrear/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

RutaAPI Key
/api/v1/*Siempre
/api/* (sesión, ambiente, dispositivo, métricas)Si HEARTHINGX_REQUIRE_API_KEY=1
/api/ws/streamSi HEARTHINGX_WS_REQUIRE_API_KEY=1
/health, /openapi.jsonNo

2. Variables y configuración

Variables del servidor (operador)

VariableDefaultDescripción
HEARTHINGX_REQUIRE_API_KEY01 = exige API Key en todas las rutas /api/*
HEARTHINGX_WS_REQUIRE_API_KEYigual que global1 = exige API Key en WebSocket
HEARTHINGX_CORS_ORIGINS*Orígenes permitidos para clientes web
SYSTEM_MODEBALANCEDUmbrales PAD: STRICT | BALANCED | COMPAT

Variables Postman (colección)

VariableEjemploDescripción
base_urlhttp://localhost:8000URL base del servidor API
api_keyhx_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_signaturemic:test-deviceIdentificador para consulta de estado de dispositivo

Variables cliente Node.js

Variable / FlagDefaultDescripción
HEARTHINGX_BASE_URL / --base-urlhttp://localhost:8000URL del API
HEARTHINGX_API_KEY / --api-keyAPI Key obligatorio
--device-id / HEARTHINGX_DEVICE_ID-1Opcional — micrófono físico PortAudio. -1 = predeterminado del sistema (no hace falta pasarlo). Use list-devices solo si tiene varios micrófonos
--hw-idUUID aleatorioID biométrico estable; mismo valor en enroll y verify (no es el micrófono)
list-devicesComando: lista micrófonos de entrada (sin API Key)
--duration10Segundos de captura de voz
--countdown4Segundos de cuenta atrás antes de hablar
--ambient-ms800Duración grabación ambiente S1
--mic-warmup-ms700Precalentamiento del micrófono
--silence-lead-ms750Silencio prefonación antes de hablar (E_PREFON)

3. Flujo de integración recomendado

Resumen visual (producción — WebSocket en vivo)

1. POST /api/session/init session_id, challenge, app_session_token
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

AspectoEnrollmentVerify
flow en session/init"enrollment""verify"
PrerequisitoNinguno (primera vez)Voz ya enrolada con el mismo hw_id
Paso extra tras ALLOWPOST /api/enroll/confirmNinguno
Cliente Nodenode 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.

CampoDónde aparecePara qué sirve
challenge_idRespuesta de session/init o GET /api/challengeIdentificador único del reto; se envía en el stream WebSocket para que el servidor valide que usó el reto correcto (anti-replay)
challenge.valueRespuesta de session/initFrase 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_msMetadatos del retoTimestamp en milisegundos cuando el usuario vio el reto en pantalla
challenge_t1_msMetadatos del retoTimestamp 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.

Headers en todas las peticiones REST autenticadas:
Authorization: Bearer {{api_key}}  o  X-API-Key: {{api_key}}

Glosario rápido de variables

VariableOrigenSignificado
api_keyDashboard / operadorCredencial B2B del integrador
session_idsession/initID de la sesión biométrica actual; va en query de environment y WebSocket
hw_idUsted lo defineIdentidad biométrica estable (mismo valor en enroll y verify). No es el micrófono físico
device-id (Node)list-devicesOpcional — índice del micrófono PortAudio. Si no lo pasa, usa el predeterminado del SO (-1)
challenge_idsession/initEnlaza la voz con el reto mostrado al usuario
app_session_tokensession/initToken de sesión de app (logout); no sustituye a session_id en el flujo biométrico

Ruta A — Flujo productivo (cliente Node + WebSocket)

Ruta A
Producción — voz en vivo con micrófono real

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.

Herramienta: cliente Node descargable (§5) Protocolo clave: WebSocket + tramas PCM Micrófono: --device-id opcional (default SO)

Paso 0 — Validar API Key

GET /api/v1/health

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

POST /api/session/init

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)

POST /api/environment?session_id={{session_id}}

Headers

Authorization: Bearer {{api_key}}
Content-Type: multipart/form-data

Cuerpo de solicitud (multipart)

CampoTipoDescripción
archivofile (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)

WS /api/ws/stream?session_id={{session_id}}

Headers (handshake)

Authorization: Bearer {{api_key}}
X-API-Key: {{api_key}}

Secuencia de mensajes

  1. Servidor → ACK inicial (sin body de solicitud del cliente):
    { "type": "ACK", "status": "ready", "session_id": "{{session_id}}" }
  2. 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"
    }
  3. Servidor → ACK handshake:
    {
      "type": "ACK",
      "status": "ok",
      "server_nonce": "base64-16-bytes",
      "server_recv_ms": 1730000000000
    }
  4. Cliente → Tramas binarias PCM (~250 ms cada una). Sin JSON — ver §17 Formato de trama.
  5. Cliente → Fin de stream (JSON):
    {
      "type": "end_of_stream",
      "timestamp_end": 1730000005000,
      "total_frames": 42,
      "geohash_final": "dr5reg000",
      "challenge_id": "ch_abc123"
    }
  6. 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)

POST /api/enroll/confirm

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

GET /api/v1/audit/auth-logs?session_id={{session_id}}&limit=20

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
}
Ejecución con el cliente Node: --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

Ruta B
Pruebas de integración REST y auditoría (no biométricas)

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.

Herramienta: colección Postman (§4) Variables obligatorias: base_url, api_key Variables auto: session_id, challenge_*

Paso 0 — Health check

Carpeta Postman: 00 Health

GET /health

Cuerpo de solicitud

No aplica — sin autenticación.

Respuesta esperada (200)

{ "status": "ok" }
GET /api/v1/health

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)

POST /api/session/init

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

POST /api/environment?session_id={{session_id}}

Headers

Authorization: Bearer {{api_key}}
Content-Type: multipart/form-data

Cuerpo de solicitud (multipart)

CampoTipoValor
archivofileSeleccione un WAV corto (~300–800 ms) sin voz humana

Respuesta esperada (200)

{ "status": "ok" }

Paso 3 — Registro de dispositivo (opcional)

Carpeta Postman: 03 Dispositivo

POST /api/device/register

Cuerpo de solicitud (JSON)

{
  "deviceId": "postman-mic-01",
  "label": "Postman integration"
}
GET /api/v1/devices/{{device_signature}}/status

Cuerpo de solicitud

No aplica — consulta de estado por URL.

Biometría de voz: la Ruta B no ejecuta biometría. El enrolamiento y la verificación reales de voz viva se realizan exclusivamente por WebSocket (tramas PCM en tiempo real) en la Ruta A. Postman se usa para la integración no biométrica: sesión, ambiente, dispositivo, confirmación de enrolamiento y auditoría.

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

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

GET /api/v1/audit/auth-logs?limit=20&session_id={{session_id}}

Cuerpo de solicitud

No aplica.

POST /api/client-errors

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"
}
Limitación: Postman no puede enviar stream de micrófono por WebSocket. Para biometría productiva use la Ruta A.

4. Guía Postman

Importar la colección

  1. Descargue la colección con el botón siguiente.
  2. En Postman → ImportUpload Files → seleccione el archivo JSON descargado.
  3. Abra la colección → pestaña Variables → configure base_url y api_keySave.
⬇ Descargar colección Postman

Variables de colección (detalle)

VariableCuándo se rellenaPara qué sirve
base_url, api_keyManual (obligatorias)URL del API y credencial B2B
session_idTras 01 Sesión → session/initQuery en environment, enroll/confirm y filtros de auditoría
challenge_idTras session/init o GET /api/challengeEnlaza el audio con el reto PAD-02
challenge_valueTras session/initFrase que debe decir el usuario (referencia humana)
challenge_t0_ms, challenge_t1_msAuto en scripts de la colecciónTimestamps de reacción humana del reto PAD-02 (los gestiona el cliente WebSocket en el flujo real)
device_signatureManual o tras registerConsulta 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.

#CarpetaRequestQué obtiene / hace
000 HealthGET /health, GET /api/v1/healthServidor activo y API Key válida
101 SesiónPOST /api/session/initsession_id + challenge; variables auto
202 AmbientePOST /api/environmentSube WAV ambiente S1 (sin voz)
303 Dispositivoregister, statusOpcional — registro lógico de micrófono
401 SesiónPOST /api/enroll/confirmCierra el enrolamiento capturado por WebSocket (Ruta A)
504 Auditoríaauth-logs, devices, subjects…ALLOW/DENY y trazabilidad
05 Métricaslatency, /health/padObservabilidad del operador
07 TelemetríaPOST /api/client-errorsErrores del cliente integrador
Limitación Postman: no reproduce el stream de micrófono en vivo por WebSocket. Use el cliente Node.js para el flujo completo biométrico. Postman es ideal para REST, auditoría y pruebas de integración parcial.

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.

Descargar el cliente de referencia — proyecto Node.js listo para audio en vivo. Tras descomprimir, ejecute npm install en la carpeta del cliente.
⬇ Descargar cliente Node.js (ZIP)

Requisitos

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)

No es obligatorio pasar el micrófono. Por defecto el cliente usa el micrófono de entrada predeterminado del sistema (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ámetroObligatorioDefaultDescripción
--api-keyAPI Key del integrador
--base-urlNohttp://localhost:8000URL del API
--hw-idRecomendadoUUID aleatorioID biométrico; mismo en enroll y verify
--device-idNo-1 (SO)Micrófono PortAudio; omitir = predeterminado
--durationNo10Segundos de captura de voz
--countdownNo4Cuenta atrás antes de hablar

Ejecución paso a paso

  1. npm install en la carpeta del cliente descargado.
  2. Opcional: node src\index.js list-devices si necesita un micrófono distinto al predeterminado.
  3. Primera vez — enrollment: node src\index.js enroll --base-url ... --api-key ... --hw-id mi-voz-001
  4. Siguientes veces — verify con el mismo --hw-id.
  5. 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)

GET /health

Auth: ninguna

Query: detail=1 opcional — añade active_sessions, pending_enrollments

Respuesta 200:

{ "status": "ok" }
GET /health/pad

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

POST /api/session/init

Auth: API Key si HEARTHINGX_REQUIRE_API_KEY=1

Content-Type: application/json

Cuerpo (JSON)

CampoTipoReq.Descripción
device_infoobjectDebe incluir hw_id (string no vacío)
device_info.hw_idstringIdentificador estable del dispositivo/usuario
device_info.modelstringNoModelo del dispositivo cliente
device_info.osstringNoSistema operativo
captcha_tokenstringToken reCAPTCHA (sandbox: mock-token)
flowstringNo"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

POST /api/session/logout

Invalida app_session_token. Body: { "app_session_token": "..." } o header Authorization: Bearer <token>.

8. Endpoints — Ambiente (S1)

POST /api/environment?session_id={uuid}

Content-Type: multipart/form-data

Debe ejecutarse antes de enviar tramas binarias por WebSocket.

CampoTipoReq.Descripción
archivofile (WAV)~300–800 ms de ruido ambiente sin voz humana
session_idqueryUUID de la sesión activa
lat, lonform floatNoCoordenadas GPS opcionales

Respuesta 200: { "ok": true, "samples": 12800, "sample_rate": 16000 }

Errores: S1_SILENT · S1_CONTAINS_VOICE · buffer demasiado corto

9. Endpoints — Challenge

GET /api/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

La biometría de voz (enrolamiento y verificación) se realiza exclusivamente por WebSocket (Protocolo WS). Este endpoint solo persiste el enrolamiento ya capturado en vivo por el stream; no procesa audio ni concede acceso por sí mismo.
POST /api/enroll/confirm

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

POST /api/device/register
{ "deviceId": "mic-partner-01", "label": "Micrófono integración" }

Respuesta: { "registered": true, "device_id": 42 } — firma almacenada como mic:{deviceId}

GET /api/v1/devices/{device_signature}/status

Scope: audit:read

Respuesta: { "status": "enrolled"|"not_enrolled"|"error", "is_active": true }

POST /api/client-errors

Telemetría de errores del cliente integrador.

CampoTipoReq.Descripción
error_codestringEj. WS_HANDSHAKE_FAILED, SEQUENCE_GAP
error_phasestringNopreflight, streaming, biometric_engine, …
session_idstringNoCorrelación con la sesión
hw_idstringNoHardware ID del cliente
extraobjectNoDatos adicionales

12. Endpoints — Métricas

GET /api/metrics/latency?endpoint=enroll&limit=50

Query: endpoint = enroll | verify | procesar_audio · limit int

13. Partner API v1 — Health

GET /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étodoRutaQuery paramsDescripció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

CampoDescripción
decisionALLOW, DENY, OPEN, ERROR
session_idUUID de la sesión biométrica
reason_code / failure_codeCódigo máquina del resultado
latency_msLatencia del intento
client_clock_skew_msDesfase de reloj cliente vs servidor
temporal_coherence_fail1 si falló coherencia temporal (TCL)
sequence_fail1 si hubo gap en secuencia de tramas

15. Partner API v1 — Administración de keys

Scope admin requerido. Uso interno / operadores.

MétodoRutaBody / ParamsDescripció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

WS /api/ws/stream?session_id={uuid}

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

CampoTipoReq.Descripción
typestring"ClientHello"
device_info.hw_idstringDebe coincidir con session/init
client_noncestringUUID único por intento (anti-replay)
timestamp_startintNoEpoch ms del inicio del stream
audio_layoutstringNo"block" | "interleaved" | "auto"
geohash_initstringNoGeohash inicial (9 chars)
gps_availableboolNoSi hay GPS
client_versionstringNoVersión del SDK cliente
challenge_idstringNoDel 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

typeDirecciónDescripción
audio_validationS→CFeedback no contractual (status: scanning)
hot_pad_alertS→CAlerta de seguridad PAD (security_halt: true)
errorS→CError no terminal o de cierre
ping / pongC↔SKeepalive
abort_sessionC→SAbortar sin finalizar

17. Formato de trama binaria (audio forense)

Cada trama se envía como mensaje binario por WebSocket, típicamente cada 250 ms.

OffsetTamañoEndianCampo
04 bytesBEsequence — uint32, empieza en 0, monotónico
48 bytesBEframe_ts_ms — uint64, epoch ms del cliente
129 bytesASCIIgeohash — relleno con null
21N bytesLEPCM int16 — dual branch A/B

Layout PCM (audio_layout)

ValorEstructura
blockTodas las muestras de rama A, luego todas las de rama B (recomendado cliente Node)
interleavedA₀, B₀, A₁, B₁, …
autoEl 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ódigoHTTPSignificado
API_KEY_REQUIRED401Falta header Bearer o X-API-Key
API_KEY_INVALID401Key incorrecta o revocada
SCOPE_DENIED403Scope insuficiente

Sesión y ambiente

CódigoHTTPSignificado
MISSING_HW_ID400Falta device_info.hw_id
S1_REQUIREDWSFalta POST /api/environment antes de tramas binarias
S1_CONTAINS_VOICE400Voz humana detectada en buffer ambiente
S1_SILENT400Silencio digital (ceros) en ambiente

WebSocket y streaming

CódigoSignificado
NONCE_REUSEDReplay detectado — client_nonce ya usado
CLOCK_SKEW_REJECTEDReloj cliente >5 min desfasado del servidor
SEQUENCE_GAPSecuencia de tramas no monotónica o con huecos
TCL_CHAIN_FAILFallo de coherencia temporal (cadena TCL)
E_PREFONVoz detectada en prefonación o silencio sintético
PAD_DENYMotor PAD rechazó el audio (no-vivo / replay)
IDENTITY_NOT_RECOGNIZEDVerify: voz no coincide con plantilla enrolada

Enrollment

CódigoHTTPSignificado
DEVICE_ALREADY_ENROLLED409El dispositivo ya tiene identidad
IDENTITY_ALREADY_ENROLLED409La voz ya está registrada

19. Checklist de certificación integrador

20. Seguridad y buenas prácticas

Recursos descargables en esta guía
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.