> 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/finger.md).

# Finger Service

**API REST de huella dactilar** — Extrae plantillas de huella dactilar y compara dos huellas (autenticación 1:1) desde tus propios sistemas, con un despliegue on-premise.

## ¿Qué es Finger Service?

Finger Service es el microservicio de API REST de huella dactilar de Facephi para despliegues on-premise. Expone, bajo `/api/v1/finger/`, un contrato público con nombres de campo orientados a huella dactilar (`image`, `position`, `fingerTemplate`, `authStatus`, `similarity`) y mantiene abstraído el runtime interno de procesamiento.

> **Aviso de cambio incompatible (2.0.0)** A partir de la versión 2.0.0, los endpoints de operaciones de huella dactilar no son compatibles con la serie 1.x.x:
>
> * `POST /api/v1/finger/extract`
> * `POST /api/v1/finger/authenticate`
>
> Las rutas heredadas `POST /api/v1/finger/create-template` y `POST /api/v1/finger/verify` ya no están disponibles y responden HTTP `404`. Los formatos de payload de petición y respuesta han cambiado: las integraciones creadas para 1.x.x deben migrar al contrato público 2.x antes de actualizar.

**Úsalo para:**

* Extraer una plantilla de huella dactilar a partir de una imagen, con su calidad
* Autenticar 1:1 dos huellas dactilares, a partir de imágenes, de plantillas o de una combinación de ambas
* Monitorizar el estado, el uso y la calidad del servicio
* Escalar el procesamiento con una topología de clúster (un nodo `master` y varios nodos `worker`)

Los formatos de imagen admitidos en `image`, `image1` e `image2` son `wsq`, `bmp`, `png`, `jpg` y `jp2`.

## Inicio rápido

### Requisitos

| Componente | Requisito                             |
| ---------- | ------------------------------------- |
| SO         | Linux x86\_64                         |
| Runtime    | Docker                                |
| 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 finger-service \
  facephicorp.jfrog.io/docker-pro-fphi/facephi-finger-service:2.2.1
