# Comprobar una clave

Si la clave funciona, qué puede hacer hoy en cada módulo, qué llamadas se le rechazan y quién arregla cada cosa, con una petición que no toca ningún dato.

Con la propia clave, UTILIA OS te dice si funciona, qué puede hacer hoy en cada módulo, qué llamadas se le han rechazado en las últimas 24 horas, qué le falta y quién lo arregla. Es la primera llamada que conviene hacer al empezar una integración y la que más ayuda a un asistente de IA que programa contra la API: no escribe nada, no necesita ningún permiso y responde aunque la clave no pueda hacer nada más.

En el panel, la ficha de cada clave en **Ajustes › Claves de API** enseña lo mismo en su sección **Comprobar**, calculado en el mismo sitio.

## La petición

**`GET /api/v2/organization-api-keys/current/diagnostics`**

Veredicto, permisos efectivos por módulo, plan, estado de la persona responsable, cupo, llamadas rechazadas en 24 horas y hallazgos con quién los arregla. Solo responde a una clave; con la sesión de una persona, 403 API_KEY_REQUIRED.

### curl

```bash
curl 'https://os.utilia.ai/api/v2/organization-api-keys/current/diagnostics' \
  -H "Authorization: Bearer $UTILIA_API_KEY"
```

### fetch

```javascript
// Node.js 18 o posterior, desde tu servidor.
const res = await fetch('https://os.utilia.ai/api/v2/organization-api-keys/current/diagnostics', {
  headers: { Authorization: `Bearer ${process.env.UTILIA_API_KEY}` },
});

const body = await res.json();
if (!res.ok) {
  throw new Error(`${body.errorCode}: ${body.message}`);
}
console.log(body.verdict.title);
for (const finding of body.findings) {
  console.log(`[${finding.severity}] ${finding.title}: ${finding.detail}`);
}
```

### python

```python
import os
import requests

res = requests.get(
    "https://os.utilia.ai/api/v2/organization-api-keys/current/diagnostics",
    headers={"Authorization": f"Bearer {os.environ['UTILIA_API_KEY']}"},
    timeout=30,
)
res.raise_for_status()
body = res.json()
print(body["verdict"]["title"])
for finding in body["findings"]:
    print(f"[{finding['severity']}] {finding['title']}: {finding['detail']}")
```

