# Integrar con un asistente de IA

La instrucción para pegar en tu asistente de IA y las reglas para que integre con la API sin preguntar a nadie ni romper nada.

Pega esto en tu asistente de IA (Claude, ChatGPT, Cursor, Copilot...) al empezar. La clave va en una variable de entorno de tu equipo o de tu servidor: tu asistente no la necesita en la conversación.

```text
Vas a integrar mi aplicación con la API externa de UTILIA OS.
Sigue https://os.utilia.ai/docs/developers/getting-started/ai-assistants
y consulta la documentación completa en https://os.utilia.ai/llms-full.txt.
La dirección base es https://os.utilia.ai/api y la clave está en la variable
UTILIA_API_KEY: no la escribas nunca en el código, en el repositorio ni en
esta conversación. Antes de programar, llama a
GET /external/v1/diagnostics?format=markdown y sigue lo que diga.
```

Esta página es para escribir código que llama a la API. Para que tu asistente trabaje con los datos de tu organización sin programar, usa [MCP](https://os.utilia.ai/docs/developers/mcp/quickstart).

## Las reglas

Cada operación nueva pasa por el mismo orden. Los tres primeros pasos no escriben nada: repítelos tantas veces como haga falta, dentro de sus cupos. El mismo ciclo, con código en curl, JavaScript y Python, está en [Probar tu integración](https://os.utilia.ai/docs/developers/getting-started/testing-quickstart).

1. **Diagnostica antes de programar.** `GET /external/v1/diagnostics` dice qué puede llamar la clave, qué le falta y quién lo arregla; con `?format=markdown`, en forma de instrucciones. Lee `verdict` (el estado en una línea), después `nextSteps` (haz los de `fixedBy: developer`; los de `organization`, pídeselos a un administrador con el texto tal cual), `access` (no programes contra una operación con `allowed: false` sin resolver su `reason`) y `findings`, cada uno con su `docsUrl` en [Hallazgos del diagnóstico](https://os.utilia.ai/docs/developers/getting-started/testing).
2. **Comprueba cada petición nueva** con `POST /external/v1/diagnostics/check-request` y el cuerpo que vas a mandar. Dice si pasaría, con el mismo `errorCode` que la llamada real, y qué campos descartaría sin avisar (`validation.ignoredFields`): un `201` sin el dato guardado. Lo que tu clave puede llamar, con sus esquemas, está en `GET /external/v1/diagnostics/openapi.json`.
3. **Ensaya sin efectos** cuando la ruta lo permita: `POST /external/v1/invoices/validate` para facturas ([sin emitir nada](https://os.utilia.ai/docs/developers/api/invoice-testing)), `POST /external/v1/diagnostics/run` para las lecturas y los [avisos de prueba](https://os.utilia.ai/docs/developers/getting-started/webhook-testing) para tu servidor. Las escrituras sin ensayo (un ticket, un cliente, un lead) se prueban con `check-request` y después con una llamada real sobre datos de prueba.
4. **Llama de verdad.** Si la ruta mueve dinero o emite un documento, manda una clave de idempotencia (`idempotencyKey`) que salga de tu pedido, nunca una nueva en cada intento, y al emitir facturas pon un tiempo de espera de al menos 30 segundos. Después de cada escritura, mira `X-Utilia-Ignored-Fields` (los SDK avisan solos con `onIgnoredFields` y `on_ignored_fields`).
5. **Lee el error** en este orden: `errorCode`, lo único por lo que decide tu código; `fixedBy` (`developer` lo arreglas tú; `organization`, un administrador, y no se rodea desde el código; `utilia`, nosotros); `docsUrl`, su explicación en [Errores y reintentos](https://os.utilia.ai/docs/developers/getting-started/errors-and-retries); `requiredScopes`, en un `403 INSUFFICIENT_SCOPE`; `retryable` y `Retry-After`, si repetir puede funcionar y cuándo; y `requestId`, para pedir ayuda. Si los fallos se repiten, `GET /external/v1/diagnostics/calls` los agrupa con su arreglo.

Con una clave publicada en una web (con dominios autorizados o usada desde un navegador), el diagnóstico llega recortado, con `credential.publishable: true`, y las llamadas rechazadas, la prueba activa y los avisos de prueba responden `403 DIAGNOSTICS_NOT_AVAILABLE_FOR_PUBLISHABLE_KEY`: se lanzan desde la pestaña **Pruebas** del panel ([por qué](https://os.utilia.ai/docs/developers/getting-started/diagnostics#claves-publicadas-en-una-web)).

Y nunca:

- **Emitas facturas para probar.** La numeración fiscal no tiene vuelta atrás: valida con `/invoices/validate`.
- **Escribas la clave** en el código, en el repositorio, en la conversación ni en la dirección. Va en `UTILIA_API_KEY` y en la cabecera `X-Api-Key`.
- **Uses una clave de API de la organización en el navegador**, ni la de la aplicación en una web sin dominios autorizados: cualquiera podría copiarla.
- **Reintentes una escritura con otra clave de idempotencia**, ni repitas una sin clave sin comprobar antes si llegó a hacerse.
- **Rodees desde el código lo que arregla la organización**: cambiar de ruta para saltarte un permiso o un plan no funciona y deja rastro.
- **Ignores `X-Utilia-Ignored-Fields`** ni los `warnings` de un ensayo.
- **Insistas ante un `429`** sin esperar lo que diga `Retry-After`.
- **Te fíes de un aviso sin verificar su firma**, ni guardes uno con `livemode: false` en tus datos de producción.
- **Llames con la clave de la aplicación a rutas fuera de `/api/external/v1`**: no son públicas y no responderán.

## Qué credencial usar

La API externa (`/api/external/v1`) solo acepta la clave de una aplicación externa. La clave de API de la organización abre otra puerta, y cruzarlas da `401 API_KEY_WRONG_KIND`.

**Dos claves que se confunden**

### Clave de aplicación (X-Api-Key: utl\_... · Recomendado)

Para tu aplicación y sus usuarios: tickets, facturas, cobros, CRM y avisos a tu servidor, por la API externa. Se crea en Ajustes › Creadas por tu empresa. Es la de esta página.

### Clave de la organización (Authorization: Bearer utk\_...)

Para un programa de tu empresa que usa la misma API que la aplicación (`/api/...`), con los permisos de una persona. Se crea en Ajustes › Claves de API y nunca vale desde un navegador.

La clave de aplicación va siempre en la cabecera `X-Api-Key`: en `Authorization` da `401 API_KEY_WRONG_HEADER`, y sola en la dirección (`apiKey`, `api_key`, `x-api-key`...), `401 API_KEY_IN_URL_NOT_ALLOWED`. Si va en la dirección y también en la cabecera, de momento entra, pero el diagnóstico avisa con `KEY_IN_URL`: quítala de la dirección y pide que se renueve. Las demás credenciales están comparadas en [Autenticación con clave de acceso](https://os.utilia.ai/docs/developers/getting-started/authentication) y en [Claves de API de la organización](https://os.utilia.ai/docs/developers/organization-api-keys/overview).

## Las rutas y los SDK

Todas con la clave en `X-Api-Key` y sin permisos de la API. En los SDK, desde la 4.6.0; con una versión anterior, llama a las rutas directamente ([Versiones y compatibilidad](https://os.utilia.ai/docs/developers/sdk/versions#matriz-de-versiones)).

| Ruta | JavaScript | Python |
| - | - | - |
| `GET /external/v1/me` | `sdk.diagnostics.me()` | `sdk.diagnostics.me()` |
| `GET /external/v1/diagnostics` | `sdk.diagnostics.get()` | `sdk.diagnostics.get()` |
| `GET /external/v1/diagnostics?format=markdown` | `sdk.diagnostics.getMarkdown()` | `sdk.diagnostics.get_markdown()` |
| `GET /external/v1/diagnostics/calls` | `sdk.diagnostics.calls()` | `sdk.diagnostics.calls()` |
| `POST /external/v1/diagnostics/check-request` | `sdk.diagnostics.checkRequest()` | `sdk.diagnostics.check_request()` |
| `POST /external/v1/diagnostics/run` | `sdk.diagnostics.run()` | `sdk.diagnostics.run()` |
| `POST /external/v1/diagnostics/webhook-sample` | `sdk.diagnostics.sendWebhookSample()` | `sdk.diagnostics.send_webhook_sample()` |
| `POST /external/v1/diagnostics/webhook-receiver-check` | `sdk.diagnostics.checkWebhookReceiver()` | `sdk.diagnostics.check_webhook_receiver()` |
| `GET /external/v1/diagnostics/openapi.json` | `sdk.diagnostics.openApi()` | `sdk.diagnostics.open_api()` |

Cada ruta, con sus campos y sus cupos, está en [Rutas del diagnóstico](https://os.utilia.ai/docs/developers/getting-started/diagnostics).

## La orden doctor

Hace el diagnóstico completo desde la consola y lo enseña ordenado, con quién arregla cada cosa y sus enlaces. Lee la clave de `UTILIA_API_KEY` y nunca la muestra. Llega con los SDK 4.6.0 ([si ya se puede instalar](https://os.utilia.ai/docs/developers/sdk/versions#lo-que-todavía-no-puedes-instalar)).

### Node

```bash
export UTILIA_API_KEY=...
export UTILIA_BASE_URL=https://os.utilia.ai/api
npx @utilia-os/sdk-js doctor                    # diagnóstico
npx @utilia-os/sdk-js doctor --run              # y prueba activa de solo lectura
npx @utilia-os/sdk-js doctor --check "POST /external/v1/invoices" --body factura.json
npx @utilia-os/sdk-js doctor --json --strict    # para tu asistente o tu CI
```

### Python

```bash
export UTILIA_API_KEY=...
export UTILIA_BASE_URL=https://os.utilia.ai/api
utilia-doctor                                   # diagnóstico
utilia-doctor --run                             # y prueba activa de solo lectura
utilia-doctor --check "POST /external/v1/invoices" --body factura.json
utilia-doctor --json --strict                   # para tu asistente o tu CI
```

Sale con un código que tu asistente o tu integración continua pueden leer:

- **`0`**: no hay nada que bloquee.

- **`1`**: hay hallazgos que bloquean (con `--strict`, también los avisos), una prueba activa que falla o una petición de `--check` que no pasaría. También si el servidor responde que la aplicación está desactivada, sin persona responsable activa o con la organización bloqueada.

- **`2`**: no se pudo comprobar: sin conexión, una clave que no vale, una dirección `http://` que no admite o la orden mal escrita.

- **La clave solo viaja cifrada**: con una dirección `http://`, la orden se niega a mandarla y sale con `2`, salvo que apunte a este mismo equipo (`localhost`, `127.0.0.1` o `::1`). `--allow-insecure-http` la deja pasar con un aviso: solo para un laboratorio aislado, nunca contra un servidor de verdad.

- **Con una clave publicada en una web**, `--run` no lanza la prueba activa: lo explica, no lo cuenta como fallo y, con `--json`, lo anota en `notices`.

- **Con `--json`** saca el diagnóstico tal cual, con `run` y `checkRequest` si los pides: es la forma más cómoda de dárselo a un asistente.

## La documentación en texto

- **`https://os.utilia.ai/llms.txt`**: el índice de la documentación pública, con un enlace por artículo y al texto completo. Es el que citan las instrucciones del diagnóstico.
- **`https://os.utilia.ai/llms-full.txt`**: el texto completo de la documentación para desarrolladores, en Markdown, en un solo fichero.
- **El contrato de tu clave**: `GET /external/v1/diagnostics/openapi.json`, con solo lo que tu clave puede llamar.
- **Copiar › Copiar para tu IA**, en la cabecera de cada artículo, copia ese artículo en Markdown. Está en el ordenador; en el teléfono, la cabecera ofrece **Compartir**.

## Siguiente paso

- [Probar tu integración](https://os.utilia.ai/docs/developers/getting-started/testing-quickstart): Los cinco pasos con código en curl, JavaScript y Python.
- [Errores y reintentos](https://os.utilia.ai/docs/developers/getting-started/errors-and-retries): Qué significa cada errorCode y quién lo arregla.
