> For the complete documentation index, see [llms.txt](https://docs.facephi.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.facephi.com/sdks/backend-sdk/iad.md).

# IAD Service

**API REST de detección de ataques de inyección (Injection Attack Detection)** — Protege tus procesos de verificación biométrica facial frente a ataques que insertan contenido fraudulento directamente en el flujo digital de captura, eludiendo la cámara física del dispositivo.

## ¿Qué es IAD Service?

IAD Service es el microservicio de API REST de **Facephi IAD** para despliegues on-premise. Analiza la captura facial cifrada que genera el componente de captura y verifica que la captura:

* proviene de una cámara legítima del dispositivo del usuario;
* no ha sido manipulada por software intermedio;
* no se ha generado en un entorno no confiable;
* no contiene artefactos compatibles con contenido sintético, replay o reproducido.

En un ataque de inyección, el atacante no muestra nada a la cámara: sustituye el flujo de vídeo antes de que llegue al proceso biométrico, de forma que desde el servidor todo parece legítimo. IAD Service responde a la pregunta **¿la captura proviene realmente de la cámara del dispositivo?** con un veredicto binario, para que los ataques de inyección se bloqueen antes de que comprometan tus procesos biométricos y sin cambiar la experiencia del usuario legítimo.

IAD detecta, entre otros, estos tipos de ataque:

* **Inyección por cámara virtual:** cámaras virtuales, vídeo pregrabado y vídeo generado sintéticamente.
* **Manipulación de drivers del sistema:** sustitución del stream a nivel de driver.
* **Entornos de dispositivo emulados:** emuladores e inyección de archivos de imagen como entrada de cámara.
* **Captura por hardware:** capturadoras USB/HDMI y reproducción de vídeo desde dispositivos emisores.
* **API hacking en navegador:** extensiones y herramientas de depuración.
* **Modificaciones del cliente:** descompilación, reempaquetado y desactivación de controles de seguridad.
* **Hooking y API injection:** interceptación de funciones de captura y sustitución de imágenes durante la ejecución.
* **Inyección de JavaScript:** sustitución del objeto de stream y elusión de controles del cliente.
* **Ataques man-in-the-middle:** interceptación y sustitución del payload.
* **Inyección a nivel de red:** payloads biométricos falsificados.
* **Replay attacks:** reutilización de capturas previamente válidas.

> **Aviso de cambio incompatible (3.0.0)** La versión 3.0.0 rompe la compatibilidad de la API REST pública con todas las versiones 2.x. Las integraciones que actualicen desde 2.x deben actualizar la ruta de evaluación, la interpretación de `diagnostic` y el análisis de las respuestas correctas.
>
> Consulta las [notas de versión de 3.0.0](/sdks/backend-sdk/iad/release_notes/3.0.0_release_notes.md) para la guía de migración.

| Contrato 2.x                                                                                      | Contrato 3.0.0                                                                  |
| ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| `POST /api/v1/iad/liveness/evaluate`                                                              | `POST /api/v1/iad/evaluate`                                                     |
| `diagnostic`: `Live` o `NoLive`                                                                   | `diagnostic`: `NoAttack` o `Attack`                                             |
| Respuesta correcta con `probability`, `score`, `faceProbability`, `sdkDuration` y `queueDuration` | Respuesta correcta con `diagnostic`, `reason`, `transactionId` y `timestamp`    |
| `GET /api/v1/iad/metrics`: bloque `quality.liveness` con `live` y `noLive`                        | `GET /api/v1/iad/metrics`: bloque `quality.detection` con `noAttack` y `attack` |

**Úsalo para:**

* Detectar ataques de inyección en capturas faciales cifradas antes de continuar tu proceso biométrico
* Elegir el pipeline de análisis y la calibración con los que se evalúa cada captura
* Extraer la imagen facial de una captura cifrada
* Monitorizar el estado y el rendimiento del servicio
* Integrarlo sin fricciones en tus flujos de onboarding y autenticación

## Inicio rápido

### Requisitos

| Componente | Requisito                                  |
| ---------- | ------------------------------------------ |
| SO         | Linux x86\_64 (se recomienda Ubuntu 24.04) |
| Licencia   | Archivo de licencia válido de Facephi      |

### Despliegue con Docker

```bash
docker run -d \
  -p 6982:6982 \
  -v /path/to/license:/app/license \
  -v /path/to/config:/app/config \
  --name iad-service \
  facephicorp.jfrog.io/docker-pro-fphi/facephi-iad-service:v3.0.0
```

### Comprobar que funciona

```bash
# Check health
curl http://localhost:6982/api/v1/iad/health

# Check version
curl http://localhost:6982/api/v1/iad/version
```

Respuestas típicas:

```json
{
  "engine": {
    "healthy": true,
    "lastError": "",
    "lastHealthCheckAt": "2026-09-25T10:20:30Z",
    "lastHealthKnown": true
  },
  "initialized": true,
  "message": "Healthy"
}
```

```json
{
  "message": "3.0.0 Copyright © 2026 FacePhi Biometria. All rights reserved."
}
```

## Endpoints de la API

> **Nota de compatibilidad** Las rutas documentadas en esta página son válidas únicamente para la versión 3.0.0 y posteriores.

### Operaciones principales

| Endpoint                      | Método | Propósito                                                                                |
| ----------------------------- | ------ | ---------------------------------------------------------------------------------------- |
| `/api/v1/iad/evaluate`        | POST   | Detecta ataques de inyección en una captura cifrada y devuelve un veredicto binario      |
| `/api/v1/iad/evaluate/custom` | POST   | Igual que `/evaluate`, con el pipeline de análisis y la calibración que elige el cliente |
| `/api/v1/iad/extract`         | POST   | Extrae la imagen facial de una captura cifrada                                           |

### Gestión

| Endpoint                         | Método   | Propósito                                                                                                                         |
| -------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `/api/v1/iad/version`            | GET      | Versión del servicio y estado de la licencia                                                                                      |
| `/api/v1/iad/health`             | GET      | Indica si el servicio puede atender evaluaciones: licencia y estado del motor de análisis, con la comprobación del motor en caché |
| `/api/v1/iad/metrics`            | GET      | Snapshot JSON de métricas operativas y de calidad                                                                                 |
| `/api/v1/iad/metrics/prometheus` | GET      | Mismo snapshot que `/metrics`, en formato de exposición Prometheus                                                                |
| `/api/v1/iad/config`             | GET/POST | Obtener o actualizar la configuración                                                                                             |

`/health` y `/metrics` tienen objetivos distintos:

* `/health` indica si el servicio puede atender evaluaciones y está pensado para las sondas de orquestadores y balanceadores. Comprueba la licencia y, si es válida, el estado del motor de análisis.
  * Si todo está bien, responde HTTP `200` con `message` igual a `Healthy` y el bloque `engine`.
  * Si el servicio no puede atender evaluaciones, responde HTTP `503` (`error` igual a `ServiceUnavailable`) con un `message` que indica el motivo: `Service not initialized`, `Invalid license: <motivo> (code: <código>)` o `Engine health check failed: <detalle>`. `<detalle>` es, por ejemplo, `Connection failed`, `Request timeout`, `Engine is unhealthy (503)` o `Engine process ended`.
  * Para que consultarlo con frecuencia sea barato, reutiliza el resultado de la última comprobación del motor, correcta o fallida, mientras tenga menos de `engine_health_max_age_seconds` segundos (10 por defecto). Cuando caduca, vuelve a comprobar el motor con un único intento, sin los reintentos de `engine_max_retries` y con un máximo de 5 segundos: fija el timeout de las sondas por encima de ese valor.
  * Si el proceso del motor termina, `/health` responde `503` con `Engine health check failed: Engine process ended` de inmediato, sin esperar a que caduque la comprobación anterior (ver «Disponibilidad del motor de análisis»).
* `/metrics` expone contadores y snapshots en memoria, sin ejecutar comprobaciones en cada scrape. Su bloque `state.engine` refleja la última comprobación del motor que ha hecho el servicio.

### Disponibilidad del motor de análisis

El motor de análisis se ejecuta como un proceso aparte dentro del mismo contenedor, y el servicio lo arranca al iniciarse.

* Si el motor no responde (por ejemplo, porque está bloqueado o todavía cargando), `POST /api/v1/iad/evaluate`, `POST /api/v1/iad/evaluate/custom` y `POST /api/v1/iad/extract` responden HTTP `503` con `message` igual a `Engine communication error: <detalle>`, y `GET /api/v1/iad/health` responde `503`. El servicio sigue en marcha y vuelve a atender las peticiones en cuanto el motor responde.
* Si el proceso del motor termina (por ejemplo, porque el sistema lo detiene por falta de memoria), el servicio no lo vuelve a arrancar: deja de aceptar peticiones, libera la licencia y termina con código de salida `1`, para que la política de reinicio del contenedor arranque de nuevo el servicio y el motor. Si el motor termina mientras el servicio arranca, el servicio termina igual, sin llegar a abrir el puerto.
* Una parada ordenada del contenedor (`docker stop`, `docker compose down` o la eliminación del pod) termina con código de salida `0`.

Configura siempre una política de reinicio en el despliegue: sin ella, el contenedor queda detenido tras una caída del motor. Ver «Recuperación ante caídas del motor de análisis» en la [guía de instalación](/sdks/backend-sdk/iad/installation/installation_instructions.md).

## Evaluación de capturas

`POST /api/v1/iad/evaluate` recibe una petición `multipart/form-data` con la captura cifrada que genera el componente de captura:

| Campo     | Obligatorio | Descripción                      |
| --------- | ----------- | -------------------------------- |
| `capture` | Sí          | Captura facial cifrada (binario) |

Las respuestas correctas (HTTP `200`) devuelven:

| Campo           | Tipo   | Descripción                                                                                                                     |
| --------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------- |
| `diagnostic`    | string | Veredicto binario: `NoAttack` o `Attack`                                                                                        |
| `reason`        | string | Motivo del rechazo; `None` cuando `diagnostic` es `NoAttack`                                                                    |
| `transactionId` | string | Identificador único de la evaluación (UUID v4 en minúsculas)                                                                    |
| `timestamp`     | string | Instante UTC en que el servicio empezó a atender la petición, en formato ISO-8601 con milisegundos (`YYYY-MM-DDTHH:mm:ss.SSSZ`) |

El orden de las claves no forma parte del contrato. Ignora los campos desconocidos para que una versión futura que añada campos no rompa la integración.

### `diagnostic`

| Valor      | Significado                                                      |
| ---------- | ---------------------------------------------------------------- |
| `NoAttack` | No se ha detectado un ataque de inyección; la captura se acepta. |
| `Attack`   | Se ha detectado un ataque de inyección; la captura se rechaza.   |

`diagnostic` es el único campo que debe decidir si la captura se acepta. Por seguridad, trata como rechazo cualquier valor distinto de `NoAttack`.

Un veredicto `Attack` es una evaluación correcta: se devuelve con HTTP `200`, no como error. Las respuestas HTTP `400` indican que la captura no se ha podido evaluar o que la petición no es válida, y las respuestas HTTP `503`, que el motor de análisis no está disponible (ver «Contrato público de errores»).

### `reason`

`reason` es informativo: sirve para mostrar un mensaje o registrar el motivo, no para decidir el resultado.

| `reason`                        | Significado                                                                                                                                  |
| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `None`                          | La captura se aceptó (`NoAttack`) y no aplica ningún motivo de rechazo.                                                                      |
| `Unknown`                       | La captura se rechazó sin un motivo concreto informado por el análisis.                                                                      |
| `UntrustedEnvironment`          | La captura se rechazó porque se generó en un entorno de ejecución no confiable.                                                              |
| `SuspiciousActivity`            | La captura se rechazó porque el dispositivo mostró patrones de actividad asociados a un ataque.                                              |
| `UntrustedDevice`               | La captura se rechazó porque no se pudo confiar en que el dispositivo fuera el que dice ser.                                                 |
| `SdkIntegrityViolation`         | La captura se rechazó porque el SDK de captura o sus librerías parecen haber sido alterados.                                                 |
| `UntrustedCorruptedPayload`     | La captura se rechazó porque el payload parece estar corrupto o manipulado.                                                                  |
| `UntrustedContent`              | La captura se rechazó porque se detectó un ataque de inyección.                                                                              |
| `UntrustedContentLowConfidence` | La captura se rechazó porque se detectaron indicios de un ataque de inyección con menor confianza; debe tratarse igualmente como un rechazo. |

Cuando existen varias causas de rechazo, el servicio devuelve solo la primera. El servicio convierte a PascalCase los motivos que informa el análisis, así que pueden aparecer valores nuevos: muestra un mensaje genérico para los que no reconozcas.

### `transactionId` y `timestamp`

* `transactionId` se genera en cada petición, también cuando se reintenta con la misma captura. Identifica de forma única la evaluación: guárdalo en tus registros para poder referenciarla, por ejemplo en una consulta de soporte.
* `timestamp` es la hora del servidor al empezar a atender la petición. Úsalo, y no el reloj del cliente, cuando necesites la hora de la evaluación.
* Ninguno de los dos aparece en las respuestas de error.

## Evaluación con pipeline explícito

`POST /api/v1/iad/evaluate/custom` evalúa la captura igual que `POST /api/v1/iad/evaluate`, pero con el pipeline de análisis y la calibración que elige el cliente. La respuesta es la misma que la de `/evaluate`.

| Campo         | Obligatorio | Valores                             | Descripción                                      |
| ------------- | ----------- | ----------------------------------- | ------------------------------------------------ |
| `capture`     | Sí          | binario                             | Captura facial cifrada, igual que en `/evaluate` |
| `pipeline`    | Sí          | `iad_r01`, `iad_r00`                | Pipeline de análisis                             |
| `calibration` | No          | `regular` (por defecto), `hardened` | Calibración del pipeline                         |

* `pipeline` y `calibration` se comparan sin distinguir mayúsculas e ignorando los espacios iniciales y finales. Si `calibration` falta o está vacío, se usa `regular`.
* En el despliegue con la imagen Docker de Facephi, `POST /api/v1/iad/evaluate` usa el pipeline `iad_r01` con la calibración `regular`, y todas las combinaciones de `pipeline` y `calibration` están disponibles sin configuración adicional.
* Si se solicita una combinación que el motor de análisis no tiene disponible, la respuesta es HTTP `400` con `message` igual a `ErrorProcessing`.

Errores de validación propios de este endpoint (HTTP `400`, sin llegar a analizar la captura):

| Caso                           | `message`                                                           |
| ------------------------------ | ------------------------------------------------------------------- |
| Falta `pipeline`               | `Required multipart field 'pipeline' not found`                     |
| `pipeline` vacío o desconocido | `Invalid value for 'pipeline'; expected 'iad_r01' or 'iad_r00'`     |
| `calibration` desconocida      | `Invalid value for 'calibration'; expected 'regular' or 'hardened'` |

Los errores del campo `capture` se comprueban antes que los de `pipeline` y son los mismos que en `/evaluate`.

## Mitigación experimental de ataques de repetición (replay)

El servicio puede aplicar una ventana de vigencia (freshness window) a los payloads de captura entrantes. Esta protección experimental está deshabilitada por defecto.

* Actívala con `FACEPHI_IAD_REPLAY_ATTACK_CHECKER_ENABLED=true`
* Ajusta la ventana de vigencia con `FACEPHI_IAD_REPLAY_ATTACK_TOLERANCE_TIME=<seconds>`
* Ventana de vigencia por defecto: `300` segundos
* La aplica el motor de análisis únicamente en los endpoints de evaluación de liveness: `POST /api/v1/iad/evaluate` y `POST /api/v1/iad/evaluate/custom`
* Cuando se supera la ventana de vigencia, `/evaluate` y `/evaluate/custom` devuelven HTTP `400` con `message` igual a `Replay attack detected`
* No se aplica a `POST /api/v1/iad/extract`: la ruta `/extract_image` del motor solo descifra y extrae la imagen facial, sin ejecutar el pipeline de detección de inyección, por lo que no comprueba la ventana de vigencia y la extracción no se rechaza aunque la captura esté fuera de la ventana

Ejemplo de despliegue:

```bash
docker run -d \
  -p 6982:6982 \
  -e FACEPHI_IAD_REPLAY_ATTACK_CHECKER_ENABLED=true \
  -e FACEPHI_IAD_REPLAY_ATTACK_TOLERANCE_TIME=60 \
  -v /path/to/license:/app/license \
  -v /path/to/config:/app/config \
  --name iad-service \
  facephicorp.jfrog.io/docker-pro-fphi/facephi-iad-service:v3.0.0
```

Esta funcionalidad solo se configura al inicio mediante variables de entorno. No forma parte de `config.json` ni se expone a través de `GET|POST /api/v1/iad/config`.

## Autenticación JWT

La autenticación JWT es opcional y está deshabilitada por defecto.

* Configúrala al inicio mediante `config.json` con `auth_enabled`, `auth_jwt_secret`, `auth_accept_authorization_header`, `auth_accept_api_key_header` y `auth_api_key_header_name`
* Sobrescribe esos mismos valores mediante variables de entorno `FACEPHI_IAD_REST_AUTH_*`
* Cuando está habilitada, las operaciones principales y `GET|POST /api/v1/iad/config` requieren un JWT válido; `/health`, `/version`, `/metrics` y `/metrics/prometheus` no lo requieren
* El token se envía en la cabecera `Authorization: Bearer <jwt>` (si `auth_accept_authorization_header` es `true`) o, tal cual, en la cabecera `auth_api_key_header_name` (si `auth_accept_api_key_header` es `true`)
* El token debe estar firmado con HS256 usando `auth_jwt_secret` e incluir el claim `exp`; si incluye `nbf`, también se comprueba. Se tolera una diferencia de reloj de 60 segundos, así que un token se sigue aceptando hasta 60 segundos después de caducar
* Las peticiones sin token o con un token no válido reciben HTTP `401` con `error` igual a `Unauthorized` y la cabecera `WWW-Authenticate: Bearer`. `message` indica el motivo: `Missing authentication token`, `JWT token is expired`, `JWT token is not yet valid`, `JWT token does not include exp claim` o `JWT token validation failed`
* Si `auth_enabled` es `true` y `auth_jwt_secret` está vacío, todos los endpoints protegidos responden HTTP `401` con `message` igual a `Authentication secret is not configured`
* `GET /api/v1/iad/config` omite todas las claves de autenticación JWT
* `POST /api/v1/iad/config` rechaza todas las claves de autenticación JWT; usa en su lugar la configuración de inicio

Ejemplo de fragmento de `config.json`:

```json
{
  "auth_enabled": true,
  "auth_jwt_secret": "replace-with-secret",
  "auth_accept_authorization_header": true,
  "auth_accept_api_key_header": true,
  "auth_api_key_header_name": "x-api-key"
}
```

Ejemplo de variables de entorno:

```bash
export FACEPHI_IAD_REST_AUTH_ENABLED=true
export FACEPHI_IAD_REST_AUTH_JWT_SECRET=replace-with-secret
export FACEPHI_IAD_REST_AUTH_ACCEPT_AUTHORIZATION_HEADER=true
export FACEPHI_IAD_REST_AUTH_ACCEPT_API_KEY_HEADER=true
export FACEPHI_IAD_REST_AUTH_API_KEY_HEADER_NAME=x-api-key
```

## Contrato público de errores

Los errores usan el cuerpo de respuesta estándar de los servicios de Facephi. La clave de fecha de los errores es `timeStamp` (con S mayúscula y precisión de segundos), distinta de `timestamp` en las respuestas correctas de evaluación.

```json
{
  "status": 400,
  "error": "BadRequest",
  "message": "NoneBecauseFaceNotFound",
  "timeStamp": "2026-09-25T09:15:02Z"
}
```

| HTTP  | `error`              | Campo con el detalle | Cuándo                                                                                                                                                             |
| ----- | -------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `400` | `BadRequest`         | `message`            | Petición inválida, captura no procesable, repetición, fallo del análisis o licencia no válida                                                                      |
| `401` | `Unauthorized`       | `message`            | Falta la autenticación o no es válida (solo con JWT habilitado)                                                                                                    |
| `413` | —                    | —                    | El cuerpo de la petición supera `client_max_body_size`. La respuesta la genera el servidor HTTP antes de llegar al servicio y no lleva el cuerpo estándar de error |
| `500` | `Exception`          | `exception`          | Error interno no controlado; por ejemplo, `POST /api/v1/iad/config` con un cuerpo JSON que no es un objeto                                                         |
| `503` | `ServiceUnavailable` | `message`            | El motor de análisis no responde (operaciones de captura) o el servicio no puede atender evaluaciones (`GET /api/v1/iad/health`)                                   |

### Errores de validación normalizados

Para `POST /api/v1/iad/evaluate` y `POST /api/v1/iad/evaluate/custom`, los fallos de validación de la captura se devuelven como HTTP `400` con estos valores de `message`:

| Escenario                                                  | `message` público                 |
| ---------------------------------------------------------- | --------------------------------- |
| Cara demasiado cerca                                       | `NoneBecauseFaceTooClose`         |
| Cara no encontrada                                         | `NoneBecauseFaceNotFound`         |
| Cara recortada                                             | `NoneBecauseFaceCropped`          |
| Cara ocluida                                               | `NoneBecauseFaceOccluded`         |
| Demasiadas caras                                           | `NoneBecauseTooManyFaces`         |
| Ángulo de la cara demasiado grande                         | `NoneBecauseAngleTooLarge`        |
| Cara demasiado pequeña                                     | `NoneBecauseFaceTooSmall`         |
| Cara demasiado cerca del borde                             | `NoneBecauseFaceTooCloseToBorder` |
| Ojos cerrados                                              | `NoneBecauseEyesClosed`           |
| No se puede procesar la imagen o el payload                | `NoneBecauseImageDataError`       |
| Problema de licencia reportado por el runtime del servicio | `NoneBecauseLicenseError`         |
| Ventana de vigencia de repetición superada                 | `Replay attack detected`          |
| Fallo de evaluación sin clasificar                         | `ErrorProcessing`                 |

`POST /api/v1/iad/extract` normaliza los errores del motor de análisis igual que `/evaluate`: devuelve los mismos valores `NoneBecause*` de la tabla anterior y `ErrorFacialImage` en lugar de `ErrorProcessing` para los fallos sin clasificar. No devuelve `Replay attack detected`: la ruta `/extract_image` del motor no ejecuta el pipeline de detección de inyección, por lo que la ventana de vigencia no se aplica a la extracción (ver «Mitigación experimental de ataques de repetición (replay)»).

Errores comunes a los tres endpoints de captura, que indican un problema de integración, de licencia o de disponibilidad. Todos se devuelven con HTTP `400`, salvo `Engine communication error: <detalle>`, que se devuelve con HTTP `503`:

| `message`                                           | Causa                                                                                                                                                                                                                                                                            |
| --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Empty multipart payload`                           | La petición no contiene ningún campo multipart (por ejemplo, porque no se envía como `multipart/form-data`)                                                                                                                                                                      |
| `Required multipart field 'capture' not found`      | Falta el campo `capture`                                                                                                                                                                                                                                                         |
| `Empty data in multipart field 'capture'`           | El campo `capture` está vacío                                                                                                                                                                                                                                                    |
| `Invalid license: <motivo> (code: <código>)`        | La licencia del servicio dejó de ser válida con el servicio en marcha (caducó o no se pudo renovar); la captura no llega a analizarse                                                                                                                                            |
| `Service not initialized`, `Service not configured` | El servicio todavía no está listo                                                                                                                                                                                                                                                |
| `Engine communication error: <detalle>`             | El motor de análisis no responde: su proceso se ha detenido, está bloqueado o no contesta a tiempo. `<detalle>` es `Too many retries` cuando sigue sin responder tras los reintentos de `engine_max_retries`, o `Connection failed` o `Request timeout` cuando no hay reintentos |

## Métricas operativas

`GET /api/v1/iad/metrics` devuelve un snapshot JSON barato, apto para scraping frecuente, con los bloques `schemaVersion`, `service`, `state`, `traffic`, `saturation`, `latency`, `usage`, `quality` y `alerts`. `GET /api/v1/iad/metrics/prometheus` publica el mismo snapshot en formato de exposición Prometheus (ver «Exposición Prometheus» más abajo). Los contadores se acumulan en memoria desde el arranque del servicio.

| Bloque       | Contenido                                                                                                                                                                                                                                                                                                                                          |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `service`    | `name` (`facephi-iad-service`), `version`, `pathBase` (`/api/v1/iad`) y `configuredThreads`                                                                                                                                                                                                                                                        |
| `state`      | `initialized`, `license.valid`, `engine` (resultado de la última comprobación del motor de análisis: `healthy`, `lastHealthKnown`, `lastHealthCheckAt` y `lastError`) y `classification`                                                                                                                                                           |
| `traffic`    | Todas las peticiones atendidas, incluidas las de gestión: `totals` (`requests`, `success`, `clientErrors`, `serverErrors`, `unauthorized`), ventanas `1m`, `5m` y `15m` (`requests`, `rps`, `successRate`, `errorRate`, `clientErrors`, `serverErrors`, `unauthorized`) y `byEndpoint`, con una entrada por endpoint que ya ha recibido peticiones |
| `saturation` | `inflight`, `maxInflight1m`, `configuredThreads`, `queueP95Ms1m` y `backlogSuspected`                                                                                                                                                                                                                                                              |
| `latency`    | Latencias en milisegundos (`avg`, `p50`, `p95`, `p99`, `max`) total, de cola (`queueMs`) y de procesamiento (`sdkMs`), por ventana                                                                                                                                                                                                                 |
| `usage`      | Operaciones de negocio: `total` y `operations`, con la clave `"<MÉTODO> <ruta>"` (por ejemplo `POST /api/v1/iad/evaluate`). Excluye `/health`, `/version`, `/config` y `/metrics`                                                                                                                                                                  |
| `quality`    | Calidad agregada de las evaluaciones (ver «Calidad de las evaluaciones»)                                                                                                                                                                                                                                                                           |
| `alerts`     | Alertas activas (ver «Alertas»)                                                                                                                                                                                                                                                                                                                    |

`errorRate` cuenta todas las respuestas `4xx` y `5xx`, así que los rechazos HTTP `400` (por ejemplo `NoneBecause*`, `Replay attack detected` o los errores de validación de la petición) también suben la tasa de error. Un veredicto `Attack` se devuelve con HTTP `200` y no cuenta como error. Para clasificar el servicio y activar la alerta `HIGH_ERROR_RATE`, el servicio descuenta de la tasa de 5 minutos las capturas rechazadas con `Replay attack detected`: detectar una repetición es el servicio funcionando correctamente.

### Clasificación del estado

`state.classification` resume el snapshot en un único valor:

| Valor       | Cuándo                                                                                                                                                                                                         |
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `unhealthy` | El servicio no está inicializado, la licencia no es válida o la última comprobación de salud del engine falló                                                                                                  |
| `unknown`   | Todavía no hay tráfico, comprobaciones de salud ni alertas de calidad                                                                                                                                          |
| `saturated` | La cola de peticiones indica saturación                                                                                                                                                                        |
| `degraded`  | La tasa de error de 5 minutos (`4xx` y `5xx`, sin contar las repeticiones detectadas) alcanza el umbral, ha habido algún error de servidor (`5xx`) en los últimos 5 minutos o hay una alerta de calidad activa |
| `idle`      | El tráfico de 15 minutos está por debajo del umbral de inactividad                                                                                                                                             |
| `ok`        | Ninguno de los casos anteriores                                                                                                                                                                                |

Los casos se evalúan en el orden de la tabla y se aplica el primero que se cumple. Las respuestas `503` de `/health` y de las operaciones de captura cuentan como errores de servidor, así que, cuando el motor de análisis vuelve a estar disponible, el servicio se clasifica como `degraded` durante un máximo de 5 minutos.

### Calidad de las evaluaciones

El bloque `quality` agrega, desde el arranque del servicio, las evaluaciones de `/evaluate` y `/evaluate/custom`. Es un objeto vacío hasta la primera evaluación o la primera captura que el motor de análisis no puede evaluar: `detection` aparece con la primera evaluación correcta y `summary` en cuanto el bloque deja de estar vacío.

```json
{
  "quality": {
    "detection": {
      "samples": 3,
      "noAttack": 2,
      "attack": 1,
      "probability": { "samples": 3, "avg": 0.73, "min": 0.2, "max": 1.0 },
      "score": { "samples": 3, "avg": 0.74, "min": 0.22, "max": 1.0 }
    },
    "summary": {
      "lowQualitySamples": 1,
      "lowQualitySince": "2026-09-25T10:20:30Z"
    }
  }
}
```

| Campo                                      | Descripción                                                                                                                                 |
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `detection.samples`                        | Evaluaciones correctas agregadas                                                                                                            |
| `detection.noAttack`, `detection.attack`   | Evaluaciones por veredicto                                                                                                                  |
| `detection.probability`, `detection.score` | Resumen (`samples`, `avg`, `min`, `max`) de las puntuaciones del análisis, en escala de 0 a 1                                               |
| `detection.faceProbability`                | Resumen de la puntuación facial adicional, cuando el análisis la informa                                                                    |
| `summary.lowQualitySamples`                | Capturas que el motor de análisis no pudo evaluar (errores `NoneBecause*`, como `NoneBecauseFaceNotFound` o `NoneBecauseImageDataError`)    |
| `summary.lowQualitySince`                  | Inicio del episodio de baja calidad que mantiene activa la alerta `LOW_QUALITY`; solo aparece cuando alguna captura no se ha podido evaluar |

Un veredicto `Attack` no es una muestra de baja calidad: es el servicio detectando un ataque, así que no activa la alerta `LOW_QUALITY` ni degrada la clasificación del servicio, aunque venga acompañado de puntuaciones bajas. Tampoco cuentan las capturas rechazadas con `Replay attack detected`.

### Alertas

| `code`               | `severity` | `message`                                         |
| -------------------- | ---------- | ------------------------------------------------- |
| `LICENSE_EXPIRED`    | `critical` | `License is invalid or expired`                   |
| `ENGINE_UNAVAILABLE` | `critical` | `Engine health check failed`                      |
| `HIGH_ERROR_RATE`    | `warning`  | `5m error rate is above the warning threshold`    |
| `SATURATED`          | `warning`  | `Queue latency and inflight indicate saturation`  |
| `LOW_QUALITY`        | `warning`  | `Recent evaluation results show degraded quality` |
| `IDLE`               | `info`     | `Traffic is below the idle threshold`             |

* `LICENSE_EXPIRED` se activa cuando la licencia no es válida, y `ENGINE_UNAVAILABLE` cuando falló la última comprobación del motor de análisis.
* `HIGH_ERROR_RATE` se activa cuando la tasa de error de 5 minutos, que incluye las respuestas `4xx` y `5xx` salvo las repeticiones detectadas, alcanza `metrics_error_rate_warning`.
* `since` indica, en `LOW_QUALITY`, el inicio del episodio de baja calidad y, en `ENGINE_UNAVAILABLE`, la hora de la última comprobación fallida del motor. En el resto de alertas es la hora del snapshot.
* La alerta `LOW_QUALITY` se mantiene mientras se hayan observado capturas que el motor de análisis no pudo evaluar dentro de la ventana `metrics_quality_alert_window_seconds`.

Los umbrales de clasificación se describen en el apartado «Umbrales de las métricas operativas» de la [guía de instalación](/sdks/backend-sdk/iad/installation/installation_instructions.md).

### Exposición Prometheus

`GET /api/v1/iad/metrics/prometheus` devuelve el mismo snapshot como texto de exposición Prometheus (`text/plain; version=0.0.4; charset=utf-8`).

Las series de nivel HTTP se publican con el prefijo `facephi_` y las series propias del servicio con el prefijo `facephi_iad_`:

| Serie                                                                                                                                                                                                                                                                                       | Tipo    | Contenido                                                                                                                                                                                          |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `facephi_requests_total`, `facephi_request_success_total`, `facephi_request_client_errors_total`, `facephi_request_server_errors_total`, `facephi_request_unauthorized_total`                                                                                                               | counter | Totales del bloque `traffic`                                                                                                                                                                       |
| `facephi_endpoint_requests_total{method,path}`, `facephi_endpoint_request_success_total{method,path}`, `facephi_endpoint_request_client_errors_total{method,path}`, `facephi_endpoint_request_server_errors_total{method,path}`, `facephi_endpoint_request_unauthorized_total{method,path}` | counter | Desglose de `traffic` por endpoint                                                                                                                                                                 |
| `facephi_endpoint_inflight_requests{method,path}`                                                                                                                                                                                                                                           | gauge   | Peticiones en vuelo por endpoint                                                                                                                                                                   |
| `facephi_window_requests{window}`, `facephi_window_rps{window}`, `facephi_window_success_rate{window}`, `facephi_window_error_rate{window}`, `facephi_window_client_errors{window}`, `facephi_window_server_errors{window}`, `facephi_window_unauthorized{window}`                          | gauge   | Ventanas `1m`, `5m` y `15m` de `traffic`                                                                                                                                                           |
| `facephi_inflight_requests`, `facephi_configured_threads`, `facephi_backlog_suspected`, `facephi_max_inflight_requests{window}`                                                                                                                                                             | gauge   | Bloque `saturation`                                                                                                                                                                                |
| `facephi_total_latency_ms{window,stat}`, `facephi_queue_latency_ms{window,stat}`, `facephi_sdk_latency_ms{window,stat}`                                                                                                                                                                     | gauge   | Bloque `latency`, con `stat` en `avg`, `p50`, `p95`, `p99` y `max`                                                                                                                                 |
| `facephi_total_latency_ms_count{window}` / `_sum{window}` / `_bucket{window,le}` y sus equivalentes de cola y procesamiento                                                                                                                                                                 | gauge   | Recuento, suma e histograma acumulado de cada serie de latencia dentro de la ventana                                                                                                               |
| `facephi_iad_service_info{name,version,path_base,schema_version}`                                                                                                                                                                                                                           | gauge   | Bloque `service`, siempre con valor `1`                                                                                                                                                            |
| `facephi_iad_configured_threads`                                                                                                                                                                                                                                                            | gauge   | Hilos configurados (`service.configuredThreads`)                                                                                                                                                   |
| `facephi_iad_initialized`, `facephi_iad_license_valid`                                                                                                                                                                                                                                      | gauge   | Bloque `state`: arranque y licencia                                                                                                                                                                |
| `facephi_iad_state_classification{classification}`                                                                                                                                                                                                                                          | gauge   | `state.classification`: vale `1` en el valor vigente y `0` en el resto                                                                                                                             |
| `facephi_iad_engine_healthy`, `facephi_iad_engine_last_health_known`, `facephi_iad_engine_last_health_check_timestamp_seconds`                                                                                                                                                              | gauge   | Bloque `state.engine`                                                                                                                                                                              |
| `facephi_iad_usage_counters_enabled`                                                                                                                                                                                                                                                        | gauge   | `usage.enabled`                                                                                                                                                                                    |
| `facephi_iad_usage_operations_total{operation}`, `facephi_iad_usage_total`                                                                                                                                                                                                                  | counter | Bloque `usage`; `operation` es la clave `"<MÉTODO> <ruta>"` de `usage.operations`                                                                                                                  |
| `facephi_iad_quality_samples{operation="detection"}`, `facephi_iad_low_quality_samples`, `facephi_iad_low_quality_since_timestamp_seconds`                                                                                                                                                  | gauge   | Bloque `quality`: evaluaciones agregadas y capturas que no se pudieron evaluar                                                                                                                     |
| `facephi_iad_detection_results_total{result}`                                                                                                                                                                                                                                               | counter | Reparto `noAttack`/`attack` de `quality.detection`                                                                                                                                                 |
| `facephi_iad_quality_score_samples{operation,metric}`, `facephi_iad_quality_score_avg{operation,metric}`, `facephi_iad_quality_score_min{operation,metric}`, `facephi_iad_quality_score_max{operation,metric}`                                                                              | gauge   | Resúmenes de `quality.detection`, donde `metric` es `probability`, `score` o `faceProbability`                                                                                                     |
| `facephi_iad_alert_active{code,severity}`, `facephi_iad_alert_since_timestamp_seconds{code,severity}`                                                                                                                                                                                       | gauge   | Bloque `alerts`: `facephi_iad_alert_active` vale `1` en las alertas activas y `0` en el resto, y `facephi_iad_alert_since_timestamp_seconds` publica su `since`, o `0` si la alerta no está activa |

Notas de la exposición:

* Solo los totales globales, las series de `saturation` sin etiquetas y las series `facephi_iad_*` declaran su tipo con una línea `# TYPE`. Las series por endpoint, por ventana y de latencia se publican sin esa línea; la columna «Tipo» indica cómo interpretarlas.
* Las series de ventana (`{window="1m|5m|15m"}`) resumen solo las peticiones de esa ventana deslizante, así que pueden bajar entre scrapes.
* Las series de clasificación y de alertas publican siempre todos los valores posibles, con `0` cuando la condición no está activa, para que una serie no aparezca ni desaparezca entre scrapes. Los recuentos de `quality` también se publican, con `0`, antes de la primera evaluación.
* Las series `facephi_iad_quality_score_*` de una puntuación solo aparecen cuando el snapshot JSON incluye su resumen.
* `facephi_iad_usage_operations_total` publica una serie por cada operación de negocio presente en `usage.operations`, es decir, por cada endpoint de negocio que ya ha recibido peticiones.
* Los instantes se publican como segundos epoch (`*_timestamp_seconds`) en lugar de como cadenas ISO-8601, y valen `0` cuando el snapshot todavía no ha registrado ese instante.
* El texto descriptivo del snapshot (`state.engine.lastError` y `alerts[].message`) se publica únicamente en la respuesta JSON: no aporta valor medible y como etiqueta multiplicaría la cardinalidad de las series.

## Ejemplo de uso

> **Nota de actualización** Los siguientes ejemplos usan el contrato público 3.x introducido en la versión 3.0.0.

### Evaluar una captura

```bash
curl -X POST \
  -F "capture=@biometric_capture.bin" \
  http://localhost:6982/api/v1/iad/evaluate
```

**Respuesta:**

```json
{
  "diagnostic": "NoAttack",
  "reason": "None",
  "timestamp": "2026-09-24T09:04:03.776Z",
  "transactionId": "f58dccd4-f2ce-4057-a99b-4dc916015745"
}
```

Captura rechazada:

```json
{
  "diagnostic": "Attack",
  "reason": "UntrustedDevice",
  "timestamp": "2026-09-24T09:10:41.052Z",
  "transactionId": "3b0e6f2a-9c41-4d7e-b1a8-5f2d9e0c7a13"
}
```

### Evaluar con un pipeline explícito

```bash
curl -X POST \
  -F "capture=@biometric_capture.bin" \
  -F "pipeline=iad_r00" \
  -F "calibration=hardened" \
  http://localhost:6982/api/v1/iad/evaluate/custom
```

La respuesta tiene el mismo formato que la de `/evaluate`.

### Extraer imagen

```bash
curl -X POST \
  -F "capture=@biometric_capture.bin" \
  http://localhost:6982/api/v1/iad/extract
```

**Respuesta:**

```json
{
  "image": "base64_encoded_image...",
  "mimeType": "image/jpeg"
}
```

### Obtener configuración

Devuelve la vista pública de la configuración en tiempo de ejecución, con todos los valores como cadenas de texto. Incluye los parámetros del servicio con su valor por defecto o el de `config.json`, y cualquier otro parámetro definido en `config.json` o enviado a `POST /api/v1/iad/config`; no refleja los valores establecidos mediante variables de entorno. Las claves de autenticación JWT se omiten intencionadamente.

```bash
curl http://localhost:6982/api/v1/iad/config
```

**Respuesta (extracto):**

```json
{
  "client_max_body_size": "100",
  "connection_timeout": "60",
  "keep_alive_request_number": "0",
  "logger_level": "info",
  "logger_max_files": "7",
  "logger_path": "",
  "logger_rotation": "daily",
  "number_of_threads": "1",
  "port": "6982"
}
```

### Actualizar configuración

`POST /api/v1/iad/config` espera un objeto JSON con el campo `config_json_string`, que contiene los parámetros que se quieren modificar serializados como una cadena JSON; los parámetros que no se incluyen conservan su valor. Solo se aplican en tiempo de ejecución los umbrales `metrics_*` con los que `GET /api/v1/iad/metrics` clasifica el estado del servicio; el resto de parámetros son de solo lectura y se configuran al inicio. La referencia de la operación `POST /api/v1/iad/config` incluye la tabla completa de parámetros. Las claves de autenticación JWT son rechazadas por este endpoint y deben establecerse únicamente al inicio.

```bash
curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"config_json_string":"{\"metrics_error_rate_warning\":0.1,\"metrics_quality_alert_window_seconds\":600}"}' \
  http://localhost:6982/api/v1/iad/config
```

**Respuesta:**

```json
{
  "message": "Configuration updated successfully"
}
```

## Configuración

Crea `/app/config/config.json`:

```json
{
  "port": 6982,
  "number_of_threads": 1,
  "connection_timeout": 60,
  "keep_alive_request_number": 0,
  "client_max_body_size": 100,
  "logger_level": "info",
  "logger_path": "/app/logs",
  "logger_rotation": "daily",
  "logger_max_files": 7,
  "auth_enabled": false,
  "auth_jwt_secret": "",
  "auth_accept_authorization_header": true,
  "auth_accept_api_key_header": true,
  "auth_api_key_header_name": "x-api-key",
  "engine_connection_timeout": 10000,
  "engine_request_timeout": 60000,
  "engine_max_retries": 3,
  "engine_retry_delay": 1000,
  "engine_verify_ssl": false,
  "engine_verbose": false,
  "engine_pool_size": 4,
  "engine_url": "http://localhost:8080",
  "engine_health_max_age_seconds": 10
}
```

### Parámetros clave

| Parámetro                          | Por defecto             | Descripción                                                                                                                                                                                             |
| ---------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `port`                             | 6982                    | Puerto de escucha del servicio                                                                                                                                                                          |
| `number_of_threads`                | 1                       | Hilos de trabajo para el procesamiento de solicitudes                                                                                                                                                   |
| `connection_timeout`               | 60                      | Tiempo máximo, en segundos, que una conexión inactiva permanece abierta                                                                                                                                 |
| `keep_alive_request_number`        | 0                       | Número máximo de peticiones por conexión keep-alive (0 = sin límite)                                                                                                                                    |
| `client_max_body_size`             | 100                     | Tamaño máximo del cuerpo de la solicitud en MB                                                                                                                                                          |
| `logger_level`                     | "info"                  | Nivel de log: `trace`, `debug`, `info`, `warning`, `error`, `critical` u `off`                                                                                                                          |
| `auth_enabled`                     | false                   | Requiere autenticación JWT para los endpoints protegidos                                                                                                                                                |
| `auth_jwt_secret`                  | ""                      | Secreto compartido HS256 usado para validar los JWT                                                                                                                                                     |
| `auth_accept_authorization_header` | true                    | Acepta `Authorization: Bearer <jwt>`                                                                                                                                                                    |
| `auth_accept_api_key_header`       | true                    | Acepta JWT en la cabecera de API key configurada                                                                                                                                                        |
| `auth_api_key_header_name`         | "x-api-key"             | Nombre de la cabecera usada cuando la extracción de token por API key está habilitada                                                                                                                   |
| `engine_connection_timeout`        | 10000                   | Timeout de conexión (ms)                                                                                                                                                                                |
| `engine_request_timeout`           | 60000                   | Timeout de solicitud (ms)                                                                                                                                                                               |
| `engine_max_retries`               | 3                       | Reintentos de una petición al engine que no obtiene respuesta (error de conexión o timeout) o que recibe un `5xx`, `408` o `429`. No se aplican a la comprobación del motor de `GET /api/v1/iad/health` |
| `engine_retry_delay`               | 1000                    | Retardo inicial entre reintentos del engine en ms; se duplica en cada reintento                                                                                                                         |
| `engine_verify_ssl`                | false                   | Verifica los certificados SSL del análisis de capturas                                                                                                                                                  |
| `engine_verbose`                   | false                   | Habilita los logs detallados del proxy de análisis de capturas                                                                                                                                          |
| `engine_pool_size`                 | 4                       | Tamaño del pool de conexiones                                                                                                                                                                           |
| `engine_url`                       | `http://localhost:8080` | URL base del runtime de análisis de capturas                                                                                                                                                            |
| `engine_health_max_age_seconds`    | 10                      | Segundos durante los que `GET /api/v1/iad/health` reutiliza la última comprobación del motor de análisis; con `0`, lo comprueba en cada llamada                                                         |

El archivo `config.json` es opcional: si no existe, el servicio arranca con los valores por defecto. La lista completa de parámetros (incluidos `logger_file_prefix`, `metrics_enabled` y los umbrales `metrics_*` de las métricas operativas) y las variables de entorno que sobrescriben cada uno se describen en el apartado «Configuración del servicio» de la [guía de instalación](/sdks/backend-sdk/iad/installation/installation_instructions.md).

## Descripción general de la arquitectura

```mermaid
flowchart TD
    Client["Cliente<br/>Aplicación"]

      Client -->|HTTP/REST| IADService

      subgraph IADService["Facephi IAD Service"]
        Components["• Licencia<br/>• Endpoints<br/>• Detección de ataques de inyección<br/>• Mitigación de ataques de repetición<br/>• Pool de conexiones<br/>• Gestión de configuración<br/>• Métricas operativas"]
      end

      style Client fill:#4a9eff,stroke:#333,stroke-width:2px,color:#000
      style IADService fill:#111111,stroke:#333,stroke-width:2px,color:#000
      style Components fill:#60a5fa,stroke:#333,stroke-width:2px,color:#000
```

## Soporte

Para consultas de licencia o soporte técnico, contacta con tu representante de Facephi.