```

Al arrancar, el servicio pone en marcha los componentes internos de procesamiento de huella y espera a que estén listos antes de abrir el puerto `6982`: durante al menos 60 segundos en un nodo `master` (30 segundos en un nodo `worker`) las peticiones no obtienen respuesta. El detalle está en la [guía de instalación](/sdks/backend-sdk/finger/installation/installation_instructions.md).

### Comprobar que funciona

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

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

Respuestas típicas:

```json
{
  "engine": {
    "healthy": true,
    "initialized": true,
    "lastError": "",
    "lastHealthCheckAt": "2026-09-30T10:20:30Z",
    "lastHealthKnown": true,
    "role": "master",
    "servicesConfigured": ["Master", "FingerTC", "Verifier"]
  },
  "initialized": true,
  "license": {
    "statusCode": "LF_OK",
    "statusMessage": "The license is valid.",
    "valid": true
  },
  "message": "Healthy"
}
```

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

## Endpoints de la API

### Operaciones principales

| Endpoint                      | Método | Propósito                                                      |
| ----------------------------- | ------ | -------------------------------------------------------------- |
| `/api/v1/finger/extract`      | POST   | Extrae una plantilla de huella dactilar a partir de una imagen |
| `/api/v1/finger/authenticate` | POST   | Autentica 1:1 dos huellas dactilares                           |

### Gestión

| Endpoint                            | Método   | Propósito                                                                          |
| ----------------------------------- | -------- | ---------------------------------------------------------------------------------- |
| `/api/v1/finger/version`            | GET      | Versión del servicio y estado de la licencia                                       |
| `/api/v1/finger/health`             | GET      | Comprobación activa de estado de la licencia y de los componentes de procesamiento |
| `/api/v1/finger/metrics`            | GET      | Snapshot JSON de métricas operativas, de uso y de calidad                          |
| `/api/v1/finger/metrics/prometheus` | GET      | Mismo snapshot que `/metrics`, en formato de exposición Prometheus                 |
| `/api/v1/finger/config`             | GET/POST | Obtener o actualizar la configuración en tiempo de ejecución                       |

### Disponibilidad por rol

El rol del nodo se elige al arrancar con la variable de entorno `FACEPHI_FINGER_ENGINE_ROLE` (ver la [guía de instalación](/sdks/backend-sdk/finger/installation/installation_instructions.md)):

| Rol                    | Endpoints disponibles                                                                                        |
| ---------------------- | ------------------------------------------------------------------------------------------------------------ |
| `master` (por defecto) | Todos: operaciones principales y gestión                                                                     |
| `worker`               | Solo los de gestión. `POST /api/v1/finger/extract` y `POST /api/v1/finger/authenticate` responden HTTP `404` |

### `/health` y `/metrics`

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

* `/health` comprueba la licencia y, si es válida, ejecuta en cada llamada la comprobación activa de los componentes internos de procesamiento y actualiza el snapshot de salud.
  * Responde siempre HTTP `200`. `message` vale `Healthy` cuando la licencia es válida y la comprobación ha ido bien, y `Unhealthy` en cualquier otro caso: revisa `license.valid` y `engine.healthy` para conocer el motivo.
  * Si la licencia no es válida, no se comprueban los componentes de procesamiento y el bloque `engine` conserva el resultado de la comprobación anterior.
  * En los nodos `master`, la comprobación aplica los reintentos de `engine_max_retries` y `engine_retry_delay`, así que cuando falla la respuesta puede tardar varios segundos: tenlo en cuenta al fijar el timeout de las sondas.
  * `engine.lastError` indica el motivo de la última comprobación fallida, por ejemplo `Too many retries` o `Engine nodes not yet fully registered` mientras los componentes de procesamiento se están poniendo en marcha.
* `/metrics` expone contadores y snapshots en memoria, sin procesar huellas ni ejecutar comprobaciones activas en cada scrape. Su bloque `state.engine` refleja la última llamada a `/health`.

## Extracción de plantillas

`POST /api/v1/finger/extract` recibe un objeto JSON con la imagen de la huella y devuelve su plantilla:

| Campo                    | Obligatorio | Descripción                                                                                               |
| ------------------------ | ----------- | --------------------------------------------------------------------------------------------------------- |
| `image`                  | Sí          | Imagen de la huella dactilar codificada en Base64 (RFC 4648). Formatos: `wsq`, `bmp`, `png`, `jpg`, `jp2` |
| `position`               | Sí          | Posición del dedo (ver «Posiciones de los dedos»)                                                         |
| `options.dpi`            | No          | Resolución de la imagen en puntos por pulgada, entero entre `250` y `1000`. Por defecto, `500`            |
| `options.scanType`       | No          | Tipo de captura: `Plain` (por defecto), `Rolled` o `Latent`                                               |
| `options.includeQuality` | No          | Booleano. Con `false`, la respuesta omite `quality` y `qualityDetails`. Por defecto, `true`               |

Petición:

```json
{
  "image": "<base64_fingerprint_image>",
  "position": "RightIndex",
  "options": {
    "dpi": 500,
    "scanType": "Plain",
    "includeQuality": true
  }
}
```

Las respuestas correctas (HTTP `200`) devuelven:

| Campo            | Tipo    | Descripción                                                                                                                                                                                             |
| ---------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `fingerTemplate` | string  | Plantilla generada, codificada en Base64. Es la que se envía después en `fingerTemplate1` o `fingerTemplate2` de `/authenticate`                                                                        |
| `position`       | string  | Posición del dedo                                                                                                                                                                                       |
| `scanType`       | string  | Tipo de captura                                                                                                                                                                                         |
| `quality`        | integer | Calidad global de la captura (0-100; cuanto mayor, mejor). Solo aparece si el procesamiento la informa y `options.includeQuality` no es `false`                                                         |
| `qualityDetails` | object  | Detalle de calidad: `nfiq2` (NFIQ 2, 0-100; cuanto mayor, mejor), `nfiq` y `score`. Cada campo solo aparece si el procesamiento lo informa, y el bloque se omite si `options.includeQuality` es `false` |
| `sdkDuration`    | integer | Tiempo de procesamiento en milisegundos                                                                                                                                                                 |
| `queueDuration`  | integer | Tiempo de espera en cola en milisegundos                                                                                                                                                                |

Respuesta:

```json
{
  "fingerTemplate": "<base64_generated_template>",
  "position": "RightIndex",
  "scanType": "Plain",
  "quality": 88,
  "qualityDetails": {
    "nfiq2": 79.0,
    "nfiq": 1.0,
    "score": 88.0
  },
  "sdkDuration": 123,
  "queueDuration": 2
}
```

Con `"includeQuality": false`, la respuesta solo contiene `fingerTemplate`, `position`, `scanType`, `sdkDuration` y `queueDuration`.

### Posiciones de los dedos

`position`, `position1` y `position2` admiten estos valores: `RightThumb`, `RightIndex`, `RightMiddle`, `RightRing`, `RightLittle`, `LeftThumb`, `LeftIndex`, `LeftMiddle`, `LeftRing` y `LeftLittle`. Se comparan distinguiendo mayúsculas y minúsculas.

## Autenticación 1:1

`POST /api/v1/finger/authenticate` compara una huella de referencia (lado 1) con una huella de galería (lado 2). Cada lado se envía como imagen o como plantilla, y se pueden combinar (por ejemplo, una imagen en el lado 1 y una plantilla en el lado 2):

| Campo                        | Obligatorio                                         | Descripción                                                                                                                   |
| ---------------------------- | --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `image1` o `fingerTemplate1` | Exactamente uno                                     | Huella de referencia: imagen codificada en Base64 (RFC 4648) o plantilla devuelta por `/extract`                              |
| `image2` o `fingerTemplate2` | Exactamente uno                                     | Huella de galería: imagen codificada en Base64 (RFC 4648) o plantilla devuelta por `/extract`                                 |
| `position`                   | Sí, salvo que se indiquen `position1` y `position2` | Posición del dedo, común a los dos lados                                                                                      |
| `position1`, `position2`     | No                                                  | Posición del dedo de cada lado. Si se indica, prevalece sobre `position` para ese lado                                        |
| `options.dpi`                | No                                                  | Resolución, en puntos por pulgada, de las imágenes de `image1` e `image2`, entero entre `250` y `1000`. Por defecto, `500`    |
| `options.authThreshold`      | No                                                  | Similitud mínima para un resultado `Positive`, entero entre `0` y `100`. Por defecto, `20`. Los valores decimales se rechazan |

Petición usando plantillas:

```json
{
  "fingerTemplate1": "<base64_probe_template>",
  "fingerTemplate2": "<base64_gallery_template>",
  "position": "RightIndex",
  "options": {
    "authThreshold": 20
  }
}
```

Petición usando imágenes:

```json
{
  "image1": "<base64_probe_image>",
  "image2": "<base64_gallery_image>",
  "position": "RightIndex",
  "options": {
    "dpi": 500,
    "authThreshold": 20
  }
}
```

Las respuestas correctas (HTTP `200`) devuelven:

| Campo           | Tipo    | Descripción                                                                                           |
| --------------- | ------- | ----------------------------------------------------------------------------------------------------- |
| `authStatus`    | string  | `Positive` si `similarity` es mayor o igual que `options.authThreshold`; `Negative` en caso contrario |
| `similarity`    | number  | Similitud entre las dos huellas, entre `0` y `100`                                                    |
| `sdkDuration`   | integer | Tiempo de procesamiento en milisegundos                                                               |
| `queueDuration` | integer | Tiempo de espera en cola en milisegundos                                                              |

```json
{
  "authStatus": "Positive",
  "similarity": 84.65,
  "sdkDuration": 131,
  "queueDuration": 1
}
```

Un resultado `Negative` es una autenticación correcta: se devuelve con HTTP `200`, no como error. Las respuestas HTTP `400` indican que la petición no es válida o que las huellas no se han podido procesar (ver «Contrato público de errores»).

## 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_FINGER_REST_AUTH_*`
* Cuando está habilitada, las operaciones principales y `GET|POST /api/v1/finger/config` requieren un JWT válido; `/health`, `/version`, `/metrics`, `/metrics/prometheus` y las peticiones `OPTIONS` 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`
* 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 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/finger/config` omite todas las claves de autenticación JWT
* `POST /api/v1/finger/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_FINGER_REST_AUTH_ENABLED=true
export FACEPHI_FINGER_REST_AUTH_JWT_SECRET=replace-with-secret
export FACEPHI_FINGER_REST_AUTH_ACCEPT_AUTHORIZATION_HEADER=true
export FACEPHI_FINGER_REST_AUTH_ACCEPT_API_KEY_HEADER=true
export FACEPHI_FINGER_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), en formato ISO-8601 UTC con precisión de segundos.

```json
{
  "status": 400,
  "error": "BadRequest",
  "message": "Invalid finger position",
  "timeStamp": "2026-09-30T09:15:02Z"
}
```

| HTTP  | `error`        | Campo con el detalle | Cuándo                                                                                                                                                                      |
| ----- | -------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400` | `BadRequest`   | `message`            | Petición inválida o huella que no se ha podido procesar                                                                                                                     |
| `401` | `Unauthorized` | `message`            | Falta la autenticación o no es válida (solo con JWT habilitado)                                                                                                             |
| `404` | —              | —                    | Ruta no disponible: rutas heredadas de 1.x.x, u operaciones principales en un nodo `worker`. La respuesta la genera el servidor HTTP y no lleva el cuerpo estándar de error |
| `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/finger/config` con un `config_json_string` que no es un objeto JSON válido                                          |

