Referencia de las instalaciones
Las tres rutas de /external/v1/app-installations: canjear el código de una instalación, listar las instalaciones de tu aplicación y consultar una, con la suscripción de las de pago.
Actualizado el . 7 min de lectura
Tres rutas de la API externa para el servidor de la aplicación que publica una ficha en el catálogo: canjear el código de cada instalación y consultar qué empresas la tienen instalada. Todas se llaman con la clave de tu aplicación en X-Api-Key, no con la de una instalación, y solo ven las instalaciones de tu propia ficha.
Requisitos
- La clave de la aplicación que publica la ficha, en
X-Api-Key. - Dos permisos de la API de esa aplicación, que nunca se piden a una empresa: «Recibir las credenciales de cada instalación» (
apps:installations:exchange) para el canje y «Ver las instalaciones de esta aplicación» (apps:installations:read) para las consultas.
Ninguna de las tres devuelve personas de la empresa que instala: solo la empresa, el estado, los permisos y, si tu aplicación es de pago, su suscripción.
Canjear el código
/api/external/v1/app-installations/exchangeCanjea el código de una instalación por su clave de API y su clave de firma, que se entregan una sola vez. Solo desde un servidor. Permiso: apps:installations:exchange.
- codestring (10 a 200)obligatorio
- El código que llega en la dirección de instalación (?code=). Vale 30 minutos y una sola vez.
Responde 200 con installation y credentials y la cabecera Cache-Control: no-store. La forma completa y cómo guardarlas están en Canjear el código.
| errorCode | HTTP | Cuándo |
|---|---|---|
EXCHANGE_FROM_BROWSER | 403 | La petición trae Origin o Sec-Fetch-Site: llega desde un navegador. |
INSTALLATION_CODE_WRONG_APP | 403 | El código es de una ficha de otra aplicación de tu organización. |
INSTALLATION_CODE_INVALID | 404 | El código no existe para tu aplicación, o la instalación está desinstalada o suspendida. |
INSTALLATION_CODE_USED | 409 | Ya se canjeó. |
INSTALLATION_CODE_EXPIRED | 410 | Pasaron los 30 minutos. |
Listar tus instalaciones
/api/external/v1/app-installationsEmpresas que han instalado la ficha de tu aplicación, de la más reciente a la más antigua, con su estado y, si es de pago, su suscripción. Permiso: apps:installations:read.
- statusquery
- Solo las de un estado guardado: PENDING_CONNECTION (sin canjear, vigente o caducado), ACTIVE (también las que piden más permisos), SUSPENDED o UNINSTALLED.
- subscriptionStatusquery
- Solo las de una suscripción en este estado: TRIALING, ACTIVE, PAST_DUE, PAUSED o ENDED. En una ficha que no es de pago no devuelve ninguna. Se combina con status.
- limitquery (1 a 100)
- Instalaciones por página. Por defecto, 50.
- cursorquery
- El nextCursor de la página anterior.
{
"items": [
{
"id": "cm3k9x2a40001qz8h7d1v2b3c",
"organizationId": "cm1a2b3c40000ab12cd34ef56",
"organizationName": "Ferretería López",
"status": "ACTIVE",
"installedVia": "CATALOG",
"installedAt": "2026-11-03T09:14:27.000Z",
"connectedAt": "2026-11-03T09:15:02.000Z",
"uninstalledAt": null,
"uninstallReason": null,
"grantedScopes": ["crm:clients:read", "crm:contacts:read"],
"pendingScopes": [],
"subscription": null
}
],
"nextCursor": null
}Consultar una instalación
/api/external/v1/app-installations/:idUna instalación de la ficha de tu aplicación, con la misma forma que cada elemento de la lista, también su suscripción si es de pago. Permiso: apps:installations:read.
- idpathobligatorio
- El identificador de la instalación: el installation_id de la dirección de instalación y de la cabecera X-Utilia-Installation-Id.
Una instalación que no es de tu ficha, o que no existe, responde 404 APP_INSTALLATION_NOT_FOUND: no se distingue para no revelar instalaciones ajenas.
Los campos de una instalación
- idstringobligatorio
- La instalación.
- organizationIdstringobligatorio
- La empresa que instaló.
- organizationNamestring | nullobligatorio
- Su nombre al instalar.
- statusenumobligatorio
- ACTIVE, CONNECTING (código vigente sin canjear), NEEDS_CONNECTION (el código caducó), ACCESS_PENDING (no ha aceptado los permisos nuevos), SUSPENDED (por UTILIA) o UNINSTALLED.
- installedViaenumobligatorio
- CATALOG (desde Explorar), LINK (ficha «Solo con el enlace») o TEST (una instalación de prueba).
- installedAtstringobligatorio
- Cuándo se instaló, en ISO 8601.
- connectedAtstring | nullobligatorio
- Cuándo canjeó tu servidor el primer código. null si nunca.
- uninstalledAtstring | nullobligatorio
- Cuándo se desinstaló.
- uninstallReasonstring | nullobligatorio
- El motivo que dio la empresa al desinstalar, si quiso contártelo.
- grantedScopesstring[]obligatorio
- Permisos de la API concedidos: los únicos con los que puede llamar su clave.
- pendingScopesstring[]obligatorio
- Permisos de una versión nueva que la empresa aún no ha aceptado.
- subscriptionobject | nullobligatorio
- La suscripción de la empresa si tu ficha es «De pago en UTILIA» (la última, aunque haya terminado). null en las gratuitas y en las de «Gratis con cuenta en el servicio». Ver el apartado siguiente.
El filtro status usa el estado guardado y la respuesta enseña el que ves en la pantalla: con status=PENDING_CONNECTION llegan instalaciones CONNECTING y NEEDS_CONNECTION, y con status=ACTIVE, también las ACCESS_PENDING. Qué significa cada estado y cómo se pasa de uno a otro está en Estados de una instalación.
La lista no avisa de que una empresa se quedó sin persona responsable o sin un aplicativo que tu aplicación necesita: la instalación sigue ACTIVE y son sus llamadas las que responden 403 APP_RESPONSIBLE_INACTIVE o 403 MODULE_NOT_ENABLED.
La suscripción de una aplicación de pago
Si tu ficha es «De pago en UTILIA», cada instalación trae subscription: lo que la empresa contrató y cómo va su pago, para que decidas en tu aplicación si le das acceso. Sin impuestos, tarjeta, facturas, datos fiscales de la empresa ni personas. Una compra que no llega a cobrarse no aparece.
{
"subscription": {
"id": "cm4a1b2c30000xy98zt76uv54",
"status": "ACTIVE",
"accessAllowed": true,
"plan": { "key": "pro", "name": "Pro" },
"interval": "MONTHLY",
"priceCents": 1900,
"currency": "EUR",
"trialEndsAt": "2026-11-05T10:12:00.000Z",
"currentPeriodStart": "2026-11-05T10:12:00.000Z",
"currentPeriodEnd": "2026-12-05T10:12:00.000Z",
"cancelAtPeriodEnd": false,
"pendingChange": { "planKey": "basic", "interval": "MONTHLY", "effectiveAt": "2026-12-05T10:12:00.000Z" },
"pauseReason": null,
"endedAt": null,
"endedReason": null,
"simulated": false
}
}- idstringobligatorio
- La suscripción. Volver a contratar después de terminar crea otra.
- statusenumobligatorio
- TRIALING (prueba gratis), ACTIVE (pagada y al corriente), PAST_DUE (falló un cobro y sigue funcionando mientras dura el plazo), PAUSED (en pausa: tu aplicación se detiene, sin acceso y sin cobros) o ENDED (terminada).
- accessAllowedbooleanobligatorio
- La suscripción deja usar la aplicación: true en TRIALING, ACTIVE y PAST_DUE. No mira el estado de la instalación: una suspendida o desinstalada no se usa aunque esté pagada.
- planobjectobligatorio
- key y name del plan de tu ficha que contrató.
- intervalenumobligatorio
- MONTHLY o YEARLY.
- priceCentsnumberobligatorio
- Precio del periodo, sin impuestos, en céntimos.
- currencystringobligatorio
- Moneda, hoy siempre EUR.
- trialEndsAtstring | nullobligatorio
- Fin de la prueba gratis.
- currentPeriodStartstring | nullobligatorio
- Inicio del periodo pagado en curso.
- currentPeriodEndstring | nullobligatorio
- Fin del periodo pagado en curso.
- cancelAtPeriodEndbooleanobligatorio
- La empresa la ha cancelado: sigue hasta el final del periodo o de la prueba y no se renueva.
- pendingChangeobject | nullobligatorio
- Bajada de plan o cambio de periodicidad para la próxima renovación: planKey, interval y effectiveAt.
- pauseReasonenum | nullobligatorio
- Solo en PAUSED: PAYMENT_FAILED, PLAN_REQUIREMENT_MISSING, LISTING_SUSPENDED, ORGANIZATION_SUSPENDED o DISPUTE_OPEN (la empresa ha abierto una disputa con su banco por un cargo de esta suscripción: no se renueva mientras dure la disputa, y esta pausa no tiene fecha de fin).
- endedAtstring | nullobligatorio
- Solo en ENDED: cuándo terminó.
- endedReasonenum | nullobligatorio
- Solo en ENDED: CANCELED, TRIAL_CANCELED, UNINSTALLED, UNPAID, PAUSE_EXPIRED, LISTING_DISCONTINUED, LISTING_REMOVED, DEVELOPER_CLOSED, ORGANIZATION_CANCELED o REFUNDED.
- simulatedbooleanobligatorio
- Compra simulada en una de tus empresas de prueba: no se cobra nada.
Para saber si una empresa puede usar tu aplicación, mira las dos cosas: que la instalación no esté SUSPENDED ni UNINSTALLED y que subscription sea null o tenga accessAllowed: true. Los SDK lo hacen con isAccessAllowed(instalacion). Los cambios te llegan en los avisos de la suscripción: no hace falta preguntar cada vez.
Comprar, cambiar de plan, cancelar o pagar no se hace por esta API: lo hace la empresa en UTILIA OS.
Con los SDK
Los métodos llegan con la versión 4.7.0 de los dos SDK, y la suscripción, el filtro subscriptionStatus e isAccessAllowed con la 4.8.0, las dos todavía sin publicar. Mientras tanto, las rutas funcionan igual con las peticiones de arriba.
import { UtiliaSDK } from '@utilia-os/sdk-js';
const sdk = new UtiliaSDK({
baseURL: 'https://os.utilia.ai/api',
apiKey: process.env.UTILIA_APP_KEY!, // la clave de la aplicación que publica la ficha
});
const { installation, credentials } = await sdk.appInstallations.exchange(code);
const pagina = await sdk.appInstallations.list({ subscriptionStatus: 'PAST_DUE' });
const una = await sdk.appInstallations.get(installation.id);
const puedeUsarla = isAccessAllowed(una); // import { isAccessAllowed } from '@utilia-os/sdk-js';Los errores llegan como UtiliaSDKError, con el errorCode de las tablas de arriba. Qué hacer con cada uno está en Errores, identificador de petición y reintentos.
Ponlo en práctica
Lo encontrarás en Creadas por tu empresa. Para abrirlo, entra con tu cuenta de UTILIA OS.