# Hacer peticiones con una clave

Dónde va la clave, cómo comprobar que funciona, qué rutas puedes llamar con ella y qué se comprueba en cada petición.

Tu servidor envía la clave en cada petición y llama a las mismas rutas que usa la aplicación, en `https://os.utilia.ai/api`. Cada petición se comprueba de nuevo: si revocas la clave o cambias los permisos de su persona responsable, la petición siguiente ya lo nota. Si todavía no tienes la clave, empieza por [Claves de API de la organización](https://os.utilia.ai/docs/developers/organization-api-keys/overview).

## Dónde va la clave

Envía la clave en la cabecera `Authorization`, con `Bearer` delante, en cada petición:

```http
Authorization: Bearer utk_…
```

No vale en la dirección ni en la cabecera `X-Api-Key`: ahí no autentica. Y solo desde un servidor: si la petición llega desde un navegador, UTILIA OS la rechaza.

## Comprobar que funciona

Para saber que la clave está bien puesta, pregúntale quién es:

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

Datos de la clave que llama: persona responsable, permisos efectivos, caducidad y cupo que le queda esta hora. 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' \
  -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', {
  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);
```

### python

```python
import os
import requests

res = requests.get(
    "https://os.utilia.ai/api/v2/organization-api-keys/current",
    headers={"Authorization": f"Bearer {os.environ['UTILIA_API_KEY']}"},
    timeout=30,
)
res.raise_for_status()
print(res.json())
```

Si la clave funciona, recibes un `200` como este:

```json
{
  "id": "cm2k8x0ab0001qz7f3d4e5f6g",
  "name": "Cuadro de mando de ventas",
  "keyPrefix": "utk_Ab12Cd34",
  "responsibleUser": {
    "id": "cm1z9y8xw0000ab12cd34ef56",
    "firstName": "María",
    "lastName": "García",
    "email": "maria@tuempresa.com"
  },
  "permissionMode": "RESTRICTED",
  "moduleAccess": ["crm:read", "finance:read"],
  "effectivePermissions": [
    { "moduleKey": "crm", "label": "CRM", "read": true, "write": false },
    { "moduleKey": "finance", "label": "Finanzas", "read": true, "write": false }
  ],
  "expiresAt": null,
  "rateLimitPerHour": 1000,
  "rateLimitRemaining": 998,
  "rateLimitResetAt": "2026-10-21T11:00:00.000Z",
  "usedPreviousKey": false,
  "previousKeyExpiresAt": null
}
```

| Nombre | Tipo | Obligatorio | Descripción |
| - | - | - | - |
| `permissionMode` | `'ALL' \| 'READ_ONLY' \| 'RESTRICTED'` | Sí | Todos los permisos, Solo lectura o Restringido. |
| `moduleAccess` | `string[]` | Sí | Solo con RESTRICTED: un acceso por módulo, 'modulo:read' o 'modulo:write'. Vacío en los otros dos modos. |
| `effectivePermissions` | `object[]` | Sí | Lo que la clave puede hacer hoy en cada módulo, después de cruzar los permisos de la persona, lo marcado en la clave y el plan. |
| `expiresAt` | `string \| null` | Sí | Fecha de caducidad en ISO 8601, o null si no caduca. |
| `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. |
| `rateLimitResetAt` | `string` | Sí | Cuándo se renueva el cupo: al empezar la hora siguiente. |
| `usedPreviousKey` | `boolean` | Sí | true si la petición entró con la clave anterior de una renovación que todavía está en transición. |
| `previousKeyExpiresAt` | `string \| null` | Sí | Hasta cuándo vale la clave anterior de una renovación en transición, o null si no hay ninguna. |

Esta ruta cuenta como una petición más de tu cupo. Si `expiresAt` trae una fecha, apúntala: cómo te avisa UTILIA OS antes de que llegue está en [Caducidad, renovación y revocación](https://os.utilia.ai/docs/developers/organization-api-keys/expiry-renewal-revocation#el-aviso-antes-de-que-caduque).

Para saber además si le falta algo, qué llamadas se le han rechazado y quién arregla cada cosa, pide su diagnóstico con `GET /api/v2/organization-api-keys/current/diagnostics`. Lo explica [Comprobar una clave](https://os.utilia.ai/docs/developers/organization-api-keys/diagnostics), con la instrucción para un asistente de IA.

## Llamar a las rutas de la aplicación

Con la clave llamas a las mismas rutas que usa la aplicación. Por ejemplo, con «CRM: leer» lees tus clientes y con «CRM: escribir» das de alta uno nuevo:

```bash
curl 'https://os.utilia.ai/api/crm/clients?limit=20' \
  -H "Authorization: Bearer $UTILIA_API_KEY"

curl -X POST 'https://os.utilia.ai/api/crm/clients' \
  -H "Authorization: Bearer $UTILIA_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"name": "Talleres Rivera"}'
```

El cliente nuevo queda creado por la persona responsable de la clave, y su organización sale siempre de la clave, nunca del cuerpo de la petición.

Ten en cuenta dos cosas al elegir rutas:

- **Son las rutas de la aplicación y cambian con ella.** No tienen la versión ni el contrato estable de la [API externa](https://os.utilia.ai/docs/developers/changelog/versioning) (`/api/external/v1`), y esta documentación no publica su referencia. Si tu programa necesita un contrato que no cambie, usa la API externa con una [aplicación externa](https://os.utilia.ai/docs/developers/getting-started/choose-credential), o las herramientas MCP con el inicio de sesión de una persona.
- **Los SDK oficiales de JavaScript y Python son clientes de la API externa** y no sirven con esta clave. Usa cualquier cliente HTTP, como en los ejemplos.

## Qué se comprueba en cada petición

Cada petición pasa unos controles en orden, y el primero que falla la corta con su código. Los cinco primeros miran la clave:

**Lo que se comprueba de la clave**

1. **¿Llega desde un servidor?** (Cabeceras de la petición): Una petición con Origin o Sec-Fetch-Site viene de un navegador. Si no: 403 API_KEY_BROWSER_NOT_ALLOWED.
2. **¿La clave es válida?** (Authorization: Bearer): Existe, no está revocada y no ha caducado. Durante una renovación vale también la anterior. Si no: 401 API_KEY_INVALID, API_KEY_REVOKED o API_KEY_EXPIRED.
3. **¿Su responsable sigue activa?**: La persona en cuyo nombre actúa la clave sigue activa en la organización. Si no: 403 API_KEY_RESPONSIBLE_INACTIVE.
4. **¿Integraciones está en el plan?** (Plan de la organización): Si no: 403 MODULE_NOT_ENABLED.
5. **¿Le queda cupo esta hora?**: Si no: 429 RATE_LIMIT_EXCEEDED, con Retry-After.

Si la clave pasa, los tres últimos miran la ruta que llamas y a la persona responsable, igual que harían con ella en la aplicación:

**Lo que se comprueba de la ruta**

1. **¿La ruta admite claves?**: Fuera quedan la gestión de cuentas, claves y ajustes y las conexiones en tiempo real. Si no: 403 API_KEY_ROUTE_NOT_AVAILABLE.
2. **¿La clave incluye el permiso?** (Permisos de la clave): Lo que marcaste al crearla. Una escritura necesita un permiso de escritura. Si no: 403 API_KEY_PERMISSION_NOT_GRANTED.
3. **¿Lo puede hacer la persona?** (Permisos y plan): Sus permisos, las filas que puede ver y el plan del módulo. Si no: 403 API_KEY_RESPONSIBLE_PERMISSION_MISSING o MODULE_NOT_ENABLED.

Nada se guarda en memoria entre peticiones: revocar una clave, dar de baja a su responsable o quitarle un permiso surte efecto en la petición siguiente. Qué permite cada modo y qué queda fuera de cualquier clave está en [Permisos de una clave](https://os.utilia.ai/docs/developers/organization-api-keys/permissions).

## Errores de una petición

Todos llegan con la [forma común de los errores](https://os.utilia.ai/docs/developers/getting-started/errors-and-retries#la-forma-de-un-error): decide con `errorCode` y guarda `requestId`. Los de permisos añaden `permissions`, la lista de permisos que faltan.

| errorCode | HTTP | Qué pasa y qué hacer |
| - | - | - |
| `API_KEY_INVALID` | 401 | La clave no existe o está mal copiada. Revisa que vaya entera tras `Authorization: Bearer`. |
| `API_KEY_REVOKED` | 401 | Está revocada. Crea otra. |
| `API_KEY_EXPIRED` | 401 | Ha caducado. Una clave caducada no se renueva: crea otra. |
| `API_KEY_UNAVAILABLE` | 401 | UTILIA OS no ha podido comprobar la clave en ese momento. Reintenta pasados unos minutos. |
| `API_KEY_BROWSER_NOT_ALLOWED` | 403 | La petición viene de un navegador. Llama desde tu servidor. |
| `API_KEY_RESPONSIBLE_INACTIVE` | 403 | La persona responsable ya no está activa. Quien gestiona las claves tiene que nombrar a otra. |
| `API_KEY_ROUTE_NOT_AVAILABLE` | 403 | Esa ruta no admite claves. Hazlo desde UTILIA OS. |
| `API_KEY_STREAMING_NOT_AVAILABLE` | 403 | Las conexiones de eventos en tiempo real no admiten claves. |
| `API_KEY_PERMISSION_NOT_GRANTED` | 403 | La clave no incluye ese permiso. Amplíala o usa otra que lo tenga. |
| `API_KEY_RESPONSIBLE_PERMISSION_MISSING` | 403 | La persona responsable no tiene ese permiso. Se arregla en sus permisos, no en la clave. |
| `API_KEY_ACTION_REQUIRES_SESSION` | 403 | La acción pide la verificación en dos pasos de una persona. Hazla desde UTILIA OS. |
| `MODULE_NOT_ENABLED` | 403 | El plan no incluye Integraciones o el módulo de la ruta. Reintentar no lo arregla. |
| `CAPABILITY_NOT_ENABLED` | 403 | El plan no incluye la capacidad que usa la ruta. |
| `FEATURE_COMING_SOON` | 403 | La función está en «Próximamente». |
| `ORGANIZATION_RESTRICTED` | 403 | La organización está en modo restringido: no salen exportaciones ni descargas masivas. |
| `TENANT_SUSPENDED`, `TENANT_CANCELLED` | 403 | La organización está suspendida o dada de baja. |
| `RATE_LIMIT_EXCEEDED` | 429 | Cupo por hora agotado o demasiadas claves no válidas desde tu IP. Espera lo que diga `Retry-After`. |

De todos ellos, solo merece la pena reintentar `API_KEY_UNAVAILABLE` y `RATE_LIMIT_EXCEEDED`, con espera. Los demás no cambian por repetir la petición.

## Siguiente paso

- [Comprobar una clave](https://os.utilia.ai/docs/developers/organization-api-keys/diagnostics): Si funciona, qué puede hacer hoy, qué llamadas se le rechazan y quién lo arregla.
- [Errores y reintentos](https://os.utilia.ai/docs/developers/getting-started/errors-and-retries): La forma de un error, X-Request-Id y cuándo merece la pena reintentar.
