> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fhiron.cl/llms.txt
> Use this file to discover all available pages before exploring further.

# API reference

> Contrato de POST /api/validate, códigos HTTP, cuota y rate limit.

## Endpoint

```http theme={null}
POST https://fhiron.cl/api/validate
```

## Headers de request

| Header                     | Requerido | Descripción                                                                                |
| -------------------------- | --------- | ------------------------------------------------------------------------------------------ |
| `Content-Type`             | sí        | Debe ser `application/json`.                                                               |
| `X-API-Key`                | sí        | API key de un tenant activo.                                                               |
| `Idempotency-Key`          | no        | UUID generado por el cliente. Reutilízalo solo al reintentar exactamente el mismo request. |
| `X-Fhiron-CL-Core-Version` | no        | Versión solicitada. La API informa la versión efectivamente resuelta.                      |
| `X-FHIR-Profile`           | no        | URL canónica del perfil que se quiere evaluar.                                             |

El body es el recurso FHIR R4 crudo. No se envuelve en `resource`, `Parameters`
ni `OperationOutcome`.

```bash theme={null}
curl https://fhiron.cl/api/validate \
  --request POST \
  --header "Content-Type: application/json" \
  --header "X-API-Key: $FHIRON_API_KEY" \
  --header "Idempotency-Key: 00000000-0000-4000-8000-000000000001" \
  --data @patient.json
```

## Respuesta compatible

Inspect conserva `ValidateResponse` como contrato público. Un recurso válido
responde `200` y uno inválido responde `422`; ambos usan el mismo shape.

```json theme={null}
{
  "valid": false,
  "errors": [
    "Patient.name es obligatorio (mínimo family + given). CL Core CorePacienteCl."
  ],
  "warnings": [],
  "profile": null,
  "resourceType": "Patient",
  "issues": [
    {
      "code": "cl-patient-02",
      "severity": "error",
      "path": "Patient.name",
      "message": "Patient.name es obligatorio (mínimo family + given). CL Core CorePacienteCl."
    }
  ]
}
```

`issues[]` agrega, cuando corresponde, `why`, `profileUrl`, `docsUrl`,
`suggestion`, `example`, `entryIndex` y un `quickFix` con `jsonPointer` RFC
6901\. `engineDegraded: true` indica que el motor remoto no completó la
validación; esa llamada no descuenta cuota.

`Fhiron-Request-ID` devuelve el UUID efectivo. Si el cliente no envía
`Idempotency-Key`, Fhiron genera uno; para que un retry sea idempotente el
cliente debe generar y conservar el UUID antes del primer intento.

## Códigos HTTP

| Código | Significado                                                        |
| ------ | ------------------------------------------------------------------ |
| `200`  | Validación ejecutada y recurso válido.                             |
| `400`  | JSON inválido o `resourceType` ausente/inválido.                   |
| `401`  | Falta `X-API-Key`.                                                 |
| `403`  | API key inválida, revocada o tenant inactivo.                      |
| `409`  | La misma `Idempotency-Key` se reutilizó para un request diferente. |
| `413`  | Bundle con más de 200 entries en un único request.                 |
| `415`  | `Content-Type` distinto de `application/json`.                     |
| `422`  | Validación ejecutada y recurso inválido; leer `ValidateResponse`.  |
| `429`  | Cuota mensual agotada o límite corto de ráfaga excedido.           |
| `502`  | El validador respondió con un error o formato inesperado.          |
| `503`  | El validador no está configurado o no está disponible.             |

Los `5xx` son fallas de infraestructura, no evidencia de que el recurso sea
válido o inválido. Reintenta con backoff y un máximo acotado de intentos,
conservando la misma `Idempotency-Key` para el mismo body, perfil y versión.

## Cuota mensual y ráfaga

Las respuestas `200` y `422` separan dos límites distintos:

| Headers                                                              | Qué representan                                                                        |
| -------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| `Fhiron-Quota-Limit`, `Fhiron-Quota-Remaining`, `Fhiron-Quota-Reset` | Cuota mensual del tenant. El reset es Unix epoch en segundos.                          |
| `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`    | Ventana corta de ráfaga por API key. `Reset` son segundos hasta reiniciar esa ventana. |

Un plan sin tope devuelve `unlimited` en los dos headers mensuales de límite y
remanente. Un `429` por ráfaga incluye `Retry-After`; un `429` por cuota incluye
los headers `Fhiron-Quota-*`. El remanente puede ser mayor que cero si un Bundle
necesita mas unidades que las disponibles; en ese caso no hay consumo parcial.
Tras un downgrade o fin de trial, el contador histórico puede superar el nuevo
límite: la API responde `429` con remanente cero, sin convertirlo en un `503`.
Cuando `engineDegraded: true`, PostgreSQL no decide consumo y la respuesta omite
todos los headers `Fhiron-Quota-*`; nunca estima un remanente desde el plan.

## Efectos operacionales

Inspect no persiste el body clínico recibido. Una validación procesada sí:

* consume una unidad para un recurso individual y una por cada `Bundle.entry`
  (minimo 1, maximo 200 por request);
* registra metadatos del resultado y duración, sin el body;
* puede disparar el webhook configurado y la alerta de uso del tenant.

Cuota y `validations_log` se confirman en una sola transacción. El UUID queda
asociado a un fingerprint HMAC opaco, no al body. Un replay con la misma clave y
request devuelve el snapshot de cuota original y no vuelve a cobrar, registrar
ni emitir `validation.complete`; la ruta vuelve a validar en HAPI y no cachea ni
reproduce el body HTTP. El webhook incluye `validation_request_id` para
correlación, sin persistir errores o warnings en el outbox. Usar esa clave con
otro request responde `409`.
Las alertas del 80% usan un outbox sin PII y solo se marcan enviadas después del
ACK del proveedor; el cron autenticado reintenta un batch acotado.

## Contrato descargable

<CardGroup cols={2}>
  <Card title="OpenAPI 3.1" icon="brackets-curly" href="/downloads/fhiron-openapi.json">
    Fuente canónica de las rutas públicas implementadas.
  </Card>

  <Card title="Colección Postman" icon="flask" href="/downloads/fhiron-postman.json">
    Generada desde OpenAPI, sin API keys ni datos clínicos reales.
  </Card>

  <Card title="Fixtures sintéticos" icon="file-code" href="/downloads/fixtures/patient-valid.json">
    Patient de ejemplo; también hay un fixture inválido.
  </Card>

  <Card title="VS Code REST Client" icon="code" href="/downloads/fhiron.http">
    Requests reproducibles usando `FHIRON_API_KEY` desde el entorno.
  </Card>
</CardGroup>

Los artefactos se regeneran de forma determinística desde OpenAPI y el check de
drift falla si un archivo descargable quedó desactualizado.
