Cambios de OAuth
Historial de cambios del servidor OAuth 2.1 de UTILIA OS y de lo que lo rodea: cambios incompatibles, retiradas con fecha, novedades y refuerzos de seguridad.
Actualizado el . 12 min de lectura
Esta página reúne los cambios del servidor OAuth 2.1 de UTILIA OS que afectan a quien ya tiene una integración: qué cambió, desde cuándo y qué tienes que adaptar. Las entradas van de la más reciente a la más antigua. Las versiones exactas de los SDK están en el CHANGELOG.md de cada paquete y en Versiones y compatibilidad del SDK.
Retiradas pendientes
Próximas retiradas
16 oct 2026
Endpoint antiguo de permisos
GET /api/oauth/user-scopes pasa a GET /api/oauth/user/scopes. Ya pasó su fecha y puede dejar de responder en cualquier momento.1 feb 2027
Campo scope de /api/oauth/apps
Se sustituye por el campo permissions.2 mar 2027
Fin de las transiciones de scopes
Los tokens anteriores al 2 de septiembre de 2026 dejan de leer datos sensibles y personas de contacto con los scopes antiguos.
GET /api/oauth/user-scopes responde con las cabeceras Deprecation: true, Sunset: Thu, 16 Oct 2026 00:00:00 GMT y Link al sustituto. Si las ves en tus registros, tienes código que todavía llama a un endpoint antiguo. POST /api/oauth/apps/revoke se retiró el 20 de octubre de 2026 y ya no responde: usa POST /api/oauth/apps/:clientId/revoke.
Estado actual
| Elemento | Valor |
|---|---|
| Access token | 1 hora (3.600 s) |
| Refresh token | 90 días desde la autorización; rota en cada renovación |
| Código de autorización | 60 segundos, un solo uso |
| PKCE | Obligatorio, solo S256 |
| SDK oficiales | @utilia-os/sdk-js y utilia-sdk, versión 4.1.0 |
20 de octubre de 2026
- Se retira la dirección única
/api/mcp. La dirección que reunía todas las áreas en un solo servidor MCP deja de funcionar:POST,GETyDELETEahttps://os.utilia.ai/api/mcpresponden 410 con un error JSON-RPC que enumera la dirección de cada área. Conecta tu asistente a la del área que uses:/api/mcp/crm,/api/mcp/work,/api/mcp/finance,/api/mcp/marketing,/api/mcp/chato/api/mcp/rrhh. Las direcciones de área no cambian. - OAuth deja de reconocerla como recurso. Una autorización con
resource=/api/mcprespondeinvalid_request, y los metadatos generales de descubrimiento ya no la anuncian. Los tokens emitidos para ella reciben 401invalid_tokenen todos los servidores, para que el cliente vuelva a autorizarse con la dirección del área, y ya no se renuevan. - Una autorización sin
resourcesolo concede identidad. Hasta ahora se ataba a/api/mcp. Si pides scopes de un servidor MCP sin indicar su dirección, y el registro de tu cliente no tiene un recurso por defecto, la autorización respondeinvalid_requestcon las direcciones de las áreas. Iniciar sesión con UTILIA (openid,profileyemail) sigue igual.
13 de octubre de 2026
- Formación, scrapers, gamificación y anuncios internos respetan lo que se autorizó. Hasta ahora, con un token de solo lectura del CRM o de Gestión de proyectos se podían crear, publicar y borrar formaciones, lanzar y borrar scrapers, ajustar puntos de gamificación y publicar anuncios internos, siempre que la persona tuviera el permiso. Ahora consultar sigue funcionando con cualquier scope de lectura, crear y modificar exige un scope de modificación (
crm:write,dev:writeo un:update), y eliminar, un scope de eliminación (crm:full,dev:fullo un:delete). Si tu integración borra en estos módulos concrm:writeodev:write, pide también el:fullo un:delete. El directorio de usuarios (users) también comprueba el scope en cada consulta; sigue funcionando con los mismos scopes con los que hoy ves la herramienta. Detalle en Scopes y recursos.
12 de octubre de 2026
- Todo
invalid_clientllevaerror_uri. La respuesta añadeerror_uri, la dirección del apartadoinvalid_clientde Problemas frecuentes con OAuth en la documentación pública: la misma en todos los casos y en todos los endpoints. Lleva a la explicación de las causas posibles sin decir cuál ha sido la tuya. Missing client_iddesaparece como texto propio. Una petición sinclient_idenPOST /api/oauth/tokenoPOST /api/oauth/introspectresponde como cualquier otro fallo de autenticación del cliente:401conClient authentication failed.- Cabecera
WWW-Authenticateen el 401 de autenticación del cliente. Si tu cliente se autenticó con la cabeceraAuthorization: Basic, el401 invalid_clientde esos dos endpoints llevaWWW-Authenticate: Basic realm="UTILIA OS", como pide la RFC 6749 (sección 5.2). Si mandas las credenciales en el cuerpo, no la lleva. - La introspección rechazada deja rastro. Si un cliente de una aplicación externa de tu organización intenta la introspección y se le rechaza, lo verás en Actividad › Errores, en la ficha de esa aplicación, como mucho una vez cada 24 horas. El texto es siempre el mismo y no dice la causa concreta.
9 de octubre de 2026
Cambios incompatibles
La introspección (POST /api/oauth/introspect) tiene dos restricciones nuevas:
- Solo la usan los clientes confidenciales. El cliente tiene que autenticarse con su secreto (
client_secret_basicoclient_secret_post). Un cliente público, sin secreto, recibe401 invalid_client. - Solo responde sobre los tokens de ese mismo cliente. Un token emitido a otro cliente responde
active: false, aunque siga vivo.
Qué hacer: si tu servidor comprueba tokens con la introspección, usa un cliente de tipo Con servidor propio y manda su secreto. Una aplicación pública (de navegador, móvil o escritorio) no necesita la introspección: llama a la API con el access token y, si responde 401, renuévalo con el refresh token. Detalle en Canje, revocación e introspección de tokens.
Más cambios
- Un cliente desactivado invalida sus tokens. Al desactivar OAuth en una aplicación, sus access tokens dejan de valer en el acto, aunque no consten revocados: la introspección responde
active: falsey la renovación falla coninvalid_client. invalid_clientya no dice qué falló. EnPOST /api/oauth/tokenyPOST /api/oauth/introspect, un cliente inexistente, desactivado o vencido, un secreto que falta o que no es correcto y un cliente público en la introspección responden lo mismo:401conClient authentication failed. Antes cada caso tenía su texto (Client not found,Client is inactive,Client secret required...) y con ellos se podía averiguar qué clientes existen. Si tu integración leía esos textos, compara solo el campoerror. Las causas posibles están en Problemas frecuentes con OAuth.- El cierre de sesión exige un
id_token_hintfirmado por UTILIA OS. Se comprueba su firma RS256 con la clave pública de/api/oauth/jwksy el claimisses obligatorio. - La marca de aplicación propia y la de «Verificada por UTILIA» solo las asigna UTILIA OS. Si pides
is_first_partyoverifiedcon valortrue, recibes un403.
5 de octubre de 2026
La persona puede conceder menos scopes de los que pides
La pantalla de consentimiento permite desactivar grupos de permisos antes de autorizar. Los de identidad (openid, profile, email) no se pueden desactivar. El token lleva solo lo que quedó activado, y las renovaciones conservan ese conjunto reducido.
Si tu aplicación no manda scope, la pantalla enseña ahora el conjunto por defecto que se concede (los scopes permitidos del registro del cliente o, si no tiene, los de identidad), y la persona también puede estrecharlo. Antes ese conjunto se concedía sin enseñarse.
Qué hacer: lee el campo scope de la respuesta de POST /api/oauth/token y desactiva lo que dependa de un scope no concedido. Si con la selección un servidor MCP se quedara sin ningún scope útil, la pantalla no deja autorizar, así que nunca se emite un token que el servidor rechazaría. Detalle en Pantalla de consentimiento.
Compartir documentación con enlace tiene scope propio
Crear un enlace público a un documento exige ahora dev:docs:share, que no viaja en ningún conjunto. Antes bastaba con dev:docs:update. Los tokens anteriores no lo llevan y tienen que volver a autorizarse.
14 de septiembre de 2026
El límite de los documentos de descubrimiento (/.well-known/*) sube de 120 a 600 peticiones por minuto y dirección IP: un cliente MCP con varios servidores configurados los pide muchas veces al arrancar.
3 de septiembre de 2026
- Fusiones con scope propio:
crm:clients:merge,crm:contacts:mergeycrm:leads:mergeno viajan en ningún conjunto, tampoco encrm:full. Se piden por su nombre. - Acceso al portal del cliente con scope propio: la herramienta
portal_accessexigecrm:portal:viewocrm:portal:managey ya no se abre concrm:contacts:update, sin periodo de transición.
2 de septiembre de 2026
- Datos sensibles con scope propio: el NIF, el límite de crédito y el descuento del cliente pasan a
crm:clients:view-sensitive, y los datos bancarios y fiscales del proveedor, afinance:suppliers:view-sensitive. Los tokens anteriores siguen leyéndolos con el scope antiguo hasta el 2 de marzo de 2027. - Personas de contacto de los proveedores: pasan a
finance:supplier-contacts:viewyfinance:supplier-contacts:manage, fuera definance:readyfinance:write. Los tokens anteriores siguen operando confinance:suppliers:viewyfinance:suppliers:updatehasta el 2 de marzo de 2027.
28 de agosto de 2026
Ningún conjunto concede ya la administración de la organización: org:team:*, org:observability:* y org:settings:update se piden por su nombre. Hasta esta fecha los expandía crm:full. Solo org:settings:view sigue viajando en crm:read y crm:full.
28 de julio de 2026
Cambios en el alcance de los tokens y en la respuesta de GET /api/oauth/apps.
El campo scope de GET /api/oauth/apps se retira el 1 de febrero de 2027
Sigue llegando hasta esa fecha, pero ha cambiado de significado: ya no lista todo lo que hay en el token, sino solo lo que el plan de la organización deja ejercer. Lo demás no se ha revocado: sigue concedido, pero pertenece a aplicativos no contratados y el servidor MCP lo deniega.
Campos nuevos para sustituirlo:
- permissionsarray
- Permisos ejercibles con el plan actual. Cada elemento: scope, description (en castellano) e impact (destructive, write, read o identity).
- permissionCountnumber
- Número de permisos ejercibles. Siempre igual a la longitud de permissions.
- unavailablePermissionCountnumber
- Permisos concedidos que el plan actual no deja ejercer.
- unavailableModulesarray
- Aplicativos no contratados de los que vienen esos permisos. Cada elemento: key y label.
La forma completa de la respuesta está en OAuth: visión general.
Una renovación solo puede estrechar el alcance
Antes, grant_type=refresh_token recalculaba el alcance a partir de los permisos vigentes de la persona, así que un token podía crecer más allá de lo que se consintió. Ahora el token renovado es siempre un subconjunto del consentido, acotado además por el servidor al que está atado. Para ampliar el alcance hay que volver a autorizar.
Si la persona pierde todos los permisos del servidor del token, la renovación revoca la cadena y responde invalid_grant. Los tokens solo de identidad (openid, profile, email) no se ven afectados.
Los tokens emitidos antes del cambio se revocaron al aplicarlo, salvo los solo de identidad: su primera renovación respondió invalid_grant y hubo que autorizar una vez más.
Qué hacer:
- Sustituye
scopeporpermissionsantes del 1 de febrero de 2027, y muestra tambiénunavailablePermissionCountyunavailableModules, que explican por qué faltan permisos. - Trata
invalid_grantvolviendo a pedir la autorización. - No cuentes con que un token gane scopes solo: pídelos en una autorización nueva.
12 de mayo de 2026
support:writeincluyesupport:tickets:create: los clientes consupport:writepueden abrir tickets sin volver a autorizar, porque el conjunto se resuelve en el servidor. Si no quieres esa ampliación, revoca el token y pide solo los scopes individuales que necesites.- Abrir tickets por MCP: la herramienta
ticketsadmitecreate. Quien abre el ticket es siempre la persona dueña del token, el título y la descripción se limpian de HTML, y hay un máximo de 10 tickets por hora y persona por esta vía.
16 de abril de 2026
Cambios incompatibles
- El access token dura 1 hora en lugar de 30 días. Toda integración tiene que renovar con el refresh token; el valor exacto llega en
expires_in. - El refresh token rota en cada renovación. Reutilizar uno ya rotado revoca la cadena y responde
invalid_grant(Refresh token has been rotated). Si dos renovaciones compiten a la vez por el mismo, la que pierde recibeRefresh token rotated concurrently. Detalle en Rotación del refresh token.
Endpoints que se retiran el 16 de octubre de 2026
GET /api/oauth/user-scopespasa aGET /api/oauth/user/scopes, que además admiteclient_idpara devolver la información pública del cliente.POST /api/oauth/apps/revokepasa aPOST /api/oauth/apps/:clientId/revoke, con el identificador en la ruta y sin cuerpo.
Novedades
GET /api/oauth/clients/:clientId: información pública de un cliente. Responde{ "known": false }con200si no existe, para impedir enumerar clientes.GETyPOST /api/oauth/logout: cierre de sesión iniciado por tu aplicación (OpenID Connect RP-Initiated Logout 1.0), con el campopost_logout_redirect_urisen el registro de clientes (hasta 10 direcciones).- Parámetros
prompt(none,login,select_account,consent) ylogin_hinten/api/oauth/authorize.
Seguridad
- Las direcciones de los clientes (
redirect_uris,client_uri,logo_uri,post_logout_redirect_uris) solo admitenhttpyhttpsy rechazan los hosts internos.localhost,127.0.0.0/8y::1se admiten en las direcciones de vuelta, porque los clientes nativos las necesitan. login_hintsolo admite letras, dígitos y. _ @ + -, con un máximo de 255 caracteres.- Los tokens de una persona cuya cuenta deja de estar activa se rechazan de inmediato.
POST /api/oauth/introspectexige autenticación de cliente.
Límites de uso establecidos
| Endpoint | Límite |
|---|---|
GET /.well-known/* | 120 por minuto (600 desde el 14 de septiembre de 2026) |
POST /api/oauth/register | 30 por hora |
POST /api/oauth/token y POST /api/oauth/revoke | 60 por minuto |
POST /api/oauth/introspect | 120 por minuto |
GET /api/oauth/scopes | 60 por minuto |
GET /api/oauth/clients/:clientId y /api/oauth/logout | 30 por minuto |
8 de abril de 2026
Los SDK admiten el parámetro theme (light o dark) al construir la URL de autorización.
2 de abril de 2026
- OAuth en las aplicaciones externas: se activa desde la ficha de cada aplicación, en Gestión de proyectos › Integraciones.
- OpenID Connect:
/.well-known/openid-configuration,/api/oauth/userinfo,/api/oauth/jwkseid_tokenfirmados con los claims estándar. - SDK:
getAuthorizationUrl()yget_authorization_url()gestionan PKCE solos;handleCallback(code)yhandle_callback(code)recuperan elcode_verifiery comprueban elstate;isAuthenticated(),logout()y, en JavaScript,loginWithPopup().