### Errores de validación

Las peticiones se validan antes de procesar las huellas. Los errores de validación se devuelven con HTTP `400`:

| Escenario                                                            | `message`                                                                                                |
| -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| El cuerpo no es un objeto JSON enviado como `application/json`       | `Value, object or array expected.`                                                                       |
| Falta un campo obligatorio de `/extract` (`image` o `position`)      | `Key <campo> not found`                                                                                  |
| Un campo no es una cadena de texto o está vacío                      | `Key <campo> is not a string`, `Key <campo> is empty string`                                             |
| `options` no es un objeto                                            | `Key options is not an object`                                                                           |
| `options.dpi` u `options.authThreshold` no es un entero sin signo    | `Key <campo> is not an unsigned integer`                                                                 |
| `options.includeQuality` no es un booleano                           | `Key includeQuality is not a bool`                                                                       |
| El cuerpo de `/authenticate` es un objeto JSON vacío                 | `Empty payload`                                                                                          |
| Una imagen o plantilla no es Base64 válido                           | `Malformed Base64 input`                                                                                 |
| Falta la posición del dedo en `/authenticate`                        | `Missing required field: position`                                                                       |
| La posición del dedo no es un valor admitido                         | `Invalid finger position`                                                                                |
| `options.scanType` no es un valor admitido                           | `Invalid scanType value`                                                                                 |
| `options.dpi` está fuera del rango 250-1000                          | `Invalid dpi value`                                                                                      |
| `options.authThreshold` es mayor que 100                             | `Invalid authThreshold value`                                                                            |
| No se envía ningún origen del lado 1 o del lado 2 en `/authenticate` | `Missing required field: image1 or fingerTemplate1`, `Missing required field: image2 or fingerTemplate2` |
| Se envían los dos orígenes de un mismo lado en `/authenticate`       | `Provide exactly one of image1 or fingerTemplate1`, `Provide exactly one of image2 or fingerTemplate2`   |

