Rutas del diagnóstico
Referencia de /me, el diagnóstico y su veredicto, las llamadas rechazadas, el contrato de tu clave y las claves publicadas, con sus cupos.
Actualizado el . 14 min de lectura
Incluido en todos los planes. Ver precios
Las rutas que describen tu aplicación, con sus campos, sus cupos y lo que responden con cada clave. Para probar en cinco minutos, empieza por Probar tu integración. Lo que significa cada hallazgo está en Hallazgos del diagnóstico.
Las rutas de un vistazo
| Ruta | Qué te dice | Efecto |
|---|---|---|
GET /external/v1/me | Quién es la aplicación, su clave, su cupo y sus permisos | Ninguno |
GET /external/v1/diagnostics | Qué puede y qué no puede hacer, por qué y el siguiente paso | Ninguno |
GET /external/v1/diagnostics/calls | Las llamadas rechazadas, agrupadas, con su arreglo | Ninguno |
POST /external/v1/diagnostics/check-request | Si una petición pasaría, sin ejecutarla | Ninguno |
POST /external/v1/diagnostics/run | Si cada permiso de lectura funciona de verdad | Lecturas mínimas, sin devolver datos |
POST /external/v1/diagnostics/webhook-sample | Si tu servidor recibe y entiende un aviso | Un aviso de prueba a tu servidor |
POST /external/v1/diagnostics/webhook-receiver-check | Si tu servidor rechaza los avisos falsos | Tres avisos de prueba a tu servidor |
GET /external/v1/diagnostics/openapi.json | El contrato con solo lo que tu clave puede llamar hoy | Ninguno |
check-request y la prueba activa están en Comprobar y ensayar sin escribir, y los dos envíos de avisos, en Probar los avisos a tu servidor. Ninguna ruta escribe datos de tu organización ni emite nada: los avisos de prueba son lo único que sale hacia fuera, y llegan de verdad a tu servidor. La pestaña Pruebas del panel enseña el mismo diagnóstico, calculado en el mismo sitio.
Lo que exigen
- La clave de la aplicación en
X-Api-Keyy ningún permiso de la API: una aplicación sin permisos puede diagnosticarse, que es justo cuando más falta le hace. - Ningún plan, salvo los dos envíos de avisos, que exigen Integraciones: el trabajo del diagnóstico es explicar los bloqueos, también los de plan.
- Con la organización suspendida o de baja responden todas y lo dicen, menos los dos envíos de avisos, que se rechazan con
TENANT_SUSPENDEDoTENANT_CANCELLED. - Con la aplicación desactivada o sin persona responsable activa no responde ninguna: la llamada se corta antes, como cualquier otra, con
403 APP_DISABLEDo403 APP_RESPONSIBLE_INACTIVE, y eldocsUrlde ese error explica qué hacer. Por eso los hallazgosapp-disabled,responsible-missingyresponsible-inactivesolo se ven en la pestaña Pruebas y por MCP, con la operaciónapp_diagnosis. - Con una clave publicada en una web, cuatro rutas no la aceptan: ver Claves publicadas en una web.
Cupos
Cupos del diagnóstico
- Rutas de diagnóstico
- 30al minuto
- Por aplicación, además del cupo por hora de la clave.
- Prueba activa
- 6al minuto
- Por aplicación con la clave y por persona en el panel.
- Avisos de prueba
- 5al minuto
- Un solo cupo entre la clave y el panel.
- Diagnóstico guardado
- 30segundos
Todas cuentan en el cupo por hora de la clave. Al pasar el cupo de la prueba activa o el de los avisos de prueba, la respuesta es 429 DIAGNOSTICS_RATE_LIMITED, con Retry-After y las cabeceras X-RateLimit-Limit, X-RateLimit-Remaining y X-RateLimit-Reset de ese cupo (6 o 5 al minuto, con Remaining a 0), no las del freno general por minuto; el cuerpo trae retryAfterSeconds y retryable: true. Estas dos pruebas no se lanzan sin contarlas: si el contador de su cupo no responde, la respuesta es 503 DIAGNOSTICS_UNAVAILABLE, y basta con repetir en unos minutos.
Quién es tu aplicación
/api/external/v1/meIdentidad mínima y estable de la credencial: aplicación, organización (nombre y estado), clave en uso, cupo por hora y lo que queda, permisos concedidos y versión de la API. Sin permisos de la API.
{
"app": { "id": "ckapp1234567890", "name": "Tienda online", "isActive": true },
"organization": { "name": "Ejemplo SL", "status": "active" },
"credential": {
"keyInUse": "current",
"previousKeyExpiresAt": null,
"rateLimitPerHour": 1000,
"rateLimitRemaining": 987
},
"scopes": ["crm:invoices:read", "crm:invoices:write", "crm:invoices:issue"],
"apiVersion": "v1",
"docsUrl": "https://os.utilia.ai/docs/developers/getting-started/testing"
}Si responde 200, la clave vale y va en la cabecera buena, y ya cuenta como primera llamada correcta.
keyInUse: "previous": llamas con la clave anterior durante una renovación;previousKeyExpiresAtdice hasta cuándo vale.organization.status:active,suspendedocancelled.- Con una clave publicada en una web:
credential.publishable: true, el estado de la organización solo diceactiveoinactive, yscopessolo lista los permisos que esa clave puede usar; los demás se cuentan encredential.scopesOutsideCeiling.
El diagnóstico de tu aplicación
/api/external/v1/diagnosticsQué puede y qué no puede hacer la aplicación, por qué, quién lo arregla y el siguiente paso. Con ?format=markdown devuelve las instrucciones para tu asistente de IA. Sin permisos de la API.
Se calcula en el servidor, con la configuración de la aplicación, el plan de la organización y el tráfico de las últimas 24 horas, y se guarda 30 segundos. Es la misma fuente que pinta el panel: lo que ves en la pestaña Pruebas y lo que lee tu programa no se contradicen.
curl https://os.utilia.ai/api/external/v1/diagnostics \
-H "X-Api-Key: $UTILIA_API_KEY"
# Las instrucciones para tu asistente de IA
curl "https://os.utilia.ai/api/external/v1/diagnostics?format=markdown" \
-H "X-Api-Key: $UTILIA_API_KEY"sdk.diagnostics está en los SDK a partir de la 4.6.0. Con el cliente asíncrono de Python (async with UtiliaSDK(...) as sdk:), cada llamada lleva await. Una respuesta recortada:
{
"schemaVersion": 1,
"generatedAt": "2026-10-21T10:00:00.000Z",
"verdict": {
"status": "attention",
"title": "Falta un paso: la facturación de la aplicación está desactivada",
"detail": "Pide a un administrador de tu organización que active la facturación de la aplicación. Mientras tanto, prueba con POST /external/v1/invoices/validate, que no emite nada."
},
"app": { "id": "ckapp1234567890", "name": "Tienda online", "isActive": true, "purposes": ["billing", "webhooks"] },
"organization": { "name": "Ejemplo SL", "status": "active" },
"responsible": { "state": "active" },
"findings": [
{
"code": "BILLING_DISABLED_WITH_INVOICE_SCOPES",
"severity": "blocking",
"fixedBy": "organization",
"title": "La facturación de la aplicación está desactivada",
"detail": "Tiene permisos de facturas, pero cada factura que intente emitir se rechaza con BILLING_NOT_ENABLED. Pide a un administrador de tu organización que active la facturación de la aplicación. Mientras tanto, prueba con POST /external/v1/invoices/validate, que no emite nada.",
"docsUrl": "https://os.utilia.ai/docs/developers/getting-started/testing#billing-disabled-with-invoice-scopes",
"action": { "kind": "enable_billing", "label": "Activar la facturación" }
}
],
"nextSteps": [
{
"code": "BILLING_DISABLED_WITH_INVOICE_SCOPES",
"fixedBy": "organization",
"text": "Pide a un administrador de tu organización que active la facturación de la aplicación. Mientras tanto, prueba con POST /external/v1/invoices/validate, que no emite nada.",
"docsUrl": "https://os.utilia.ai/docs/developers/getting-started/testing#billing-disabled-with-invoice-scopes"
}
]
}- verdictobjetoobligatorio
- El estado en una línea: status (ok, attention, failing o idle; paused solo en el panel y por MCP), title y detail con el único paso que hace falta. Ver Cómo se lee el veredicto.
- findingsarrayobligatorio
- Los hallazgos, cada uno con code, severity (blocking, warning, optional o info), fixedBy, title, detail, docsUrl y, si se puede, action. Cada code tiene su apartado en Hallazgos del diagnóstico.
- nextStepsarrayobligatorio
- Los pasos en orden: primero lo que arreglas tú (developer), después la organización y después nosotros.
- accessarrayobligatorio
- Por recurso, cada operación con allowed y, si no, reason (la primera causa, en el orden en que la comprueba la llamada real), missingScopes y su ensayo en seco (dryRun). Lo que el plan no incluye y la aplicación tampoco intenta usar solo se cuenta en unavailable.
- scopesarrayobligatorio
- Cada permiso concedido con su state: usable, not_in_plan, capability_inactive o coming_soon, y enforcementDate si está en transición.
- credentialobjetoobligatorio
- keyInUse, previousKeyExpiresAt, lastUsedAt, rateLimitPerHour, rateLimitRemaining y, con una clave publicada en una web, publishable: true y scopesOutsideCeiling, cuántos permisos concedidos no puede usar esa clave.
- organization / responsibleobjetoobligatorio
- El nombre y el estado de la organización, y el estado de la persona responsable (active, inactive o missing). Nunca su nombre ni su correo.
- billingobjeto | nullobligatorio
- Si la facturación de la aplicación está activa, si el plan incluye Finanzas y si envía las facturas por correo. null si la aplicación no factura.
- webhooksobjetoobligatorio
- Configuración de los avisos, eventos fuera de plan, entregas de las últimas 24 horas y el resultado de la última comprobación del receptor.
- trafficobjetoobligatorio
- Última llamada, última que fue bien, recuentos de las últimas 24 horas, el pulso por horas y las llamadas rechazadas agrupadas (con resolved: true las que ya no tienen causa). No cuenta las llamadas al propio diagnóstico ni a /me.
- securityobjetoobligatorio
- openAdvisories: cuántas recomendaciones de seguridad hay abiertas sobre las credenciales de la aplicación.
- journeyarrayobligatorio
- Los primeros pasos de la integración según su propósito, marcados solos con lo que observa el servidor.
Las instrucciones para tu asistente
Con ?format=markdown, la misma ruta devuelve unas instrucciones sacadas del mismo diagnóstico. Nunca llevan la clave: la nombran como la variable UTILIA_API_KEY.
- Conexión: la dirección base de la API,
https://os.utilia.ai/api(la que esperan los SDK y la variableUTILIA_BASE_URLde la ordendoctor), la ruta completa de la API pública, la cabecera de la clave, el contratoGET /external/v1/diagnostics/openapi.jsoncomo referencia de rutas y campos, y la documentación para asistentes enhttps://os.utilia.ai/llms.txt. - Estado ahora: el veredicto.
- Qué puede llamar hoy y qué no y por qué, operación por operación.
- Lo que falta, con quién lo arregla y su
docsUrl. - Primeros pasos: los de
journey, hechos o pendientes, con cómo se marca cada uno. - Reglas para probar sin romper nada.
Cómo se lee el veredicto
verdict.title empieza siempre por una de estas frases, y la pestaña Pruebas enseña la misma palabra arriba del todo. Lo que sigue a los dos puntos es el hallazgo que manda.
title empieza por | status | Cuándo |
|---|---|---|
| Funciona | ok | Nada que revisar. |
| Funciona en parte | attention | Unas rutas fallan y otras han ido bien en la última hora. No llega a No funciona ni avisa por correo. |
| Revisar | attention | Llamadas rechazadas por falta de permiso, sin otras que vayan bien. |
| Falta un paso | attention | Algo bloquea, pero ninguna llamada lo ha encontrado todavía: la facturación desactivada sin emisiones rechazadas, o los datos fiscales de la organización incompletos. |
| Funciona, pero hay que revisar algo | attention | Un aviso: funciona, con algo pendiente. |
| No funciona | failing | Un bloqueo que ya rechaza llamadas, por la configuración o por el tráfico. |
| Todavía no ha llamado | idle | La clave nunca ha recibido una respuesta correcta. Solo en el panel y por MCP: con la clave, la propia llamada al diagnóstico ya es una. |
| Todavía no usa la API | idle | La clave ha respondido bien (a /me o al diagnóstico), pero no hay llamadas a ninguna otra ruta. |
| Sin uso en los últimos 30 días | idle | Usó la API, pero no en los últimos 30 días. |
| Desactivada | paused | La aplicación está desactivada y rechaza todas sus llamadas. Solo en el panel y por MCP: con la clave, el diagnóstico responde 403 APP_DISABLED. |
Las tres frases de idle siguen una sola regla. Usar la API es llamar a cualquier ruta salvo GET /external/v1/me y las del propio diagnóstico; probar la clave es recibir cualquier respuesta correcta con ella, también de esas dos. Es la misma regla que marca el paso Primera llamada correcta.
En el panel, todo attention que no es «Funciona en parte» se lee Revisar. Los mismos estados son los de la salud de la aplicación.
Por qué no se puede llamar a una operación
En access, cada operación que tu aplicación no puede llamar trae reason: la primera causa, en el orden en que la comprueba la llamada real. Cada motivo corresponde al error que daría esa llamada, así que puedes tratarlo igual que el error, sin esperar a que ocurra.
reason | Qué pasa | La llamada real responde |
|---|---|---|
APP_DISABLED | La aplicación está desactivada. Lo arregla la organización. Solo en el panel y por MCP: con la clave, la llamada se corta antes. | 403 APP_DISABLED |
RESPONSIBLE_INACTIVE | La aplicación no tiene persona responsable, o está dada de baja o bloqueada. Lo arregla la organización. Solo en el panel y por MCP. | 403 APP_RESPONSIBLE_INACTIVE |
PUBLISHABLE_KEY_CEILING | La clave está publicada en páginas web, la operación exige un permiso que no se usa con ella y UTILIA OS ya lo exige también desde los servidores. Lo arregla la organización, separando la integración en dos aplicaciones. | 403 SCOPE_NOT_ALLOWED_FROM_BROWSER |
MODULE_NOT_IN_PLAN | El plan no incluye el aplicativo de la operación (moduleKey). Lo arregla la organización. | 403 MODULE_NOT_ENABLED |
COMING_SOON | La función está en «Próximamente». No hay que hacer nada. | 403 FEATURE_COMING_SOON |
CAPABILITY_NOT_ACTIVE | El plan no tiene activa la función que necesita (capabilityKey). Lo arregla la organización. | 403 CAPABILITY_NOT_ENABLED |
TENANT_SUSPENDED y TENANT_CANCELLED | La organización está suspendida o de baja, y la operación crea o cambia datos. Las consultas siguen funcionando. | 403 TENANT_SUSPENDED o 403 TENANT_CANCELLED |
SCOPE_MISSING | Falta un permiso de la API; missingScopes dice cuál. Lo concede la organización. | 403 INSUFFICIENT_SCOPE |
BILLING_DISABLED | La operación factura o cobra y la facturación de la aplicación está desactivada. Lo arregla la organización. | 403 BILLING_NOT_ENABLED |
RESPONSIBLE_PERMISSION_DENIED | La persona responsable no tiene el permiso que exige esta operación: la aplicación nunca puede más que ella. Lo arregla la organización, dándole el permiso o nombrando a otra persona. | 403 APP_RESPONSIBLE_PERMISSION_DENIED |
Lo que el plan no incluye y la aplicación tampoco intenta usar (no tiene su permiso) no se detalla: se cuenta en unavailable, por motivo, con MODULE_NOT_IN_PLAN, CAPABILITY_NOT_ACTIVE o COMING_SOON. Con una clave publicada, las operaciones cuyo permiso no puede usar esa clave también se cuentan ahí, con PUBLISHABLE_KEY_CEILING, aunque hoy pasen desde un servidor.
Llamadas rechazadas
/api/external/v1/diagnostics/callsLlamadas fallidas de esta aplicación agrupadas por ruta, método, estado y errorCode, con su recuento, la primera y la última vez, el último requestId, qué significa y quién lo arregla. Sin permisos de la API.
La consulta admite window, con 24h (la de por defecto) o 7d; en los SDK, calls({ window: '7d' }) y calls(window="7d"). Cada grupo usa la plantilla de la ruta, nunca la dirección con sus valores, y no incluye las llamadas al propio diagnóstico ni a /me. Con una clave publicada en una web, responde 403 DIAGNOSTICS_NOT_AVAILABLE_FOR_PUBLISHABLE_KEY: esas llamadas se ven en la pestaña Pruebas.
{
"window": "24h",
"rejected": [
{
"method": "POST",
"route": "/external/v1/invoices",
"statusCode": 403,
"errorCode": "INSUFFICIENT_SCOPE",
"count": 12,
"firstAt": "2026-10-21T08:02:11.000Z",
"lastAt": "2026-10-21T09:58:40.000Z",
"lastRequestId": "550e8400-e29b-41d4-a716-446655440000",
"fixedBy": "organization",
"title": "A la aplicación le falta un permiso de la API para esta operación.",
"docsUrl": "https://os.utilia.ai/docs/developers/getting-started/errors-and-retries#insufficient_scope"
},
{
"method": "GET",
"route": "/external/v1/leads",
"statusCode": 403,
"errorCode": "MODULE_NOT_ENABLED",
"count": 3,
"firstAt": "2026-10-21T07:40:02.000Z",
"lastAt": "2026-10-21T07:41:15.000Z",
"lastRequestId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"fixedBy": "organization",
"title": "El plan de la organización no incluye el aplicativo que usa esta operación.",
"docsUrl": "https://os.utilia.ai/docs/developers/getting-started/errors-and-retries#module_not_enabled",
"resolved": true
}
]
}Qué queda registrado
También las llamadas que se cortan antes de llegar a la operación:
- Las que rechaza un guard: plan, origen y freno por minuto, y también la propia clave cuando se reconoce de qué aplicación es (desactivada, sin responsable activo o llamando con la clave anterior que ya no vale).
- Las que no llegan a ninguna ruta, si traen una clave válida en
X-Api-Key: una dirección que no existe (404, sin código), un método que la ruta no admite (405 METHOD_NOT_ALLOWED) y un origen que rechaza CORS (403 CORS_ORIGIN_NOT_ALLOWED). La comprobación previa que hace el navegador (OPTIONS) no lleva la clave y no deja rastro. - Estado
499: tu programa cortó la llamada antes de recibir la respuesta, casi siempre porque su tiempo de espera es más corto de lo que tarda la ruta. No sabes si se hizo: si era una escritura, compruébalo antes de repetirla.
Se guardan como mucho 10 rechazos por segundo y aplicación, para que una clave usada en bucle no llene el registro: por encima, el recuento es una muestra.
Un grupo trae resolved: true cuando su causa ya no existe con la configuración de hoy: la facturación ya está activada, el permiso ya está concedido, el plan ya incluye el aplicativo, o la aplicación, su responsable o la organización vuelven a estar activos. Esos grupos van al final, no generan hallazgos y no cuentan en el veredicto: son de antes del arreglo.
El contrato de tu clave
/api/external/v1/diagnostics/openapi.jsonEl contrato OpenAPI 3 de la API pública con solo las operaciones que esta clave puede llamar hoy. Sin permisos de la API.
Es el contrato de la API pública recortado a tu clave, con la misma decisión que el diagnóstico: permisos de la API, plan de la organización, «Próximamente» y facturación. Dáselo tal cual a tu asistente de IA o a tu generador de clientes: todo lo que hay dentro se puede llamar con esa clave. En los SDK, openApi() y open_api().
Lo que no puedes llamar no se detalla: x-utilia-unavailable, en la raíz, cuenta cuántas operaciones quedan fuera por cada motivo, sin decir cuáles. Con una clave publicada en una web, PUBLISHABLE_KEY_CEILING cuenta las operaciones cuyo permiso no puede usar esa clave y PUBLISHABLE_KEY, las pruebas que se lanzan desde el panel. Si responde 503, el contrato no está disponible en ese momento; repite más tarde.
Claves publicadas en una web
La clave de una aplicación cuenta como publicada en páginas web si la aplicación tiene dominios autorizados o si la clave vigente se ha usado desde un navegador desde su última renovación. Cualquiera puede copiarla de la página y usarla desde un servidor, sin cabecera Origin, así que para el diagnóstico esa clave es pública. Lo decide la configuración y la historia de la clave, no la petición: una cabecera Origin la pone quien quiere. Qué permisos admite una clave así, y cómo separar la web y el servidor en dos aplicaciones, está en Claves dentro de una página web.
Lo que responde cada clave
Nunca ha estado en una página
Clave de servidor
Con dominios o usada desde un navegador
Clave publicada en una web
Con esa clave, GET /external/v1/me y GET /external/v1/diagnostics responden con credential.publishable: true, para que un 0 o un null no se lean como «no hay nada», y sin lo que solo interesa a tu organización o diría para qué más sirve la clave:
organization.statussolo diceactiveoinactive, y los hallazgosTENANT_SUSPENDEDyTENANT_CANCELLEDconservan su código con un texto neutro, sin hablar de la suscripción.- Sin recomendaciones de seguridad abiertas (
security.openAdvisoriesa0, sinCREDENTIAL_ADVISORY_OPEN). - Sin el resultado de la comprobación del receptor (
webhooks.lastReceiverCheckanull, sinWEBHOOK_RECEIVER_ACCEPTS_INVALID_SIGNATURE) ni los hallazgosORIGINS_UNRESTRICTEDyPUBLISHABLE_KEY_SENSITIVE_SCOPES. scopesyaccesssolo llevan los permisos y las operaciones que esa clave puede usar. Lo demás se cuenta sin nombrarlo: encredential.scopesOutsideCeilingy, en cada grupo deaccess, enunavailablecon el motivoPUBLISHABLE_KEY_CEILING.
GET /diagnostics/calls, POST /diagnostics/run, webhook-sample y webhook-receiver-check responden 403 DIAGNOSTICS_NOT_AVAILABLE_FOR_PUBLISHABLE_KEY antes de gastar ningún cupo, porque enseñan el tráfico de la aplicación, leen como ella o envían avisos firmados a su servidor. La pestaña Pruebas del panel las lanza sin usar la clave. check-request y el contrato siguen disponibles: no revelan nada de la organización y dicen lo mismo que la llamada real.
Si llegan llamadas con esa clave a rutas que la aplicación no tiene concedidas, el diagnóstico no propone conceder el permiso: pueden no venir de tu web. Lo explica scope-missing-for-observed-calls.