SDK para terceros
Guía de integración
Para que tu producto SaaS sepa qué ha comprado cada uno de tus clientes en Mitnix Commerce, lo verifique sin intermediarios y deje de concederlo cuando Commerce lo revoca.
Para el equipo de un tercero que vende su producto SaaS con la plataforma de Mitnix Commerce y quiere que ese producto sepa, sin intermediarios, qué ha comprado cada uno de sus clientes (capacidades, límites, cuotas y vigencia) y deje de concederlo cuando Commerce lo revoca. English version.
Todo lo que aquí se describe está implementado en los cinco paquetes @mitnix-ai-solutions/platform-* 1.0.2 y se ejecuta de principio a fin en el ejemplo tercero-saas, que Mitnix compila y prueba contra los paquetes tal como se publican.
1. Cómo encaja
tu producto SaaS ──token OIDC de tu plataforma──▶ Commerce (/installation/v1/*)
│ │
│ 1 trust/bundle paquete de confianza ◀──┤ (autoridad de licencias de tu tercero)
│ 2 tenant-grants Grants cifrados (JWE) ◀─┤ (uno por cliente, firmados por tu autoridad)
│ 3 trust/operations clave de contenido ◀────┤ (open-grant: Trust la abre; la clave privada nunca sale del HSM)
│ 4 tenant-license-status revocaciones ◀───────┘
▼
verifica con el ancla ▶ concede capacidades, límites y cuotas por cliente (tenant)
- No hay ningún secreto en tu producto. Te acreditas con el token OIDC que tu plataforma de ejecución emite para tu carga de trabajo; Commerce lo verifica contra lo que registraste.
- Verificas tú, offline. Commerce te entrega el Grant, pero lo que lo hace de fiar es su firma (ES256, formato
mitnix.signed-artifact/v3) comprobada contra un ancla que fijas tú. Una licencia de Mitnix o de otro tercero se rechaza. - Commerce deduce quién eres del token (tercero, producto y entorno). El SDK nunca envía tu
vendorId.
2. Instalación
Los cinco paquetes están en npmjs.com y se instalan sin ningún token:
npm install @mitnix-ai-solutions/platform-common@1.0.2 @mitnix-ai-solutions/platform-artifacts@1.0.2 \
@mitnix-ai-solutions/platform-trust@1.0.2 @mitnix-ai-solutions/platform-licensing@1.0.2 \
@mitnix-ai-solutions/platform-commerce-client@1.0.2
Node 22 o superior (fetch y WebCrypto de la plataforma; el SDK no usa tipos de Node ni de DOM). La licencia del SDK es propietaria y gratuita, válida solo para usarlo con Commerce (LICENSE). La procedencia de lo publicado se verifica sin credenciales con el manifiesto firmado por Mitnix (verificación). Las versiones y su compatibilidad con /installation/v1 están en mitnix.net/sdk.
3. Registrar tu producto en la consola de Commerce
Lo hace el administrador de tu tercero en tu consola (https://<tu host>/consola/#/canal, «Identidad de tus productos»). Por cada producto y entorno registra:
| Campo | Qué es |
|---|---|
| Producto | El de tu catálogo, con la forma <tercero>:<producto> (tras los dos puntos: minúsculas, dígitos y guiones internos, hasta 32). |
| Entorno | development, staging o production. Debe ser el entorno en el que vende la instalación de Commerce (la consola lo indica y lo propone). |
| Emisor | El iss exacto del token de tu plataforma. |
| Audiencia | El aud exacto: la que pongas es la que tu producto pedirá al emitir el token. Usa la URL de Commerce. |
| Sujeto | El sub exacto del token de tu carga de trabajo. |
Qué hace Commerce al registrar:
- Descubre el emisor (
<emisor>/.well-known/openid-configuration): elissuerdel documento debe ser exactamente el registrado, y fija eljwks_uri. No vuelve a descubrir nada después. - Exige que el emisor sea
https://con un nombre público (ni IP literal, ni puerto, ni credenciales, nilocalhost) y que resuelva solo a direcciones públicas. - Una identidad (emisor y sujeto) es de un solo producto y entorno en toda la plataforma.
- Pide a Trust la identidad de custodia
tercero.<tercero>.<producto>del entorno. Un responsable de seguridad de Mitnix la aprueba. Con «Comprobar custodia», cuando está aprobada y activa, Commerce registra tu instalación SaaStercero.<tercero>.<producto>.<entorno>(por ejemplotercero.prueba.excerptia.staging): es el destinatario de tus Grants y elinstallationIdque usa tu producto.
Cambiar la identidad de un producto y entorno ya registrado (otro emisor, audiencia o sujeto) la sustituye sin perder la custodia: no hay que volver a aprobarla.
4. El token de tu carga de trabajo
Tu plataforma de ejecución emite un token OIDC (JWT) para tu servicio. Commerce lo acepta solo si:
- está firmado con RS256 o ES256 (P-256) con una clave del
jwks_urifijado, y llevakid; iss,subyaudson exactamente los registrados;- lleva
expeiat, vive 24 horas o menos (exp−iat), no ha caducado y no se emitió ni empieza (nbf, si lo lleva) en el futuro, con 30 s de tolerancia de reloj.
Cómo se obtiene en cada plataforma lo explica la documentación de la plataforma; lo que Commerce exige es lo anterior. Verificado en Commerce hoy: Cloud Run (es como se identifican las aplicaciones de Mitnix): GET http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=<audiencia> con la cabecera Metadata-Flavor: Google; dura una hora. En Kubernetes (token de cuenta de servicio proyectado con la audiencia pedida), AWS, Azure o un CI con OIDC rige lo mismo: tu emisor tiene que ser público y descubrible por Commerce desde Internet. Un emisor privado (un clúster sin su documento de descubrimiento público) no se puede registrar.
El SDK no guarda el token ni su caché: llama a tu proveedor en cada petición (tokenProvider({ forceRefresh })) y lo pone solo en Authorization: Bearer. El token nunca aparece en una URL, un cuerpo, un error ni un registro. Tras un 401 pide uno nuevo (forceRefresh: true) y repite una sola vez.
const commerce = createCommerceChannelClient({
commerceUrl: "https://<la URL de Commerce que te indique Mitnix>",
tokenProvider: async ({ forceRefresh }) => pedirTokenALaPlataforma({ audience, ignorarCache: forceRefresh }),
});
Para desarrollo y pruebas, sin plataforma de ejecución, puedes usar un emisor OIDC tuyo con client_credentials (es lo que hace la prueba en staging del ejemplo); el ejemplo lo trae como una fuente de token más (MITNIX_TOKEN_URL).
5. El ancla de confianza
Para verificar licencias sin consultar a nadie, tu producto fija un ancla (solo claves públicas, nada secreto):
const ancla: VendorTrustAnchor = {
vendorId: "acme", // tu identificador de tercero
environment: "production", // el `environment` del paquete que firma Trust (ver nota)
trustUpdatePublicKey: { kty: "EC", crv: "P-256", x: "…", y: "…" }, // clave pública de la autoridad trust-update de Mitnix
};
- El paquete de confianza (
mitnix.trust.vendor-bundle/v1) lo firma la autoridadtrust-updatede Mitnix y contiene la raíz de terceros y las autoridades de licencias de tu tercero con sus claves públicas, más sus revocaciones. Si alguien te sirviera un paquete firmado con otra clave, de otro tercero o de otro entorno,applyVendorTrustBundlelo rechaza (VENDOR_BUNDLE_SIGNATURE_INVALID,…_SCOPE_MISMATCH,…_ENVIRONMENT_MISMATCH). - Nota sobre
environment: es el entorno con el que Trust firma, no el entorno de tu aplicación. Trust solo admite hoyproductiony lo usa también en staging, de modo que el paquete de staging llevaenvironment: "production". Compruébalo en el paquete que recibes (payload.environment). - Dónde se obtiene: en tu consola de Commerce, «Identidad de tus productos» (
https://<tu host>/consola/#/canal), sección Ancla de confianza. La ve cualquier miembro de tu equipo, también el lector. Muestra tuvendorId, elenvironmentque debes poner (el que Trust escribe de verdad en tu paquete, con una nota si no coincide con el entorno de venta) y la clave pública detrust-updateen JWK con su huella SHA-256 (RFC 7638). «Copiar ancla» y «Descargar JSON» te dan el objetoVendorTrustAnchortal cual; «Copiar clave (JWK)» da solo la clave, que es lo que espera el ejemplo enMITNIX_TRUST_UPDATE_JWKoMITNIX_TRUST_UPDATE_JWK_FILE. Commerce la toma de la distribución de confianza de Trust y solo te la muestra si verifica la firma del paquete que te sirve. Si aún no tienes paquete (Trust no ha aprobado tu autoridad de licencias), la sección te lo dice. - Comprueba la huella antes de fijar el ancla: es el SHA-256 en base64url del JSON
{"crv":"P-256","kty":"EC","x":"…","y":"…"}(claves en ese orden y sin espacios), y debe coincidir con la que ves en la consola. - Rotación: cuando Mitnix rota la clave de
trust-update, la consola muestra más de una. «En uso» es la que firma el paquete que Commerce te sirve ahora; «Vigente» o «Siguiente» son las que vienen. El ancla del SDK admite una sola clave (trustUpdatePublicKey) y el verificador de Grants vuelve a comprobar el paquete guardado con ella. Por eso el cambio se hace así: prepara la clave nueva y, cuando la consola la marque «En uso», sustituye la del ancla y aplica enseguida el paquete nuevo (applyVendorTrustBundle). Mientras no cambies el ancla, el paquete nuevo se rechaza conVENDOR_BUNDLE_SIGNATURE_INVALIDy tu producto sigue con el último que aplicó.
6. El flujo en código
El ejemplo lo implementa en src/licencias.ts (clase ServicioDeLicencias). Los pasos:
// 1. Paquete de confianza: se aplica tal cual lo sirve Commerce (base64url). Si falla, se conserva el último válido.
const entregado = await commerce.trustBundle(); // { vendorId, sequence, bundleId, bundle }
const aplicado = await applyVendorTrustBundle({ bundle: entregado.bundle, anchor: ancla, store: almacenDelPaquete });
// 2. Grants cifrados: uno por cliente (tenant).
const { grants } = await commerce.tenantGrants(); // [{ grantId, encryptedDocument }]
// 3. Abrir el JWE. open-grant pasa por Commerce hasta Trust: el SDK extrae la clave cifrada, la envía y descifra en local.
const abierto = await openTenantGrantJwe(grant.encryptedDocument, commerce.openGrant, { expectedInstallationId: installationId });
// 4. Verificar y aplicar: firma de TU autoridad, destinatario, vigencia, secuencia por tenant…
const runtime = new TenantGrantRuntime(almacenDeTenants, createAnchoredTenantGrantVerifier({ anchor: ancla, bundleStore: almacenDelPaquete }));
const r = await runtime.apply(abierto.value, { installationId, installationKind: "saas", now: new Date().toISOString(), expectedAuthority: { vendorId: ancla.vendorId } });
// 5. Revocaciones: lo que Commerce ha revocado después de firmar.
const estado = await commerce.tenantLicenseStatus(); // { tenants: [{ commercialAccountReference, grantId, revokedAddons, revokedAt?, confirmedEntitlements }] }
Qué garantiza cada paso:
openTenantGrantJwevalida el JWE (RSA-OAEP-256+A256GCM,encrypted_keyde 384 bytes), rechaza antes de gastar una operación de Key Vault si el destinatario no es tu instalación, y borra los bytes de la clave de contenido tras usarla. El resultado es el documento firmado sin verificar: descifrar no prueba quién lo firmó.TenantGrantRuntime.applyvalida en el orden normativo (TENANT_GRANT_VALIDATION_ORDER). ConexpectedAuthority: { vendorId }solo valetercero-<vendorId>-licenciasy sus generaciones-g<n>: una licencia de Mitnix o de otro tercero daLG-CHAIN-INVALID. El verificador anclado solo acepta una autoridad que el último paquete declara activa, sin revocar, vigente cuando firmó y al verificar.- Una autoridad revocada en un paquete nuevo hace que sus Grants den
LG-AUTHORITY-REVOKED.
Lo que aporta tu producto: los almacenes
El SDK no persiste nada (ni crea temporizadores). Tu producto implementa:
VendorTrustBundleStore: el último paquete aplicado y una marca de agua monótona (la mayor secuencia aplicada) en un sitio donde restaurar una copia antigua del paquete no la arrastre. Es el anti-rollback: un paquete viejo está bien firmado, así que solo la secuencia lo detiene.TenantGrantStateStore: el estado protegido por tenant (Grant vigente, secuencia más alta aceptada, Grants sustituidos) con sustitución atómica condicionada.
El ejemplo usa los almacenes efímeros del SDK (createEphemeral…), que no son para producción: no persisten ni resisten un rollback.
7. Capacidades, límites y cuotas
Con el Grant aplicado (AppliedTenantGrant, de store.load(tenantRef)):
| Necesitas | Función | Resultado |
|---|---|---|
| ¿Puede usar la capacidad ahora? | requireTenantCapability(grant, capabilityId, ahora) / tenantHasCapability |
CAPABILITY_NOT_GRANTED sin Grant vigente, sin la capacidad o con su entitlement vencido |
| Vigencia del contrato | tenantGrantTermStatus(grant, ahora) |
not-yet-valid, active o expired (desde la entrada en vigor hasta term.validUntil, incluido) |
| Vigencia de un complemento | tenantEntitlementStatus / tenantEntitlementValidUntil |
Cada entitlement puede tener su validUntil; nunca pasa del fin del contrato |
| Gracia tras vencer | tenantGrantGrace(grant, ahora, politicas) |
Las políticas (TenantGracePolicy) las declaras tú; el SDK no trae ninguna |
| Límite de stock (p. ej. usuarios) | admitStockOperation(grant, limitId, cuantos, ahora) |
STOCK_LIMIT_REACHED si ya alcanzaste el máximo |
| Cuotas por periodo y lotes prepago | computeQuotaBalance, planReservation, closeReservation, replayConsumptionLedger |
Reserva al aceptar, cargo al completar, liberación al fallar; periodo calendar-month-utc y después los lotes por caducidad más próxima |
El libro de consumo (ConsumptionLedgerEntry) es tuyo: el saldo se reconstruye desde el Grant y el libro; un libro inconsistente da LEDGER_INCONSISTENT y no produce saldo.
Combina el Grant con el estado de Commerce. El Grant firmado dice qué se compró y hasta cuándo; no dice que luego se devolvió. Commerce lo revoca (devolución, disputa perdida, impago) y lo anuncia en tenant-license-status: revokedAddons (todos los confirmados si el Grant está revocado) y revokedAt. Además, confirmedEntitlements es lo que Commerce confirma como adquirido: una capacidad firmada que no figura ahí no se concede. El ejemplo (describir en licencias.ts) hace esta combinación y falla cerrado: sin estado de Commerce, no concede nada.
8. Frecuencia de consulta recomendada
Es una recomendación del SDK, no un requisito del servidor (Commerce no envía Retry-After hoy).
| Consulta | Coste | Recomendación |
|---|---|---|
tenantLicenseStatus() |
Solo la base de Commerce; sin firma ni Trust | Cada 5 minutos como mucho; es lo que detecta una devolución |
trustBundle() |
Solo la base de Commerce; nunca consulta a Trust ni a Azure | Al arrancar y cada hora; es lo que trae una revocación de tu autoridad |
tenantGrants() |
Solo la base de Commerce | Al arrancar y cuando tenantLicenseStatus muestre un grantId distinto del aplicado (renovación o ampliación) |
openGrant (open-grant) |
Una operación de Key Vault de Trust por Grant | Una vez por grantId: no abras uno ya aplicado ni repitas uno rechazado de forma definitiva mientras el paquete no cambie (el ejemplo lo hace así) |
confirmPurge |
Idempotente | Solo cuando Commerce lo pida (ver «Bajas») |
El SDK no reintenta nada salvo el 401, no espera y no sondea: la frecuencia y la espera las decide tu producto, y solo debe repetir lo idempotente (las consultas y confirmPurge).
9. Errores del canal
El cliente lanza CommerceChannelError con code, retryable, retryAfterSeconds?, status? y commerceCode?. El mensaje es un texto fijo del SDK: el del servidor nunca llega a tu producto.
| Qué ves | code del SDK |
Qué significa y qué hacer |
|---|---|---|
| 401 | COMMERCE_AUTHENTICATION_FAILED |
Commerce no te acredita, incluso tras pedir otro token. Revisa en #/canal emisor, audiencia y sujeto, la hora del reloj y que el token vive 24 h o menos. No se arregla repitiendo |
503 INSTALLATION_IDENTITY_UNVERIFIABLE |
COMMERCE_UNAVAILABLE (retryable) |
El JWKS de tu emisor no respondió a Commerce. No es un fallo de tu producto: repite más tarde |
404 NOT_FOUND en trust/bundle |
COMMERCE_REQUEST_REJECTED |
Commerce no reconoce el token como la identidad de ningún producto de tercero (el resto de rutas responden 401 en ese caso). Registra el emisor, la audiencia y el sujeto exactos en #/canal, para el producto y el entorno de la instalación |
404 INSTALLATION_TRUST_BUNDLE_NOT_AVAILABLE |
COMMERCE_TRUST_BUNDLE_NOT_AVAILABLE |
Aún no hay paquete de confianza para tu tercero (la autoridad de licencias todavía no está aprobada). Espera y avisa a Mitnix; no es un fallo de tu producto |
403 INSTALLATION_CUSTODY_SCOPE_FORBIDDEN |
COMMERCE_REQUEST_REJECTED |
La custodia solo opera con la aplicación y el entorno de tu identidad. Revisa el producto y el entorno registrados |
| 429, otros 5xx, red o plazo | COMMERCE_UNAVAILABLE (retryable) |
Repite más tarde; si hay Retry-After, retryAfterSeconds lo trae (acotado a 3600 s) |
| 400, 422 y otros 4xx | COMMERCE_REQUEST_REJECTED |
Fallo de integración; no se repite |
| 3xx | COMMERCE_REDIRECT_REFUSED |
Commerce no redirige el canal; el SDK rechaza cualquier redirección para que el token no salga del origen |
| 2xx que no cumple el contrato | COMMERCE_RESPONSE_INVALID |
Nunca recibes un dato a medias |
| Tu proveedor de token falla | COMMERCE_TOKEN_UNAVAILABLE |
Reintentable si el fallo de tu proveedor lo es |
Un fallo al abrir un Grant llega como TENANT_GRANT_KEY_OPEN_FAILED con details.causeCode, retryable y retryAfterSeconds (solo si tienen forma segura). Los rechazos de verificación (LG-CHAIN-INVALID, LG-SIGNATURE-INVALID, LG-AUTHORITY-REVOKED, LG-RECIPIENT-MISMATCH, LG-NOT-YET-VALID, LG-SUPERSEDED…) son definitivos para ese documento. El ejemplo los traduce a una acción en src/errores.ts. La lista completa de códigos está en los tipos que exportan los paquetes: CommerceChannelErrorCode en platform-commerce-client, y TenantGrantValidationCode y TenantGrantJweErrorCode en platform-licensing/tenant-grants.
10. Bajas y devoluciones
- Devolución o disputa perdida: Commerce revoca la licencia; la verás en
tenantLicenseStatus()(revokedAddons,revokedAt). El Grant firmado sigue siendo válido hasta suterm.validUntil, así que tu producto debe consultar el estado y dejar de conceder. - Fin del contrato:
tenantDispositions()indica, por tenant, si debes bloquear y conservar los datos (block-and-retain) o borrarlos (purge), condataRetainedUntil. Tras una purga,confirmPurge({ commercialAccountReference, grantId, scope? })confirma a Commerce que la hiciste.
11. Probar tu integración
- Unitarias, sin red: el ejemplo prueba todo el flujo contra un Commerce simulado a nivel HTTP (
commerceSimuladoensrc/pruebas/mundo.ts) con claves reales (paquete firmado, Grant firmado por tu autoridad y por la de otro tercero, JWE cifrado con RSA-3072) y el SDK sin dobles. Puedes copiar ese doble para probar tu producto. - De punta a punta, contra el Commerce de staging con un tercero de pruebas: registra la identidad de tu producto de pruebas, compra con tarjeta de prueba y ejecuta el ejemplo con tu token (ver su README).
12. Qué no hace el SDK
No guarda el token, no persiste el paquete ni los Grants, no abre conexiones propias fuera de fetch, no crea temporizadores ni sondea, no reintenta salvo un 401 y no envía nunca tu vendorId ni un secreto. Todo eso es de tu producto, a propósito: así el SDK no necesita ningún permiso en tu entorno.
Producto de ejemplo
Un producto SaaS mínimo (Node 22, TypeScript estricto) que hace todo el recorrido de esta guía con los paquetes publicados en npmjs. Sus pruebas se ejecutan sin red contra un Commerce simulado con claves reales.
tar -xzf mitnix-ejemplo-tercero-saas.tar.gz
cd mitnix-ejemplo-tercero-saas
npm install
npm testSHA-256: eccf2461e0eb9e0e36eb277c9eb6b9a39f46822cce076172f1d558ad0c0b1e54