El orden exacto en el que se comprueba cada caso está en la [referencia de la API](/sdks/backend-sdk/finger/technical_documentation/api_reference.md).

### Errores de procesamiento

| `message`                                               | Causa                                                                                                                                                                                                                                             |
| ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Finger template creation failed`                       | `/extract`: no se ha podido generar la plantilla a partir de la imagen                                                                                                                                                                            |
| `Finger verification failed`                            | `/authenticate`: no se han podido comparar las huellas                                                                                                                                                                                            |
| `Invalid response from Engine`                          | Los componentes internos de procesamiento han devuelto una respuesta que el servicio no puede interpretar                                                                                                                                         |
| `Fingerprint processing communication error: <detalle>` | No se ha podido comunicar con los componentes internos de procesamiento, o han respondido con un error HTTP. `<detalle>` es, por ejemplo, `Too many retries` cuando han seguido fallando tras los reintentos configurados en `engine_max_retries` |
| `Service not initialized`, `Service not configured`     | El servicio todavía no está listo                                                                                                                                                                                                                 |

Solo las operaciones completadas con éxito incrementan los contadores de uso y alimentan las cifras de calidad de `/metrics`.

## Métricas operativas

`GET /api/v1/finger/metrics` devuelve un snapshot JSON barato, apto para scraping frecuente. `GET /api/v1/finger/metrics/prometheus` publica el mismo snapshot en formato de exposición Prometheus (ver «Exposición Prometheus» más abajo). Las cifras de tráfico, saturación, latencia y calidad se acumulan en memoria desde el arranque del servicio; los contadores de uso son los de la licencia.

| Bloque                                                                    | Contenido                                                                                                                                                                                                                                                                                          |
| ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `schemaVersion`, `timestamp`                                              | Versión del formato (`1.0`) e instante UTC del snapshot                                                                                                                                                                                                                                            |
| `service`                                                                 | `name` (`facephi-finger-service`), `version`, `role`, `pathBase` (`/api/v1/finger`) y `configuredThreads`                                                                                                                                                                                          |
| `state`                                                                   | `initialized`, `license` (`valid`, `statusCode`, `statusMessage`), `engine` (resultado de la última llamada a `/health`: `role`, `servicesConfigured`, `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`) 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 (`totalMs`), de cola (`queueMs`) y de procesamiento (`sdkMs`), por ventana                                                                                                                                                     |
| `usage`                                                                   | Contadores de uso de la licencia para las operaciones completadas con éxito: `enabled`, `operations.extract`, `operations.authenticate` y `total` (suma de ambos)                                                                                                                                  |
| `usageCountersEnabled`, `extractCount`, `authenticateCount`, `totalCount` | Los mismos valores de `usage`, con los nombres de campo de la versión 2.1.0                                                                                                                                                                                                                        |
| `quality`                                                                 | Calidad agregada de las operaciones (ver «Calidad de las operaciones»)                                                                                                                                                                                                                             |
| `alerts`                                                                  | Alertas activas (ver «Alertas»)                                                                                                                                                                                                                                                                    |

`errorRate` cuenta todas las respuestas `4xx` y `5xx`, así que los rechazos HTTP `400` (por ejemplo, los errores de validación) también suben la tasa de error. Un resultado `Negative` de `/authenticate` se devuelve con HTTP `200` y no cuenta como error.

### Contadores de uso

Los contadores de uso dependen de la licencia. Si tu licencia no los tiene habilitados, `usage.enabled` (y `usageCountersEnabled`) vale `false` y los contadores valen `0`; consulta con Facephi si necesitas habilitarlos.

* Solo cuentan las operaciones `/extract` y `/authenticate` completadas con éxito (HTTP `200`). Las peticiones inválidas y los errores de procesamiento no se cuentan.
* Los valores son los acumulados por la licencia, incluidos los incrementos de esta instancia que todavía no se han sincronizado, así que no se reinician al reiniciar el servicio.

### 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 de los componentes de procesamiento falló                          |
| `unknown`   | Todavía no hay tráfico, comprobaciones de salud ni alertas de calidad                                                                                           |
| `saturated` | Hay tráfico y la cola de peticiones indica saturación                                                                                                           |
| `degraded`  | La tasa de error de 5 minutos (`4xx` y `5xx`) alcanza el umbral, ha habido algún error de servidor (`5xx`) desde el arranque o hay una alerta de calidad activa |
| `idle`      | Hay tráfico y el de los últimos 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.

### Calidad de las operaciones

El bloque `quality` agrega, desde el arranque del servicio, las operaciones completadas con éxito. Es un objeto vacío hasta la primera: `extract` aparece con la primera extracción, `authenticate` con la primera autenticación y `summary` en cuanto el bloque deja de estar vacío.

```json
{
  "quality": {
    "extract": {
      "samples": 12,
      "measuredSamples": 12,
      "overallQuality": { "samples": 12, "avg": 81.5, "min": 42.0, "max": 96.0 },
      "nfiq2": { "samples": 12, "avg": 74.2, "min": 38.0, "max": 91.0 }
    },
    "authenticate": {
      "samples": 7,
      "positive": 6,
      "negative": 1,
      "similarity": { "samples": 7, "avg": 71.3, "min": 8.5, "max": 97.0 }
    },
    "summary": {
      "lowQualitySamples": 1,
      "lowQualitySince": "2026-09-30T09:58:12Z"
    }
  }
}
```

| Campo                                            | Descripción                                                                                                                     |
| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------- |
| `extract.samples`                                | Extracciones correctas agregadas                                                                                                |
| `extract.measuredSamples`                        | Extracciones que devolvieron `quality` o `qualityDetails.nfiq2`. Las extracciones con `"includeQuality": false` no cuentan aquí |
| `extract.overallQuality`, `extract.nfiq2`        | Resumen (`samples`, `avg`, `min`, `max`) de `quality` y de `qualityDetails.nfiq2`                                               |
| `authenticate.samples`                           | Autenticaciones correctas agregadas                                                                                             |
| `authenticate.positive`, `authenticate.negative` | Autenticaciones por resultado                                                                                                   |
| `authenticate.similarity`                        | Resumen de `similarity`                                                                                                         |
| `summary.lowQualitySamples`                      | Extracciones con `quality` o `qualityDetails.nfiq2` por debajo de 50                                                            |
| `summary.lowQualitySince`                        | Instante de la primera extracción de baja calidad desde el arranque; solo aparece cuando hay alguna                             |

Un resultado `Negative` de `/authenticate` no es una muestra de baja calidad: es una comparación correcta entre dos huellas distintas, así que no activa la alerta `LOW_QUALITY` ni degrada la clasificación del servicio, aunque venga acompañado de una similitud baja.

### Alertas

| `code`               | `severity` | `message`                                          |
| -------------------- | ---------- | -------------------------------------------------- |
| `LICENSE_EXPIRED`    | `critical` | `License is invalid or expired`                    |
| `ENGINE_UNAVAILABLE` | `critical` | `Fingerprint 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 fingerprint 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 la última llamada a `/health` no pudo comprobar los componentes de procesamiento; su `since` es el instante de esa comprobación.
* `HIGH_ERROR_RATE` se activa cuando la tasa de error de 5 minutos, que incluye las respuestas `4xx` y `5xx`, alcanza `metrics_error_rate_warning`.
* `SATURATED` e `IDLE` acompañan a las clasificaciones `saturated` e `idle`.
* `LOW_QUALITY` se activa cuando al menos 20 extracciones han devuelto una cifra de calidad, al menos el 20 % de ellas tuvo `quality` o `nfiq2` por debajo de 50 y la última de esas extracciones de baja calidad ocurrió dentro de la ventana `metrics_quality_alert_window_seconds`. Su `since` es el inicio del episodio de baja calidad vigente.

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/finger/installation/installation_instructions.md).