- **Solo habla de la clave que llama** y de su organización. Una clave no puede comprobar otras claves: `GET /api/v2/organization-api-keys/:id/diagnostics` es la ruta del panel y con una clave responde `403 API_KEY_ROUTE_NOT_AVAILABLE`.
- **Cuenta como una petición más** de tu cupo por hora.
- **Si la clave no llega a autenticarse** (revocada, caducada, sin persona responsable activa o sin Integraciones en el plan), lo que recibes es el error de la propia autenticación, con su `errorCode`: lo explica la tabla de [Errores de una petición](https://os.utilia.ai/docs/developers/organization-api-keys/first-request#errores-de-una-petición). Esos casos sí los enseña la ficha de la clave en el panel.

## La respuesta

```json
{
  "schemaVersion": 1,
  "generatedAt": "2026-10-21T10:30:00.000Z",
  "verdict": {
    "status": "attention",
    "title": "La clave funciona, pero hay algo que revisar",
    "detail": "La clave caduca pronto."
  },
  "key": {
    "id": "cm2k8x0ab0001qz7f3d4e5f6g",
    "name": "Cuadro de mando de ventas",
    "keyPrefix": "utk_Ab12Cd34",
    "expiresAt": "2026-10-24T22:59:59.999Z",
    "previousKeyExpiresAt": null,
    "usedPreviousKey": false,
    "rateLimitPerHour": 1000,
    "rateLimitRemaining": 987,
    "rateLimitResetAt": "2026-10-21T11:00:00.000Z"
  },
  "organization": { "name": "Ejemplo SL", "status": "active", "kind": "company" },
  "responsible": { "state": "active" },
  "plan": { "integrationsEnabled": true, "modules": ["crm", "finance", "integrations"] },
  "permissions": {
    "mode": "RESTRICTED",
    "byModule": [
      { "module": "crm", "label": "CRM", "permissions": ["CLIENTS_VIEW", "CONTACTS_VIEW"], "inPlan": true },
      { "module": "marketing", "label": "Marketing", "permissions": [], "inPlan": false }
    ]
  },
  "findings": [
    {
      "code": "PERMISSIONS_OUT_OF_PLAN",
      "severity": "warning",
      "fixedBy": "organization",
      "title": "Parte de lo marcado en la clave no está en el plan",
      "detail": "La clave tiene marcado Marketing, que el plan no incluye. Las peticiones a esos módulos se rechazan con MODULE_NOT_ENABLED; lo demás funciona. Lo marcado no se borra: vuelve a valer si el plan lo incluye.",
      "docsUrl": "https://os.utilia.ai/docs/developers/organization-api-keys/diagnostics#permissions-out-of-plan"
    },
    {
      "code": "KEY_EXPIRES_SOON",
      "severity": "warning",
      "fixedBy": "organization",
      "title": "La clave caduca pronto",
      "detail": "Caduca en 3 días (expiresAt). Una clave caducada no se renueva: para que no se corte, alarga su caducidad desde su ficha en Ajustes › Claves de API, o crea otra y cámbiala en tu programa antes de esa fecha.",
      "docsUrl": "https://os.utilia.ai/docs/developers/organization-api-keys/diagnostics#key-expires-soon"
    }
  ]
}
```

| Nombre | Tipo | Obligatorio | Descripción |
| - | - | - | - |
| `schemaVersion` | `1` | Sí | Versión de la forma. Añadir campos no la cambia; cambiar o quitar uno, sí. |
| `verdict.status` | `'ok' \| 'attention' \| 'failing'` | Sí | Funciona, funciona pero hay algo que revisar, o no funciona (del todo). |
| `key.usedPreviousKey` | `boolean` | Sí | true si esta petición entró con la clave anterior de una renovación en transición. |
| `key.rateLimitRemaining` | `number \| null` | Sí | Peticiones que le quedan a la clave en la hora en curso; null si no se puede saber en ese momento. |
| `organization.status` | `'active' \| 'suspended' \| 'cancelled'` | Sí | Suspendida o de baja, las consultas siguen funcionando y las escrituras se rechazan. |
| `organization.kind` | `'company' \| 'developer' \| 'sandbox'` | Sí | Empresa, cuenta de desarrollador de la consola o empresa de prueba. Con developer, plan.modules va vacío y los módulos son developer-apps y developer-sandboxes: lo explica Claves de API de la cuenta, en la documentación de la consola. |
| `responsible.state` | `'active' \| 'inactive' \| 'missing'` | Sí | Solo el estado de la persona en cuyo nombre actúa la clave: nunca su nombre ni su correo. |
| `plan.modules` | `string[]` | Sí | Aplicativos que incluye el plan de la organización. |
| `permissions.mode` | `'ALL' \| 'READ_ONLY' \| 'RESTRICTED'` | Sí | Todos los permisos, Solo lectura o Restringido. |
| `permissions.byModule` | `object[]` | Sí | Por módulo, los permisos que la clave tiene hoy: los de la persona responsable, recortados a lo marcado en la clave y al plan. Con inPlan: false, un módulo marcado que el plan ya no incluye. |
| `findings` | `object[]` | Sí | Lo que hay que saber, de más a menos grave. Cada uno con code, severity, fixedBy, title, detail y docsUrl. |

### Cómo se lee el veredicto

- **`ok`**: la clave funciona y no hay nada que revisar. Puede venir con hallazgos `info`, que no piden nada.
- **`attention`**: funciona, pero hay algo que revisar antes de que falle, como una caducidad cercana o la clave anterior todavía en uso.
- **`failing`**: hay al menos un hallazgo `blocking`. El título dice «La clave no funciona», salvo cuando lo único que bloquea es que la organización está suspendida o de baja: entonces dice «La clave solo puede consultar», porque las lecturas siguen funcionando.

`fixedBy` dice quién lo arregla, sin culpar a nadie: `developer` (quien programa la integración), `organization` (quien gestiona las claves o el plan de tu organización) o `utilia`.

## Llamadas rechazadas

Desde que UTILIA OS sabe qué clave es (la encuentra por su huella), cada respuesta con estado `4xx` o `5xx` cuenta: también las que corta la propia comprobación de la clave (persona responsable inactiva, plan, cupo, clave revocada o caducada) y las de después (ruta que no admite claves, permiso que falta, módulo fuera del plan, organización suspendida, datos no válidos). El diagnóstico las junta en el hallazgo [`rejected-calls`](#rejected-calls), con las tres más frecuentes y el `requestId` de la última.

- **Se agrupan por la ruta plantilla**, `/api/crm/clients/:id`, nunca por la dirección con sus valores, y no se guarda ni la IP ni el cuerpo.
- **No cuentan** las peticiones con una clave que no existe, con la anterior de una renovación que ya dejó de valer ni las que frena el límite de intentos fallidos por IP: en esos casos no se sabe de qué clave son.
- **Un 404 suelto es normal** (buscar algo que no existe): los 404 no cuentan para que el hallazgo pase de `info` a `warning`, que pasa con 5 rechazos de otro tipo en 24 horas.

## Con un asistente de IA

Si un asistente de IA programa tu integración, dale esta instrucción antes de empezar. La clave va en la variable de entorno `UTILIA_API_KEY` del servidor donde se ejecuta la orden: nunca la pegues en el chat ni la escribas en el código.

```text
Antes de escribir código contra UTILIA OS, comprueba la clave ejecutando en el servidor:

curl 'https://os.utilia.ai/api/v2/organization-api-keys/current/diagnostics' -H "Authorization: Bearer $UTILIA_API_KEY"

- Lee verdict.status y la lista findings.
- Programa solo contra los módulos y permisos de permissions.byModule.
- Si hay un hallazgo con severity "blocking" y fixedBy "organization" o "utilia",
  para y dime su title y detail: no se arregla cambiando el código.
- Si una llamada falla, vuelve a pedir el diagnóstico: el hallazgo rejected-calls
  dice qué ruta y qué código fallan, y su docsUrl explica cómo arreglarlo.
- Nunca me pidas la clave ni la escribas en ningún fichero.
```

## Desde el panel

La sección **Comprobar** de la ficha de la clave enseña el mismo veredicto y los mismos hallazgos, dice quién arregla cada uno y, si puedes hacerlo tú, te deja hacerlo desde ahí: cambiar los permisos o la caducidad, ponerla a nombre de otra persona o ir a Mi plan. Lleva también la orden de arriba, lista para copiar.

**`GET /api/v2/organization-api-keys/:id/diagnostics`**

El mismo diagnóstico, para el panel y la API de gestión. Con la sesión de una persona con «Ver las claves de API» o «Gestionar las claves de API»; una clave de otra organización responde 404.

Parámetro de ruta: `id`.

Desde el panel aparecen además los casos en los que la clave no llega a autenticarse (revocada, caducada, sin persona responsable activa o sin Integraciones en el plan), y `key.usedPreviousKey` vale siempre `false`: no hay ninguna petición con la clave detrás.

## Hallazgos

Cada hallazgo tiene su apartado, con el ancla que usa su `docsUrl`: el código en minúsculas y con guiones (`KEY_EXPIRES_SOON` es `#key-expires-soon`). Debajo del código van su gravedad habitual y quién lo arregla. La gravedad es `blocking` (sin esto falla lo que intentas), `warning` (funciona, pero hay que revisar algo), `optional` o `info`.

## Hallazgos: la clave

### `org-api-keys-disabled`

**`ORG_API_KEYS_DISABLED`** · Bloquea · Lo arregla UTILIA

Las claves de API de la organización no están activadas en este servidor y toda petición con una clave se rechaza con `API_KEY_UNAVAILABLE`. No hay nada que cambiar en la clave: vuelve a funcionar cuando UTILIA las active. Solo se ve desde el panel.

### `key-revoked`

**`KEY_REVOKED`** · Bloquea · Lo arregla la organización

La clave está revocada y toda petición se rechaza con `API_KEY_REVOKED`. Una clave revocada no vuelve a valer: hay que crear otra. Si el diagnóstico enseña además llamadas rechazadas, algún programa sigue usándola. Solo se ve desde el panel.

### `key-expired`

**`KEY_EXPIRED`** · Bloquea · Lo arregla la organización

La clave ha caducado y toda petición se rechaza con `API_KEY_EXPIRED`. Una clave caducada no se renueva ni se alarga: hay que crear otra. Solo se ve desde el panel.

### `key-expires-soon`

**`KEY_EXPIRES_SOON`** · Aviso · Lo arregla la organización

Faltan menos de 7 días para la caducidad (`expiresAt`). Para que no se corte, quien gestiona las claves alarga la caducidad desde la ficha de la clave o crea otra, y tú la cambias en tu programa antes de esa fecha. Los dos caminos están en [Evitar que se corte](https://os.utilia.ai/docs/developers/organization-api-keys/expiry-renewal-revocation#evitar-que-se-corte).

### `key-previous-in-use`

**`KEY_PREVIOUS_IN_USE`** · Aviso · Lo arreglas tú

La clave se renovó y todavía llegan peticiones con la anterior: la propia petición del diagnóstico (`key.usedPreviousKey: true`) o alguna de las últimas 24 horas. La anterior deja de valer en `previousKeyExpiresAt`; después, sus peticiones se rechazan con `API_KEY_INVALID`. Cambia la clave por la nueva en la configuración de todos tus servidores antes de esa fecha.

## Hallazgos: persona responsable, organización y plan

### `responsible-inactive`

**`RESPONSIBLE_INACTIVE`** · Bloquea · Lo arregla la organización

La persona en cuyo nombre actúa la clave está dada de baja o bloqueada, o la clave no tiene ninguna (`responsible.state` vale `inactive` o `missing`). Toda petición se rechaza con `API_KEY_RESPONSIBLE_INACTIVE` hasta que quien gestiona las claves la ponga a nombre de otra persona. Solo se ve desde el panel.

### `tenant-suspended`

**`TENANT_SUSPENDED`** · Bloquea si la clave escribe; aviso si solo consulta · Lo arregla la organización

La organización está suspendida. Las consultas siguen funcionando, pero toda petición que crea o cambia datos se rechaza con `TENANT_SUSPENDED` hasta que la organización regularice su suscripción en Mi plan.

### `tenant-cancelled`

**`TENANT_CANCELLED`** · Bloquea si la clave escribe; aviso si solo consulta · Lo arregla la organización

La organización está dada de baja. Las consultas siguen funcionando y las escrituras se rechazan con `TENANT_CANCELLED`. Para volver a operar, la organización tiene que recuperar su cuenta escribiendo a <info@utilia.ai>.

### `integrations-not-in-plan`

**`INTEGRATIONS_NOT_IN_PLAN`** · Bloquea · Lo arregla la organización

El plan no incluye Integraciones y toda petición con una clave se rechaza con `MODULE_NOT_ENABLED`. La clave no se pierde: vuelve a funcionar en cuanto el plan lo incluya. Solo se ve desde el panel.

### `no-effective-permissions`

**`NO_EFFECTIVE_PERMISSIONS`** · Bloquea · Lo arregla la organización

Lo marcado en la clave, los permisos de su persona responsable y el plan no tienen nada en común: la clave solo puede comprobarse a sí misma y cualquier otra ruta se rechaza con `API_KEY_PERMISSION_NOT_GRANTED` o `API_KEY_RESPONSIBLE_PERMISSION_MISSING`. Se arregla ampliando lo que puede hacer la clave o los permisos de su persona responsable, como explica [Permisos de una clave](https://os.utilia.ai/docs/developers/organization-api-keys/permissions).

### `permissions-out-of-plan`

**`PERMISSIONS_OUT_OF_PLAN`** · Aviso · Lo arregla la organización

La clave tiene marcados módulos que el plan ya no incluye (los de `permissions.byModule` con `inPlan: false`). Las peticiones a esos módulos se rechazan con `MODULE_NOT_ENABLED` y lo demás funciona. Lo marcado no se borra: vuelve a valer si el plan lo incluye. Solo sale con el modo Restringido, porque en los otros dos modos la clave no marca módulos a mano.

## Hallazgos: llamadas

### `rejected-calls`

**`REJECTED_CALLS`** · Aviso a partir de 5 rechazos que no son 404; si no, información · Lo arregla quien corresponda al rechazo más frecuente

En las últimas 24 horas se han rechazado llamadas de la clave. El texto nombra las tres más frecuentes, con su estado, su `errorCode`, el método y la ruta, y el `requestId` de la última, que es lo que pide soporte. `fixedBy` sale del rechazo más frecuente: la organización si es un permiso, el plan, la persona responsable o la cuenta; UTILIA si es un fallo del servidor; y tú en el resto. Qué significa cada código y qué hacer está en [Errores de una petición](https://os.utilia.ai/docs/developers/organization-api-keys/first-request#errores-de-una-petición); qué se cuenta y qué no, en [Llamadas rechazadas](#llamadas-rechazadas).

## Siguiente paso

- [Hacer peticiones con una clave](https://os.utilia.ai/docs/developers/organization-api-keys/first-request): Dónde va la clave, qué rutas puedes llamar y qué se comprueba en cada petición.
- [Permisos de una clave](https://os.utilia.ai/docs/developers/organization-api-keys/permissions): Qué permite cada modo y qué queda fuera de cualquier clave.
