Cambios de las integraciones
Historial de cambios de la API externa, los SDK, MCP y el widget de UTILIA OS, con los cambios incompatibles destacados.
Actualizado el . 31 min de lectura
Este es el registro de cambios de todo lo que usa tu integración: la API externa, los SDK de JavaScript y Python, las herramientas MCP, OAuth y el widget. Los cambios que te obligan a tocar tu código o a conceder un permiso nuevo llevan la marca CAMBIO QUE ROMPE o Cambio incompatible. Las versiones vigentes están al final de la página.
Si vienes de una versión anterior, estos son los cambios que te obligan a actuar:
Cambios que exigen tocar tu integración
27 may
Firma de los avisos
Se firmatimestamp.rawBody: los receptores que firmaban solo el cuerpo rechazan las entregas.2 sep
Borrar archivos por MCP
dev:write y crm:write dejan de borrar archivos; hace falta un ámbito propio.3 sep
Portal del cliente por MCP
portal_access exige crm:portal:view y crm:portal:manage.5 sep
SDK 3.0.0 y permisos de la API
identify exige crm:users:write y desaparece el periodo de gracia de los permisos.7 sep
SDK 4.0.0 y cobro con tarjeta
Se retira el correo al proveedor; cobrar exige Pagos con tarjeta (Stripe) en el plan.5 oct
Enlaces a la documentación
create_share_token exige el ámbito dev:docs:share.
2026-10-05
Los enlaces a la documentación tienen scope y permiso propios
Cambio incompatible para las integraciones que crean enlaces de documentación.
documentation(operation: 'create_share_token')exige el scopedev:docs:sharey el permisoDEV_DOCS_SHARE(«Compartir documentación con enlace»). Antes bastaba condev:docs:updatey con poder ver la documentación.dev:docs:sharese pide por su nombre: no viaja endev:read,dev:write,dev:fullniadmin:full. Añádelo a los scopes que pide tu aplicación. Los tokens emitidos antes no lo llevan y hay que volver a autorizarlos.- REST:
POST /api/dev-management/documentation/:id/shareexigeDEV_DOCS_VIEWyDEV_DOCS_SHARE. - Los enlaces
PRIVATEpiden sesión:GET /api/share/:tokenresponde 401 conerrorCode: SHARE_LOGIN_REQUIREDsi no hay sesión, y 404 si la sesión es de otra organización o no puede leer el documento. LosPUBLICyTEMPORARYno cambian. - Adjuntos de un enlace
PRIVATE:GET /api/share/:token/file/:fileId?format=jsondevuelve{ "url": "…" }en lugar de redirigir, para pedir la descarga con la sesión en la cabecera.
El usuario puede conceder menos scopes de los que pides
- La pantalla de consentimiento deja desactivar grupos de permisos antes de
autorizar. El token lleva solo los scopes que quedan activados, y las
renovaciones conservan ese conjunto reducido. Los de identidad (
openid,profile,email) no se pueden desactivar. - Lee el
scopede la respuesta del token en vez de dar por hecho el conjunto pedido, y degrada con elegancia lo que dependa de un scope no concedido. - Sin
scopeen la solicitud, la pantalla enseña el conjunto por defecto que se concede (losallowedScopesdel registro o, sin ellos, los de identidad), y también se puede estrechar. - Detalle en Pantalla de consentimiento.
2026-09-12
SDK JavaScript 4.1.0 y SDK Python 4.1.0, publicados en npm y PyPI
Superficie nueva, sin cambios incompatibles: actualizar desde la 4.0.1 no exige tocar nada.
- El calendario laboral de la empresa, por MCP.
sdk.mcp.attendance.holidays(sobrePOST /api/mcp/rrhh) ofrecegetScope(),setScope(),import(year)ylist()en JavaScript, yget_scope(),set_scope(),import_year(year)ylist()en Python, en las variantes asíncrona y síncrona. - El ámbito decide qué festivos cuentan. Sin comunidad elegida solo cuentan los nacionales y los propios de la empresa, así que lo primero es consultar el ámbito y solo después cargar el año. Cambiar el ámbito no borra festivos.
- Los municipales no se cargan: los publica cada ayuntamiento y la respuesta
lo avisa en
notice. - Permisos:
hr:attendance:viewpara consultar y listar;hr:attendance:managepara fijar el ámbito y cargar el año.
2026-09-09
SDK JavaScript 4.0.1 y SDK Python 4.0.1, publicados en npm y PyPI
Versión solo de documentación: no cambia ni el comportamiento ni la firma de ningún método, así que actualizar desde la 4.0.0 no exige tocar nada.
AnnouncementsServicedocumenta sus reglas: qué ámbitos exige (dev:announcements:view,:managey:publish) y que antes de preparar o publicar un anuncio hay que consultarinstructions().- El número de versión del paquete de Python queda alineado: arrastraba una
cifra antigua en su
package.jsonauxiliar.
Quien venga de la 3.x debe leer antes los cambios incompatibles de la 4.0.0: esa versión retiró tres métodos sin sustituto.
2026-09-07
Pedir más plan y reservar una llamada, también por MCP
La herramienta transversal org_plan pasa de dos operaciones a doce. Las diez nuevas cubren pedir que se amplíe el plan (request_upgrade, list_my_requests, withdraw_upgrade_request, list_requests, dismiss_request) y reservar quince minutos con el equipo de UTILIA (list_sales_call_slots, book_sales_call, get_my_sales_call, cancel_sales_call, reschedule_sales_call). Las tres escrituras de la reserva exigen confirm: true.
- Dos ámbitos transversales nuevos.
org:plan:requestlo expanden los conjuntos:writey:fullde los siete dominios, nunca los:read;org:plan:manage_requests, solo los:full. La herramienta deja de anunciarse como de solo lectura, porque seis de sus doce operaciones escriben. - Categoría de avisos nueva en la herramienta
notifications:planRequests(«Peticiones de plan»). - No entra en los SDK ni en la API externa: una clave de API no es una persona, no tiene administrador a quien pedirle nada y no puede sentarse en una videollamada.
El detalle está en MCP: pedir plan y reservar llamada.
Saber si la organización puede cobrar antes de intentarlo
Una aplicación externa ya no tiene que enterarse por un error de que la organización no puede cobrar con tarjeta.
- Endpoint nuevo
GET /external/v1/payment-methods/readiness. DevuelveplatformAvailable,organizationCanCharge,reasonylivemode. Los seis motivos son estables:PLATFORM_DISABLED,NO_ACCOUNT,ONBOARDING_INCOMPLETE,CHARGES_DISABLED,ACCOUNT_DEAUTHORIZEDyREADY. Exige los aplicativos Finanzas y Stripe y el permisobilling:payment-methods:read; no estrena ningún permiso. - En los dos SDK:
sdk.paymentMethods.getReadiness()en JavaScript ypayment_methods.get_readiness()en Python, en las variantes asíncrona y síncrona. Van en la 4.0.0 de los dos SDK, junto con la retirada del correo al proveedor que se describe abajo. - Es el mismo criterio que usa el panel. Antes convivían tres formas distintas de decidir si se podía cobrar y se contradecían entre ellas. Ahora hay una, y la sirven este endpoint y las pantallas del panel.
El cobro con tarjeta se gatea por su propio aplicativo
Cobrar una factura, confirmar el cobro, devolverlo por la pasarela y gestionar las tarjetas guardadas exigen ahora el aplicativo «Pagos con tarjeta (Stripe)» en el plan de la organización, además de Finanzas. Sin él, la respuesta es 403 con errorCode: 'MODULE_NOT_ENABLED'.
Alcanza también a las herramientas MCP equivalentes: la herramienta payment_methods entera, crm_invoices_workflow en charge, charge_confirm, refund_create, payment_intent_cancel y payment_intents_list, y crm_clients_info:stripe_readiness.
No lo exigen, y es deliberado: el reembolso registrado a mano (refund_manual), los listados que incluyen reembolsos manuales, y marcar una factura como cobrada o parcialmente cobrada. Registrar una devolución en efectivo no necesita pasarela y exigirla sería falso.
Los ámbitos del token no cambian
La puerta del plan es ortogonal al permiso del token: crm:invoices:update y billing:payment-methods:* siguen concediendo exactamente lo mismo que antes. Lo que cambia es que, además, la organización tiene que tener contratado el aplicativo. Un 403 MODULE_NOT_ENABLED no se arregla pidiendo más permisos.
MCP: el copiloto puede consultar el estado de los pagos con tarjeta
Operación nueva finance_settings:get_stripe, solo lectura. Devuelve la disponibilidad de la pasarela, el estado de la cuenta conectada y los requisitos pendientes ya traducidos al castellano. Ámbito finance:settings:view, el mismo que el resto de lecturas de ajustes de Finanzas; permiso del usuario: configurar o ver los pagos con tarjeta; aplicativos Finanzas y Stripe.
No hay ni habrá alta ni desconexión por MCP: el alta exige que una persona teclee en un formulario alojado por Stripe con verificación de identidad, y la desconexión es en la práctica irreversible.
RETIRADO: PATCH /finance/stripe/account/publishable-key devuelve 410
Nunca tuvo efecto. La clave publicable que usa el producto es la de la plataforma UTILIA OS, no una de la cuenta conectada, así que no había nada que guardar. No lo llamaba ni el panel, ni los SDK, ni las herramientas MCP. Responde 410 Gone de forma permanente y no tiene sustituto.
SDK 4.0.0: el método de disposición de cobro y la retirada del correo al proveedor
Versión MAYOR en los dos SDK. Lo que obliga a subir de mayor no es el método nuevo, sino la retirada: los tres métodos que desaparecen se publicaron en la 2.28.0 y siguen vivos en la 3.1.0, que está en npm y en PyPI, así que quitarlos rompe a quien los use.
- Añade
sdk.paymentMethods.getReadiness()(payment_methods.get_readiness()en Python, en las dos variantes), con los tiposChargingReadinessyChargingReadinessReason. - Retira sin sustituto
sdk.mcp.erp.suppliers.getEmailRecipients,sendEmailyreplyEmail, y con ellos los tiposSupplierEmailRecipients,SupplierSendEmailInputySupplierReplyEmailInput.
Cómo actualizar. Si solo dejabas constancia de un correo que enviabas tú, usa logInteraction con type: 'EMAIL', que no cambia. Si de verdad enviabas el correo a través de UTILIA OS, esa capacidad ya no existe en el producto. No hay ninguna otra ruptura en esta versión: la tabla completa está en Versiones y compatibilidad.
RUPTURA: se retira el correo al proveedor
El envío de correo desde la actividad del proveedor desaparece del producto, y con él su superficie de integración. No hay sustituto: para dejar constancia de un correo que se mandó por otra vía sigue estando log_interaction con type: 'EMAIL'.
- Se retiran tres rutas REST:
GET /finance/suppliers/:supplierId/interactions/email-recipients,POST /finance/suppliers/:supplierId/interactions/send-emailyPOST /finance/suppliers/:supplierId/interactions/:interactionId/reply. - Se retiran tres operaciones MCP de la herramienta
suppliers:get_email_recipients,send_emailyreply_email, junto con su parámetroemailMessage. El resto de la actividad (registrar, conversación, historial, adjuntos, lote y reuniones) no cambia. - Se retiran de los dos SDK los métodos
getEmailRecipients,sendEmailyreplyEmaildesdk.mcp.erp.suppliers(get_email_recipients,send_emailyreply_emailen Python) y sus tipos de entrada y salida. - Los ámbitos
finance:suppliers:interactions:ready:managesiguen existiendo y conservan todo lo demás: solo dejan de conceder lo relativo al correo.
2026-09-05
SDK 3.0.0: la facturación y el cobro de una aplicación externa funcionan de verdad
Versión mayor en los dos SDK. La razón principal no es el volumen: es que cinco servicios que existían y devolvían 401 siempre pasan a funcionar contra otra ruta, y uno desaparece. La tabla de rupturas completa está en Versiones y compatibilidad del SDK.
sdk.subscriptionsfunciona, sobre/external/v1/subscriptions. Hasta ahora atacaba una ruta de la API del panel. Firmas nuevas:clientIddesaparece yuserIdes obligatorio. Se retiranremove(la baja escancel) ystats.sdk.paymentAuthorizationses nuevo (sdk.payment_authorizationsen Python), y ahora sí crea autorizaciones desde la aplicación, con su prueba legal.sdk.mandatesqueda como alias en desuso;listForClientse retira.sdk.invoicesgana cinco acciones:send,charge,markPaid,getPublicLinkyregeneratePublicLink.sdk.invoices.scheduledChargesfunciona sobre la API externa. En Python se reubica bajosdk.invoicescon alias en la raíz, y se retiraretrieve, cuya ruta no existía.sdk.invoices.refunds.createemite el reembolso.cancelPreviewycancelse retiran: deshacer un reembolso es del equipo.sdk.invoices.rectificationsse retira con un error que dice qué usar.sdk.invoices.getPdfUrltambién (sustituto:regeneratePublicLink). Si tu integración lo usó, regenera la clave de API de la aplicación.sdk.webhooks.verifyy.parseverifican cualquier familia de avisos, con tipos por evento.sdk.chat.webhooks.verifyqueda como alias.- Enumerados derivados del esquema real:
InvoiceStatuspasa de 7 a 12 valores yTaxSystemsumaIPSIeINTERNATIONAL. En Python, un enumerado incompleto reventaba con un error de validación en la primera factura reembolsada.
API externa: suscripciones, autorizaciones de cobro, cobros programados y acciones sobre la factura
POST /external/v1/subscriptionsy sus trece rutas. Cuotas recurrentes de los usuarios de la aplicación, con generación, envío y cobro automáticos. Exige el aplicativo Finanzas y la capacidad de facturación recurrente./external/v1/payment-authorizations. El consentimiento firmado que habilita cobrar sin el usuario delante, con texto canónico, versión, momento, IP y navegador. Exige Finanzas, Stripe y la capacidad de cobro automático./external/v1/invoices/:id/scheduled-chargesy/external/v1/scheduled-charges/:id. Cobros programados para una fecha futura, con cancelación y reintento adelantado.- Cinco acciones sobre una factura emitida:
send(envío por correo con su PDF),mark-paid(cobro fuera de la pasarela),refund,public-link(leer y emitir) ycharge(cobro sin nadie delante). POST /external/v1/invoicesaceptasendEmaily devuelveemailSent. UnemailSenten falso no es una factura fallida: está emitida y numerada, solo falló el correo.- Puente de identidad. El usuario de la aplicación se materializa como ficha de cliente del CRM la primera vez que se factura, se guarda tarjeta o se crea una suscripción.
POST /users/identifyaceptaclientIdpara vincularlo a una ficha creada antes. POST /users/identifyacepta los datos fiscales:taxIdybillingAddress(constreet,city,postalCodeycountryobligatorios yprovinceopcional). Se propagan a la ficha del CRM y a la de la pasarela. El alta de factura rellena con el receptor del documento los huecos que el usuario no tenga guardados, y lo que declareidentifymanda siempre.dayOfMonthes opcional en una suscripción mensual: sin él se toma el día destartDate, recortado a 28 cuando es posterior.
CAMBIO QUE ROMPE: el puente solo alcanza las fichas de cliente que creó la propia aplicación
Una aplicación externa solo puede reutilizar, converger o vincular fichas que
haya creado ella, con POST /external/v1/clients o con el propio puente. Una
ficha nativa del CRM, dada de alta por el equipo, queda fuera de su alcance:
identify({ clientId }) la rechaza con 404 CLIENT_NOT_ACCESSIBLE y la
deduplicación automática por identificador fiscal o correo tampoco la
reutiliza. Cuando la coincidencia cae en una ficha nativa se crea una ficha
nueva y el hallazgo se registra en el histórico de auditoría.
Unir una ficha de la aplicación con una ficha nativa sigue siendo posible desde el panel, con sesión iniciada, permiso y alcance.
CAMBIO QUE ROMPE: send solo escribe a direcciones conocidas de la factura
En POST /external/v1/invoices/:id/send, to y cc
solo aceptan direcciones ya registradas de esa factura: contactos de facturación
activos del cliente puente, contactos del CRM marcados para recibir facturas, el
correo del usuario externo y el destinatario con el que se emitió. Cualquier otra
rechaza el envío entero con 422 INVOICE_EMAIL_RECIPIENT_NOT_ALLOWED.
Hay además un cupo de 100 envíos por hora y aplicación: superado, 429 RATE_LIMIT_EXCEEDED con la cabecera Retry-After.
CAMBIO QUE ROMPE: las lecturas de una aplicación ya no traen sus claves
GET /integrations/apps y GET /integrations/apps/:id ya no devuelven ninguna
clave: traen webhookSecretConfigured, un booleano, y de la clave de API solo su
prefijo, su nombre, su estado y sus datos de uso. Las claves se entregan solo al
crearlas y al regenerarlas.
El enlace público que emite la aplicación solo permite ver y pagar
allowedActions es siempre ["VIEW", "PAY"] en los enlaces emitidos por la API
externa, también cuando la factura viene de una suscripción. Firmar o retirar
autorizaciones de cobro no es posible desde ese enlace. El enlace que reparte el equipo desde
el panel no cambia. En un cobro con REQUIRES_ACTION, el enlace que se entrega
es adicional y no revoca el que el usuario ya tuviera.
Dos códigos de error nuevos donde antes había uno confuso
AUTO_CHARGE_DISABLED_FOR_ORGANIZATION(409, sinnextStep): la organización tiene apagado el cobro automático con tarjeta. Antes salía comoPAYMENT_AUTHORIZATION_REQUIRED, que mandaba a la aplicación a recoger un consentimiento que no desbloqueaba nada. Lo enciende el equipo de la organización en los ajustes de Finanzas.SCHEDULED_CHARGE_RETRY_TOO_SOON(409) ya se emite. Estaba declarado y no lo lanzaba nadie: el tope de cinco reintentos manuales por factura cada 24 horas respondía con un 409 sin código.
La configuración de facturación de una aplicación deja de exigir la pasarela
GET y PATCH /integrations/apps/:id/billing-config se gobiernan con
Integraciones y Finanzas, sin Stripe, igual que billing-summary.
Emitir facturas es capacidad de Finanzas y funciona sin pasarela: una
organización con Finanzas y sin Stripe factura por transferencia y registra el
cobro a mano. Antes, la pantalla le mostraba un candado que prometía un plan
superior para algo que su plan ya incluía. Los cobros con tarjeta siguen
exigiendo Stripe.
El permiso «Emitir facturas» deja de anunciarse como reservado
El catálogo describía crm:invoices:issue como un alias reservado que «todavía
no es exigido por ningún endpoint» y remitía a POST /external/v1/invoices/:id/issue.
Las dos cosas eran falsas: ese endpoint no existe y POST /external/v1/invoices
exige el permiso desde el primer día. Crear una factura necesita crm:invoices:write
y crm:invoices:issue; el texto de «Crear facturas» lo advierte ahora.
CAMBIO QUE ROMPE: POST /external/v1/users/identify exige permiso
Desde esta fecha exige crm:users:write. Si tu aplicación ya usaba
identify, concédele ese permiso antes de desplegar.
CAMBIO QUE ROMPE: se retira el periodo de gracia de los permisos
El periodo de transición de los permisos ha terminado. El permiso se exige en todas las llamadas, también en aplicaciones creadas hace tiempo.
Permisos: 56, con una categoría nueva
- Once permisos nuevos:
crm:invoices:send,crm:users:write,billing:refund, los tres de suscripciones, los tres de autorizaciones de cobro y los dos de cobros programados. El catálogo pasa de 45 a 56. - Categoría
financial-critical, para lo que mueve dinero o corta un ingreso:billing:charge,billing:refund,billing:subscriptions:cancel,billing:payment-authorizations:writeycrm:invoices:cancel. Mismas exigencias al conceder que los críticos de datos personales, con distinto motivo de blindaje, porque quien revisa la solicitud necesita saber por qué está protegido. - El catálogo se recorta por el plan. Cada entrada declara qué aplicativos y capacidades exige, UTILIA OS lo recorta antes de servirlo y conceder un permiso fuera de plan responde
400 SCOPE_OUT_OF_PLAN. Un cambio de plan nunca borra permisos ya concedidos: dejan de resolverse.
Avisos: dieciséis eventos nuevos en dos dominios
- Suscripciones (ocho):
SUBSCRIPTION_CREATED,_UPDATED,_PAUSED,_RESUMED,_CANCELLED,_COMPLETED,_INVOICE_GENERATEDy_PAYMENT_AUTHORIZATION_PENDING. - Cobros programados (ocho):
CHARGE_SCHEDULED,_STARTED,_SUCCEEDED,_FAILED,_REQUIRES_ACTION,_RETRY_SCHEDULED,_RETRIES_EXHAUSTEDy_CANCELLED. - Dos más en dominios existentes:
PAYMENT_METHOD_EXPIRINGyPAYMENT_AUTHORIZATION_CONSUMED. INVOICE_SENTya se emite. Estaba declarado desde el principio y nunca llegaba a nadie.PAYMENT_AUTHORIZATION_GRANTEDy_REVOKEDpasan a dirigidos cuando el recurso pertenece a un usuario de una aplicación externa. Antes viajaban siempre por difusión.- En
CHARGE_REQUIRES_ACTION, la dirección del enlace solo viaja a veces: sí cuando el cobro lo lanzó la aplicación, nulo cuando lo lanzó el motor de cobros programados, porque de un enlace emitido solo se guarda su huella. En los dos casos llegapublicLinkAvailable.
2026-09-03
Ámbitos: el portal del cliente sale de «Modificar contactos»
- CAMBIO QUE ROMPE:
portal_accessexige ámbitos propios. Sus lecturas exigencrm:portal:viewy sus diez escriturascrm:portal:manage; ya no basta concrm:contacts:update. - Sin red de compatibilidad, a propósito. Los tokens con
crm:fullsiguen funcionando sin tocar nada; los demás se reautorizan pidiendo la familia nueva, que el descubrimiento del servidor de CRM ya publica. - La lectura queda fuera de
crm:readpese a terminar en:view, y la gestión fuera decrm:write. Mismo criterio que las personas de contacto del proveedor.
Fusión de duplicados: cuatro ámbitos nuevos que ningún paquete expande
crm:clients:merge,crm:contacts:mergeycrm:leads:merge. Gobiernan las operacionesdeduplicateymergede clientes, contactos y leads. Fusionar archiva o borra fichas y reescribe sus punteros en medio centenar de tablas, sin vuelta atrás desde la aplicación.- Quedan fuera de
crm:read,crm:writey también decrm:full, aunque ese paquete expanda su dominio por prefijo. La exclusión es explícita: dejarlos entrar por prefijo se lo habría concedido, sin ningún acto nuevo del titular, a toda integración autorizada hace meses concrm:full. Se piden por su nombre. - Las previas por reglas siguen siendo baratas.
deduplicate_previewde clientes y contactos es lectura pura bajo el ámbito de ver, así que se puede enseñar el trabajo pendiente a una persona sin pedir el de fusionar. La previa demerge, en cambio, la ejecuta la misma operación destructiva y exige el ámbito de fusión. dryRunes lo único que distingue una previa de una fusión aplicada: las dos respuestas tienen la misma forma. Quien no lo mire dará por fusionado lo que sigue intacto.crm_clients: mergeañade dos banderas que exigen alcance total sobre el permiso de fusionar:allowDifferentTaxId(dos identificadores fiscales distintos son la misma empresa, sin excepción si las dos fichas tienen facturas emitidas) yallowPortalAccessMerge(los interlocutores externos de una empresa pasan a ver los documentos de la otra).
SDK JavaScript 2.27.0 y SDK Python 2.27.0
sdk.mcp.crmes un servicio nuevo, contraPOST /api/mcp/crm, con los sub-espaciosclients,contactsyleads: altas y ediciones sueltas y en lotes de hasta 50, duplicados, fusión con su previa, búsqueda de datos de empresa en internet, consentimiento de correo del contacto y transiciones del lead.- El CRM gana la fusión de duplicados en los dos SDK:
deduplicatePreview,deduplicate,mergePreviewymergeen clientes y contactos, ymergePreview/mergeen leads. Los métodos de fusión envían la confirmación por ti; los de previa, no, y ahí está toda la diferencia. - Guía nueva de punta a punta: Poner al día una cartera de clientes, con lotes, logotipos, fusión con previa y espejo incremental por cursor, en los dos SDK.
2026-09-02
SDK JavaScript 2.26.0 y SDK Python 2.26.0: buscador global y avisos
- Dos herramientas MCP transversales nuevas.
sdk.mcp.search(buscador global del ecosistema, publicado en los servidores de gestión del trabajo, CRM y Finanzas) ysdk.mcp.notifications(avisos de la persona que autorizó el token y sus preferencias, en los siete servidores). Tienen página propia. - Los datos bancarios y fiscales dejan de viajar por defecto. Ámbitos nuevos
finance:suppliers:view-sensitiveycrm:clients:view-sensitive, fuera de los conjuntos de lectura y escritura. Los tokens emitidos antes los conservan hasta el 2 de marzo de 2027. - REGRESIÓN DECLARADA:
dev:writeycrm:writedejan de borrar archivos, versiones y carpetas del gestor. Para borrar hace faltadev:files:fullocrm:files:delete. En contrapartida,dev:writeganadev:files:manage. - Vincular archivos a una ficha exige
FILE_MANAGER_LINK_CRM, noCLIENTS_EDIT, y las herramientas de archivos admiten trece tipos de ficha en vez de siete. - El cliente MCP deja de tragarse el mensaje de los errores de una tool: lee las tres formas que conviven, añade la pista al mensaje y conserva el código original en
errorCode.
SDK JavaScript 2.25.0 y SDK Python 2.25.0: fusión con superviviente elegido
- La fusión de duplicados deja de imponer quién sobrevive:
deduplicate({ survivorIds })elige como mucho uno por grupo, y llamar sin argumentos se comporta exactamente igual que antes. Un identificador que no manda sobre nada es un error, no un valor descartado en silencio: la operación es todo o nada. - La previa devuelve por fin los números de todos los miembros, el superviviente propuesto incluido: sin ellos no se puede saber qué se movería al elegir a otro.
2026-09-01
SDK JavaScript 2.24.0 y SDK Python 2.24.0: las personas del proveedor dejan de ser contactos del CRM
- CAMBIO DE SUPERFICIE. Las personas de contacto de un proveedor pasan a ser una entidad propia de Finanzas que vive dentro del proveedor, con su propio identificador y sus propios permisos. El motivo es de producto: hasta entonces, para ver con quién se habla en un proveedor hacía falta tener contratado el CRM.
linkContactyunlinkContactdesaparecen y no se sustituyen por nada: ya no hay nada que atar ni que soltar. El equivalente escreateContactpara el alta ydeleteContactpara la baja.getContactydeleteContactson nuevos, yupdateContactcambia de firma: direcciona la persona por su propio identificador, no por el par proveedor más contacto.
2026-08-05
SDK JavaScript 2.23.0 y SDK Python 2.23.0: campañas, listas y entregabilidad de Marketing
sdk.mcp.marketingpasa a cubrir SEIS herramientas enteras. Se añadencampaigns(marketing_campaigns, 35 operaciones),lists(marketing_lists, 17) ydeliverability(marketing_deliverability, 10), yreportsse completa concomplaints,linkHeatmapyaudienceTech(doce en total). Los mismos nombres ensnake_caseen Python, en variante asíncrona y síncrona.- Qué hizo la audiencia y segundos intentos:
engagementBreakdown(gratuito),resendPreview,createResendylistResends. Los siete grupos no son porciones de una tarta —openedya contiene aclicked— y «no abrieron» excluye siempre a quien pulsó. Un segundo intento nunca escribe a quien se quejó, exige un asunto distinto del original, admite como mucho dos por campaña y no toca los contadores de la campaña original. listRecipientsdeja de perder el filtro de EXCLUIDOS (isExcluded) y ganaengagement, ortogonal al resultado de envío.- Interés de los suscriptores y apartado de inactivos:
listSubscribers(que no existía en MCP, así que apartar era un viaje de ida),engagementSummary,setAsideInactive(vista previa por defecto) yreactivateSubscriber. Apartar no es dar de baja: no se toca el consentimiento. - Política de quejas:
getComplaintPolicyysetComplaintPolicy. El inquilino solo puede ser más estricto (10 a 30 puntos básicos); el techo del 0,3 % y el bloqueo del servidor compartido son de plataforma. Antes de un bloqueo hay siete días sostenidos y un aviso por correo con 48 horas de antelación, y se levanta solo cuando la tasa baja. - La tasa de quejas es
nullcuando no hay forma de medirla y un SUELO cuando la hay. Nunca es un cero. - HUECO ESCRITO:
deviceType,emailClientycountrydelistRecipientsse omiten sin el permisoEMAIL_MARKETING_VIEW_RECIPIENT_DETAILy el scope nuevomarketing:recipients:detail:view. La operación responde igual y llegarecipientDetailIncluded: false: el recorte va por campo y no por operación para que ningún token ya emitido pierda la operación. - HUECO ESCRITO: el mapa de calor por enlace no existe para
automatizaciones ni para las campañas anteriores a la medición por enlace.
Eso no es un cero. El desglose por dirección de destino (
clickThrough) sigue disponible para todo y es gratuito. - Scope nuevo en el servidor Marketing:
marketing:recipients:detail:view. Documentado en Descubrimiento OAuth y en Servidor MCP Marketing.
SDK JavaScript y SDK Python: informes de Marketing
- Nuevo
sdk.mcp.marketing.reports, la herramienta MCPmarketing_reportsentera:campaignStats,engagementOverview,clickThrough,topCampaigns,engagementHeatmapyengagementTrends(los mismos seis ensnake_caseen Python, en variante asíncrona y síncrona). Solo lectura y sin capacidad de plan. Detalle en Servidor MCP Marketing. - Las cifras de campaña cambian de base, no solo de nombre: aparecen
accepted,delivered(que valenullsin detección de devueltos),acceptanceRate,clickToOpenRate,metricsAvailabilityybounceDetectionEnabled;deliveryRatequeda sustituida poracceptanceRate, la tasa de rebote pasa a calcularse sobre los aceptados y el recuento de rebotados deja de incluir los fallos de salida. Quien tuviera cifras guardadas verá números distintos para las mismas campañas. Las definiciones, con su denominador, en Definiciones de las métricas de Marketing. sendStatusdeja de admitirOPENEDyCLICKEDal filtrar destinatarios: nunca fueron estados de entrega y filtrar por ellos devolvía una lista vacía sin dar ningún error. Ahora responde con un error que lo explica. Usa las marcas de tiempoopenedAtyclickedAt.- Los errores de plan se distinguen de los de permiso:
isPlanError()/is_plan_errory el objetoplanContext. Ojo, por el carril MCP ese código no viaja: ver Errores de plan.
SDK JavaScript y SDK Python: medición del origen y negocio por campaña
- Nuevo
sdk.mcp.marketing.trackingSettings/tracking_settings, la herramienta MCPmarketing_tracking_settingsentera desde su primera versión:get,updateypreviewLink/preview_link. Configura qué etiquetas de origen se añaden a los enlaces de los correos y si viaja el identificador del destinatario.previewLinkes una función pura: no firma nada y no emite ningún identificador real. - Scope OAuth nuevo
marketing:tracking:manage, que no entra enmarketing:writey hay que pedir de forma explícita (o usarmarketing:full). En la pantalla de autorización aparece con su propio bloque. Ver Descubrimiento OAuth y Pantalla de consentimiento. sdk.mcp.marketing.reportsse completa con la atribución de ingresos:campaignAttribution,attributionSummaryyentityOrigin(los mismos ensnake_caseen Python).topCampaignsadmite ademásCONVERSIONS(oportunidades originadas, no ventas cerradas),REVENUE_ISSUEDyREVENUE_COLLECTED; hasta ahoraCONVERSIONSdevolvía un error diciendo que el producto no las registra.- Dos huecos que hay que leer antes de integrar. Los importes de la
atribución no viajan por el servidor MCP de marketing (su token no puede
portar scopes de finanzas): llegan los recuentos y el bloque de dinero se
declara en
excludedBlocksconreason: 'missing_scope', que no es un cero. Y las facturas creadas consdk.invoices.create()(API externa v1) nacen sin vínculo comercial y quedan fuera del negocio originado por cualquier campaña, porque esa API no admiteopportunityId. - Los formularios de captación ganan
captureUtmyutmPersistenceen los tipos delead_forms_createylead_forms_update, y el envío público admite_utmy_ur. Aviso para quien incruste el widget: se sirve con una caché de hasta 24 horas sobre una dirección sin versión, así que los sitios ya publicados pueden tardar ese tiempo en tomar la versión nueva. Ver Insertar un formulario en tu web.
SDK JavaScript y SDK Python: enlaces con el dominio del cliente
- Nuevo
sdk.mcp.marketing.trackingDomains/tracking_domains, la herramienta MCPemail_tracking_domainsentera desde su primera versión:list,get,getSetupInstructions,create,verify,setEmission,retryCertificateyretire(los mismos ensnake_caseen Python, en variante asíncrona y síncrona). Hace que los enlaces medidos de las campañas salgan por un subdominio del cliente en vez de por un dominio de UTILIA, que es lo último que quedaba con efecto real sobre la entregabilidad: los filtros antispam penalizan que los enlaces de un correo apunten a un dominio ajeno al remitente. - Ningún scope nuevo. Se reutilizan
marketing:deliverability:viewymarketing:deliverability:manage, que ya existían. Las tres lecturas admiten ademásmarketing:campaigns:view, porque el permiso que pide el REST es la vista del módulo y sin esa alternativa el token cerraría lo que el permiso abre. - Ninguna operación borra nada, y es una decisión.
retireprograma el fin del servicio y devuelve enserveUntil/serve_untilla fecha hasta la que ese subdominio sigue atendiendo los enlaces de los correos ya entregados. Un enlace de un correo ya enviado no se puede romper: lleva su dominio escrito dentro y no hay forma de reescribirlo. createno empieza a medir. El alta solo abre el proceso; la medición se enciende consetEmissioncuando el dominio está sirviendo. Informar de que «ya se mide por su dominio» justo después de crearlo es falso.- Aviso que hay que trasladar al cliente: si retira su registro CNAME, el
certificado deja de poder renovarse y a los 90 días como mucho los enlaces
de sus correos YA ENVIADOS dejan de abrirse con un aviso de seguridad del
navegador.
certificateExpiresAt/certificate_expires_attrae la fecha exacta. Ver Enlaces con tu propio dominio. - Puerta de plan:
create,verify,set_emissionyretry_certificateexigen las dos capacidades del lote de medición, «Analítica avanzada de Marketing» y «Negocio por campaña». Quedan exentas las tres lecturas yretire, que es la salida: quien deja de tener las capacidades sigue viendo su subdominio y puede soltarlo cuando quiera.
2026-08-03
SDK JavaScript 2.22.0 y SDK Python 2.22.0
- Imágenes de presupuesto completas.
sdk.budgetsganauploadImageToSection/upload_image_to_section(colocar una imagen dentro de un apartado),scanLegacyImages/scan_legacy_images(detectar las imágenes heredadas incrustadas en el HTML, que se ven en el editor y no salen en el PDF) ymigrateLegacyImages/migrate_legacy_images(convertirlas en colocaciones reales, de forma idempotente y tolerando fallos parciales). textWrapqueda obsoleto: el generador del PDF nunca lo aplicó, la imagen siempre ocupa su propia línea. Se sigue aceptando y el servidor lo ignora.- La opción
deleteFileal borrar una imagen queda obsoleta: el borrado del binario pasó a ser automático por recuento de referencias. - Cambios aditivos, sin rupturas de contrato.
2026-07-28
SDK JavaScript 2.21.0 y SDK Python 2.21.0
sdk.oauth.getAuthorizedApps()/get_authorized_apps()devuelve los permisos ya traducidos y recortados por el plan de la organización:permissions,permissionCount,unavailablePermissionCountyunavailableModules. Todos los campos son opcionales, para no romper contra un backend anterior.AuthorizedApp.scopequeda obsoleto (retirada prevista el 2027-02-01) y cambia de significado: ya no es el universo de scopes concedidos a la aplicación, sino ese universo recortado por el plan. Quien lo use para decidir qué puede hacer la aplicación verá menos de lo que hay; sustitúyelo porpermissionsyunavailablePermissionCount.
2026-07-12
SDK JavaScript 2.20.0 y SDK Python 2.20.0
- Plataforma de chat extensible: nuevo servicio
sdk.chatsobre/external/v1/chat, con publicación de mensajes como aplicación (v1) y lectura de canales, miembros e historial visible (v2), mássdk.chat.webhooks.verify()para validar localmente la firma HMAC. - Detalle completo en Plataforma de chat.
2026-07-05
API externa de clientes: grupos de empresas
- El CRM incorpora los grupos de empresas (matriz y filiales, dos niveles) y la API externa
/external/v1/clientslos refleja de forma aditiva:parentClientIden todas las respuestas,parent { id, name, slug }en el detalle y el endpoint nuevoGET /external/v1/clients/:id/group. PATCH /external/v1/clients/:idaceptaparentClientIdpara vincular una filial a su matriz o desvincularla connull. Exige el scope nuevocrm:clients:write:hierarchyademás decrm:clients:write; las API Keys existentes NO lo obtienen automáticamente. El catálogo de scopes pasa de 35 a 36.- Webhooks nuevos
CLIENT_LINKED_TO_GROUPyCLIENT_UNLINKED_FROM_GROUP, con payload de solo identificadores. La mutación del vínculo NO disparaCLIENT_UPDATED. - Errores nuevos:
409 CLIENT_HIERARCHY_CONFLICTy422 CLIENT_HIERARCHY_INVALID_TYPE. - Detalle en Endpoints de clientes del CRM y Gestión de scopes.
SDK JavaScript 2.18.0 y SDK Python 2.18.0
sdk.externalClients.getGroup(id)/sdk.external_clients.get_group(client_id)y soporte deparentClientIdenupdate(en Python,parent_client_idconNoneexplícito para desvincular).- Tipos y modelos nuevos:
ExternalClientParent,ExternalClientHierarchyRole,ExternalClientGroupResponse. Espejo del catálogo de scopes actualizado a 36 entradas. - Cambios aditivos, sin cambios incompatibles. Matriz completa en Versiones y compatibilidad del SDK.
2026-07-02
SDK JavaScript 2.17.0 y SDK Python 2.17.0
- Nuevo método
sdk.mcp.opportunities.listStages()/list_stages(): devuelve las etapas del pipeline por defecto de la organización, cuyosidson los valores válidos para cambiar de etapa una oportunidad.
2026-05-27
Avisos: la firma HMAC pasa a calcularse sobre timestamp.rawBody (cambio que rompe)
- Todas las entregas de webhook cambian de mensaje firmado. Hasta esta fecha
la cabecera
X-Utilia-Signaturese calculaba sobre el cuerpo crudo a secas; desde este despliegue se calcula sobre la cadena`${timestamp}.${rawBody}`, dondetimestampes exactamente el valor de la cabeceraX-Utilia-Timestamp(milisegundos Unix). Es el patrón que popularizó Stripe y añade defensa anti-replay. - Rompe a todo receptor que no se actualice. Un receptor que siga firmando solo el cuerpo obtendrá siempre una firma distinta y rechazará las entregas legítimas (normalmente con 401). Como un 4xx se trata como fallo permanente, la entrega se cierra sin agotar los reintentos y el evento se pierde.
- Qué hay que cambiar en tu servidor: reconstruye el mensaje como
`${timestamp}.${rawBody}`antes de calcular el HMAC-SHA256, comprueba queX-Utilia-Timestampestá dentro de una ventana de ±5 minutos y compara en tiempo constante. Ejemplos completos en Node.js y Python en Firma HMAC de los avisos. - El envío de prueba del panel no acompañó al cambio en ese momento y seguía firmando solo el cuerpo. Ya está corregido: hoy la prueba firma exactamente igual que las entregas reales. Detalle en Entregas y reintentos de los avisos.
2026-05-12
MCP: la herramienta tickets puede crear tickets
- Nueva operación de creación de tickets de soporte vía MCP. Ya estaba disponible la lectura y la actualización; ahora también la creación.
- Quien abre el ticket es siempre la persona del token; no se puede abrir en nombre de otra.
- Se quita el HTML de
titleydescriptionantes de guardarlos, para que no pueda ejecutarse código si el contenido se muestra como HTML. - Límite propio del canal MCP: como mucho 10 tickets por hora y persona. El límite general de MCP (60 peticiones por minuto) sigue aplicándose por encima.
- Trazabilidad en la actividad del ticket:
TICKET_CREATEDregistra el canal (source: 'mcp') y eloauthClientIddel token de origen.
OAuth: el conjunto support:write
- El conjunto
support:writeahora se expande asupport:tickets:createademás desupport:tickets:update. Los clientes OAuth consupport:writeautorizado antes del 12 de mayo de 2026 pueden crear tickets sin volver a dar su consentimiento. Consulta los detalles en la referencia de scopes.
2026-04-08
SDK JavaScript 1.8.0
- Nuevo parámetro
themeengetAuthorizationUrl(),getLoginUrl()yloginWithPopup(): permite solicitar tema claro ('light') u oscuro ('dark') en la pantalla de consentimiento OAuth.
SDK Python 0.7.0
- Nuevo parámetro
themeenget_authorization_url()yget_login_url()(versión asíncrona y síncrona): permite solicitar tema claro ('light') u oscuro ('dark') en la pantalla de consentimiento OAuth.
2026-04-05
Documentación de OAuth
- Página nueva para obtener un token en cinco minutos: OAuth: tu primer token en 5 minutos.
- Página nueva de botones y marca, con logotipos, colores, tipografía y código del botón «Iniciar sesión con UTILIA».
- Página nueva de problemas frecuentes, con los diez fallos más comunes al integrar OAuth.
- Reescritas las seis páginas de referencia (visión general, descubrimiento, flujo de autorización, token y revocación, scopes y recursos, pantalla de consentimiento), con ejemplos en curl, JavaScript y Python.
2026-04-02
OAuth en el panel de aplicaciones
- OAuth integrado en cada aplicación externa (hoy, pestaña OAuth de su ficha).
- Configuración del nombre, las direcciones de retorno y los scopes.
- Generación y regeneración de Client ID y Client Secret.
- Visualización y revocación de aplicaciones autorizadas.
Soporte OIDC (OpenID Connect)
- Endpoint
/.well-known/openid-configurationpara descubrimiento automático. - Endpoint
/api/oauth/userinfopara obtener claims del usuario autenticado. - Endpoint
/api/oauth/jwkspara verificación de firma de ID tokens. - ID tokens JWT con claims estándar:
sub,name,email,picture, etc. - Scopes OIDC:
openid(obligatorio),profile,email.
SDK JavaScript 1.7.0
- Flujo OAuth 2.1 simplificado:
getAuthorizationUrl()gestiona PKCE automáticamente. handleCallback(code): recupera el code_verifier sin parámetros manuales.- Validación automática del estado CSRF en
handleCallback. isAuthenticated(): verifica si hay sesión OAuth activa.logout(): revoca tokens y limpia el almacenamiento.loginWithPopup(): inicio de sesión OAuth mediante ventana emergente para SPAs.ErrorCode.AUTH_EXPIRED: código de error nuevo para sesiones OAuth caducadas.- Estado PKCE persistente en
MemoryTokenStorageyFileTokenStorage.
SDK Python 0.6.0
- Flujo OAuth 2.1 simplificado:
get_authorization_url()gestiona PKCE automáticamente. handle_callback(code): recupera el code_verifier sin parámetros manuales.- Validación automática del estado CSRF en
handle_callback. is_authenticated(): verifica si hay sesión OAuth activa.logout(): revoca tokens y limpia el almacenamiento.ErrorCode.AUTH_EXPIRED: nuevo código de error para sesiones OAuth expiradas.- Corrección crítica: el cliente síncrono ahora comparte storage con el asíncrono.
- Estado PKCE persistente en
MemoryTokenStorageyFileTokenStorage.
Versiones vigentes
| Pieza | Versión |
|---|---|
| API externa | v1 |
SDK de JavaScript (@utilia-os/sdk-js) | 4.1.0 |
| SDK de Python | 4.1.0 |
Widget (@utilia-os/widget) | 1.1.0 |