### Exposición Prometheus

`GET /api/v1/finger/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_finger_`:

| 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}`                                                                                                                                                 | 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_finger_service_info{name,version,role,path_base,schema_version}`                                                                                                                                                                                                                   | gauge   | Bloque `service`, siempre con valor `1`                                                                         |
| `facephi_finger_configured_threads`                                                                                                                                                                                                                                                         | gauge   | Hilos configurados (`service.configuredThreads`)                                                                |
| `facephi_finger_snapshot_timestamp_seconds`                                                                                                                                                                                                                                                 | gauge   | Instante del snapshot (`timestamp`)                                                                             |
| `facephi_finger_initialized`, `facephi_finger_license_valid`                                                                                                                                                                                                                                | gauge   | Bloque `state`: arranque y licencia                                                                             |
| `facephi_finger_license_info{status_code}`                                                                                                                                                                                                                                                  | gauge   | `state.license.statusCode`, siempre con valor `1`                                                               |
| `facephi_finger_state_classification{classification}`                                                                                                                                                                                                                                       | gauge   | `state.classification`: vale `1` en el valor vigente y `0` en el resto                                          |
| `facephi_finger_engine_healthy`, `facephi_finger_engine_last_health_known`, `facephi_finger_engine_last_health_check_timestamp_seconds`                                                                                                                                                     | gauge   | Bloque `state.engine`                                                                                           |
| `facephi_finger_engine_service_configured{service}`                                                                                                                                                                                                                                         | gauge   | Una serie por cada servicio de procesamiento configurado en el nodo, siempre con valor `1`                      |
| `facephi_finger_usage_counters_enabled`                                                                                                                                                                                                                                                     | gauge   | `usage.enabled`                                                                                                 |
| `facephi_finger_usage_operations_total{operation}`, `facephi_finger_usage_total`                                                                                                                                                                                                            | counter | Bloque `usage`; `operation` vale `extract` o `authenticate`                                                     |
| `facephi_finger_quality_samples{operation}`, `facephi_finger_quality_measured_samples{operation="extract"}`                                                                                                                                                                                 | gauge   | Muestras del bloque `quality`                                                                                   |
| `facephi_finger_quality_score_samples{operation,metric}`, `facephi_finger_quality_score_avg{operation,metric}`, `facephi_finger_quality_score_min{operation,metric}`, `facephi_finger_quality_score_max{operation,metric}`                                                                  | gauge   | Resúmenes de `quality`: `metric` vale `overallQuality` o `nfiq2` en `extract`, y `similarity` en `authenticate` |
| `facephi_finger_authenticate_results_total{result}`                                                                                                                                                                                                                                         | counter | Reparto `positive`/`negative` de `quality.authenticate`                                                         |
| `facephi_finger_low_quality_samples`, `facephi_finger_low_quality_since_timestamp_seconds`                                                                                                                                                                                                  | gauge   | Bloque `quality.summary`                                                                                        |
| `facephi_finger_alert_active{code,severity}`, `facephi_finger_alert_since_timestamp_seconds{code,severity}`                                                                                                                                                                                 | gauge   | Bloque `alerts`: vale `1` en las alertas activas y `0` en el resto                                              |

Notas de la exposición:

* Solo los totales globales, las series de `saturation` sin etiquetas y las series `facephi_finger_*` 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 operación.
* Las series `facephi_finger_quality_score_*` de un resumen solo aparecen cuando el snapshot JSON lo incluye.
* 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.license.statusMessage`, `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

### Extraer una plantilla

```bash
curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"image":"<base64_fingerprint_image>","position":"RightIndex","options":{"scanType":"Plain"}}' \
  http://localhost:6982/api/v1/finger/extract
```

**Respuesta:**

```json
{
  "fingerTemplate": "<base64_generated_template>",
  "position": "RightIndex",
  "scanType": "Plain",
  "quality": 88,
  "qualityDetails": {
    "nfiq2": 79.0,
    "nfiq": 1.0,
    "score": 88.0
  },
  "sdkDuration": 123,
  "queueDuration": 2
}
```

### Autenticar dos huellas

```bash
curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"image1":"<base64_probe_image>","fingerTemplate2":"<base64_gallery_template>","position":"RightIndex","options":{"authThreshold":20}}' \
  http://localhost:6982/api/v1/finger/authenticate
```

**Respuesta:**

```json
{
  "authStatus": "Negative",
  "similarity": 6.35,
  "sdkDuration": 128,
  "queueDuration": 1
}
```

### 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 servidor REST 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/finger/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/finger/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/finger/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 parámetros de conexión con los componentes de procesamiento (`engine_url`, `engine_connection_timeout`, `engine_request_timeout`, `engine_max_retries`, `engine_retry_delay`, `engine_verify_ssl` y `engine_verbose`, únicamente en los nodos `master`) y los umbrales `metrics_*` con los que `GET /api/v1/finger/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/finger/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":"{\"engine_request_timeout\":45000,\"metrics_error_rate_warning\":0.1}"}' \
  http://localhost:6982/api/v1/finger/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"
}
```

### 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 con los componentes internos de procesamiento (ms)                                                                                    |
| `engine_request_timeout`           | 60000                   | Timeout de solicitud a los componentes internos de procesamiento (ms)                                                                                     |
| `engine_max_retries`               | 3                       | Reintentos de una petición a los componentes de procesamiento que no obtiene respuesta (error de conexión o timeout) o que recibe un `5xx`, `408` o `429` |
| `engine_retry_delay`               | 1000                    | Retardo inicial entre reintentos en ms; se duplica en cada reintento                                                                                      |
| `engine_verify_ssl`                | false                   | Verifica los certificados SSL de la comunicación con los componentes de procesamiento                                                                     |
| `engine_verbose`                   | false                   | Habilita los logs detallados de la comunicación con los componentes de procesamiento                                                                      |
| `engine_pool_size`                 | 4                       | Tamaño del pool de conexiones                                                                                                                             |
| `engine_url`                       | `http://localhost:8080` | URL base de los componentes internos de procesamiento                                                                                                     |

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), las variables de entorno que sobrescriben cada uno y las variables de topología del clúster (`FACEPHI_FINGER_ENGINE_ROLE`, `FACEPHI_FINGER_ENGINE_SERVICES` y `FACEPHI_FINGER_ENGINE_MASTER`) se describen en la [guía de instalación](/sdks/backend-sdk/finger/installation/installation_instructions.md).

## Descripción general de la arquitectura

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

      Client -->|HTTP/REST| FingerService

      subgraph FingerService["Facephi Finger Service"]
        Components["• Licencia<br/>• Endpoints<br/>• Validación del contrato público<br/>• Procesamiento de huella dactilar<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 FingerService fill:#111111,stroke:#333,stroke-width:2px,color:#000
      style Components fill:#60a5fa,stroke:#333,stroke-width:2px,color:#000
```

La documentación y los payloads públicos mantienen intencionadamente abstraído el runtime interno de procesamiento: las integraciones deben apoyarse únicamente en el contrato público documentado.

## Documentación

* [Guía de instalación](/sdks/backend-sdk/finger/installation/installation_instructions.md)
* [Licenciamiento offline con servidor local](/sdks/backend-sdk/finger/installation/offline_licensing.md)
* [Especificaciones técnicas](/sdks/backend-sdk/finger/technical_documentation/technical_specifications.md)
* [Referencia de la API](/sdks/backend-sdk/finger/technical_documentation/api_reference.md)
* [Registro de cambios](/sdks/backend-sdk/finger/changelog.md)

## Soporte

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