Casos de uso
Vender actualizaciones y controlar versiones
Decide qué versiones de tu producto cubre cada licencia — actualizaciones de por vida, una ventana de soporte, una versión mayor, protección de actualización, suscripciones con o sin respaldo perpetuo — y aplícalo en tu aplicación o en tu propia página de descargas, sin reactivación, sin volver a introducir la clave y con soporte completo sin conexión.
Objetivo
Vende actualizaciones tal como funciona realmente tu negocio — «todas las versiones 2.x», «cualquier versión publicada durante el año siguiente a la compra», «lo que se publique mientras la suscripción esté activa» — y haz que tu aplicación (y tu propia página de descargas) obtenga una respuesta de sí o no para cualquier versión. Esto es la cobertura de versiones: Keyright registra qué cubre cada licencia, lo firma, lo entrega a la máquina del cliente junto con el lease y decide la elegibilidad, sin pedir nunca a un cliente que vuelva a introducir una clave ni que reactive, y sin tumbar una instalación en funcionamiento.
Keyright es un oráculo de cobertura: decide si una licencia cubre una versión. Nunca aloja descargas, canales de actualización ni listados de versiones — tú sigues gestionando tu propio actualizador y tu página de descargas, y preguntas a Keyright si un cliente concreto puede tener una versión concreta.
Cuándo usarla
- Úsala si cobras por las mejoras de versión, ofreces una ventana de mantenimiento, vendes por versión mayor o gestionas una suscripción de escritorio — cualquier caso en el que «poseer el software» y «tener derecho a esta compilación» sean preguntas distintas.
- Úsala si quieres controlar tu propia página de descargas o tu actualizador por cliente, en línea o aislado de la red (air-gapped), a partir de una única fuente de verdad firmada.
- No la uses solo para impedir que una licencia caducada se ejecute — de eso se encarga la propia caducidad de la licencia (consulta Activar, desactivar y mover puestos). La cobertura añade encima la dimensión de fecha de publicación / versión.
La cobertura de versiones forma parte del plan superior del servicio alojado (Scale y Enterprise) y de las licencias autoalojadas que incluyen la característica de cobertura. Los planes inferiores ven la característica bloqueada en el panel con una invitación a mejorar el plan. El SDK es idéntico en todos los planes: una licencia sin cobertura simplemente se lee como no configurada y permite ejecutar todas las versiones.
Cómo funciona
Cada licencia lleva un conjunto de términos de cobertura. Un término es un rango de versiones (2.0.0 ≤ v < 3.0.0), una ventana de fechas (cualquier cosa publicada entre dos fechas de calendario) o ambos — opcionalmente limitado a un canal de publicación (stable por defecto; las betas necesitan un término beta explícito). Una versión está cubierta cuando cualquier término coincide con su número de versión y con su fecha de habilitación (normalmente, la fecha en que se publicó).
Tres propiedades hacen que sea seguro aplicarla:
- Instantánea, no en vivo. Cuando emites una licencia, la política de cobertura de la edición se expande en términos que se almacenan en esa licencia. Cambiar la política después nunca altera lo que compraron los clientes existentes; los nuevos términos se aplican a las licencias existentes mediante una acción explícita y con vista previa (consulta Aplicar una política a los clientes existentes).
- Los términos solo crecen. Los términos son un registro de solo anexado. Las renovaciones y las mejoras de versión añaden términos; el equipo de soporte puede eliminar un término, pero nada que un cliente pueda ejecutar hoy se le retira jamás de forma silenciosa por editar una política.
- Firmada y sin conexión. La cobertura viaja como un bloque de cobertura firmado por separado y vinculado a la licencia y al producto. La aplicación lo verifica con la misma clave pública que ya incorpora, lo guarda en caché y decide la cobertura localmente — en línea, sin conexión o aislada de la red. Un número de secuencia monótono impide reutilizar un bloque más antiguo para restaurar una cobertura vencida, y un bloque ausente o manipulado nunca se trata como «cubierto».
Dos ideas más que encontrarás a lo largo de la página:
- Fecha de habilitación frente a fecha de publicación. La cobertura se evalúa contra la fecha de habilitación de una versión, que por defecto es su fecha de publicación pero puede fijarse por separado. Así es como un hotfix de seguridad publicado después de que terminara la ventana de soporte de un cliente puede seguir estando cubierto para él: fecha el hotfix con la fecha de su versión base.
- El registro de versiones es opcional. La aplicación de la cobertura funciona por completo a partir de la versión y la fecha de habilitación incrustadas en la propia compilación. Si además registras las versiones (desde CI o desde el panel), Keyright puede decir a los clientes la versión más reciente que pueden usar, previsualizar para cuántos clientes está cubierta una versión y fechar los hotfixes por ti.
Qué modelo de cobertura encaja
Seis preajustes en lenguaje llano cubren los negocios habituales. Elige uno por edición; se expande en términos en cada licencia que emitas a partir de ese momento.
| Preajuste | Qué obtiene el cliente | Uso típico |
|---|---|---|
| Lifetime updates (actualizaciones de por vida) | Todas las versiones, para siempre. | Una compra única que promete mejoras gratuitas. |
| Updates for a period (actualizaciones durante un periodo) | Cualquier versión publicada en los N meses siguientes a la compra; la copia sigue funcionando para siempre. | El licenciamiento perpetuo clásico de «un año de actualizaciones». |
| One major version (una versión mayor) | Todas las versiones de una línea mayor (p. ej. todas las 2.x), para siempre. | Precios por versión mayor. |
| One major + upgrade protection (una versión mayor + protección de actualización) | Una versión mayor para siempre, más todo lo publicado en los N meses siguientes — incluida la siguiente versión mayor si se publica a tiempo. | Precios por versión mayor con un plan de mantenimiento. |
| Subscription (suscripción) | Todo lo publicado mientras la suscripción está activa; nada después de que venza. | Licenciamiento de escritorio al estilo SaaS. |
| Subscription with fallback (suscripción con respaldo) | Como Subscription, pero tras el vencimiento el cliente conserva las versiones publicadas mientras pagaba. | Suscripciones que nunca deben dejar tirado a un cliente. |
Cualquier otra cosa — una única versión fija, una línea por versión menor, una ventana personalizada — es un término personalizado que añades a una licencia desde la sección Advanced (avanzado) de su pestaña Coverage (o con la API de grants que se describe más abajo).
Actualizaciones de por vida
Escenario. Vendes una clave de pago único y prometes mejoras gratuitas de por vida (o simplemente no restringes las actualizaciones y quieres que el portal lo diga). Nada caduca nunca.
Términos. Un término abierto: todas las versiones, todas las fechas. Lo que lee el cliente (portal, SDK, API): «Tu licencia cubre todas las versiones.» — un estado sin cobertura no puede darse jamás.
Configuración. Política de la edición → Lifetime updates (sin preguntas), o en una sola licencia Change coverage… → Lifetime updates. API: {"preset":"lifetime"}.
Actualizaciones durante un periodo
Escenario. Una licencia perpetua con doce meses de actualizaciones. El cliente conserva su copia para siempre y puede instalar cualquier cosa publicada dentro de la ventana — incluida una nueva versión mayor si se publica a tiempo. Una versión publicada después de la ventana está «no cubierta — ventana vencida», así que la aplicación ofrece Renew (renovar).
Términos. Una ventana de fechas desde la fecha de compra hasta la compra + N meses, cualquier versión. Renovar anexa otra ventana (contigua, o con un hueco que puedes optar por dejar sin cubrir). El cliente lee: «Tu licencia cubre cualquier versión publicada hasta el 9 oct 2027.»
Configuración. Política de la edición → Updates for a period → Updates for (months after purchase) (actualizaciones durante N meses tras la compra). API: {"preset":"updatesForPeriod","periodMonths":12}.
Una versión mayor
Escenario. Precios por versión mayor: una licencia 2.x ejecuta todas las versiones 2.x que se publiquen jamás, incluidas las que salgan años después, pero no la 3.0. Una compilación 3.0 está «no cubierta — fuera del rango de versiones», así que la aplicación ofrece Upgrade (actualizar a una versión superior). Las líneas anteriores (1.x) siempre están permitidas — el derecho a volver a versiones anteriores viene incorporado.
Términos. Un rango de versiones [major.0.0, major+1.0.0), cualquier fecha. El cliente lee: «Tu licencia cubre todas las versiones de Acme Studio 2.»
Configuración. Política de la edición → One major version → Which major version? (¿qué versión mayor?). API: {"preset":"oneMajor","major":2}. Cuando tu edición vende «la versión mayor actual», sube la versión mayor de la política cuando publiques la siguiente — los clientes existentes conservan la línea que compraron.
Una versión mayor + protección de actualización
Escenario. Precios por versión mayor más un plan de mantenimiento: el cliente posee 2.x para siempre y recibe todo lo publicado en los N meses siguientes — de modo que si la 3.0 sale dentro de la ventana, la obtiene, junto con cualquier parche 3.x publicado antes de que la ventana se cierre. Una versión 3.x fechada después de la ventana queda fuera de ambos términos (→ Renew, puesto que es la ventana lo que ha vencido). Es el modelo más habitual de «mejoras de pago con red de seguridad».
Términos. Dos términos — la línea mayor y una ventana de fechas desde la compra hasta la compra + N meses. El cliente lee: «Tu licencia cubre todas las versiones de Acme Studio 2, más cualquier versión publicada hasta el 9 oct 2027.»
Configuración. Política de la edición → One major + upgrade protection → versión mayor + meses. API: {"preset":"oneMajorPlusUpgradeProtection","major":2,"periodMonths":12}.
Suscripción
Escenario. El cliente puede usar cualquier versión publicada mientras paga. La propia licencia suele llevar una caducidad que coincide con el periodo de facturación (de modo que la aplicación se detiene cuando la suscripción termina); la cobertura añade la dimensión de la fecha de publicación para que, por ejemplo, un cliente que baja a un plan más barato o cuyo plazo se amplía sin una clave nueva reciba exactamente las versiones a las que tiene derecho.
Términos. Una ventana de fechas desde la emisión hasta la emisión + N meses. Cada renovación anexa la siguiente ventana. Tras un vencimiento, las versiones fechadas después de la ventana están en «ventana vencida» → Renew. El cliente lee: «Tu licencia cubre cualquier versión publicada hasta el 9 oct 2027.»
Configuración. Política de la edición → Subscription → Term (months) (plazo en meses). API: {"preset":"subscription","periodMonths":12}.
Suscripción con respaldo
Escenario. Una suscripción que nunca deja tirado a un cliente: cuando deja de pagar, conserva las versiones publicadas mientras pagaba (un «respaldo perpetuo»), y solo las versiones posteriores al vencimiento requieren una renovación.
Términos. En el momento de la emisión este preajuste se expande exactamente igual que Subscription — una ventana para el plazo pagado. El respaldo consiste simplemente en que esa ventana no se revoca al vencer: como los términos solo crecen, un cliente cuya suscripción termina sigue teniendo un término que cubre todo lo publicado durante ella. Cuando proceses un vencimiento en tu flujo de facturación, no revoques el término de cobertura; si además dejas que la propia licencia caduque, la aplicación sigue tu comportamiento de caducidad habitual. Para dejar constancia explícita del vencimiento, añade un término personalizado con el motivo Fallback (respaldo) que termine en la fecha de vencimiento (consulta términos personalizados).
Configuración. Política de la edición → Subscription with fallback → Term (months). API: {"preset":"subscriptionWithFallback","periodMonths":12}.
Betas y acceso anticipado
Un término sin canal cubre únicamente las versiones estables. Para que un cliente pueda ejecutar compilaciones beta, dale un término en el canal beta (activa Include beta builds (incluir compilaciones beta) en la política, o establece "channel":"beta" en un término personalizado) y compila tus versiones preliminares con KeyrightCoverageChannel=beta. Keyright cubre las betas; nunca hace caducar compilaciones — si quieres que una versión preliminar caduque por sí sola, incrusta KeyrightCoverageBuildExpiry en ella y comprueba CoverageBuildInfo.Current.BuildExpiryUtc en tu propio código de arranque.
Configurarlo en el panel
Todo lo que sigue vive bajo Releases & coverage (versiones y cobertura) — las vistas Intermediate (intermedia) y Advanced (avanzada) del panel; también aparece en la vista Basic (básica) en cuanto registras una versión o guardas una política — consulta Panel de administración → Nivel de experiencia.
1. Registrar versiones (opcional, recomendado)
Registrar una versión deja constancia de su número de versión, su fecha de publicación, una fecha de habilitación opcional distinta y su canal. Nada más — ni archivos ni URL. Con un registro, los clientes ven la versión más reciente que pueden usar, el panel previsualiza para quién está cubierta cada versión y el comprobador de cobertura puede fechar una versión por ti.
Desde CI, registra cada versión a medida que se publica:
# KEYRIGHT_URL and KEYRIGHT_ADMIN_TOKEN can also be passed as --url / --admin-token
keyright release --register --product acme-studio --version 2.4.0 --release-date 2026-10-01
# a hotfix that must stay covered for everyone who had 2.3.0: date it as the base release
keyright release --register --product acme-studio --version 2.3.1 --release-date 2026-09-30 --entitlement-date 2026-07-02
# a preview build on the beta channel
keyright release --register --product acme-studio --version 3.0.0 --release-date 2026-10-08 --channel beta
O a través de la API (idempotente por producto + versión):
curl -X POST $BASE/admin/products/acme-studio/releases -H "X-Admin-Token: $TOKEN" -H "content-type: application/json" \
-d '{"version":"2.3.1","releaseDate":"2026-09-30","entitlementDate":"2026-07-02","channel":"stable"}'
# -> { "product":"acme-studio", "version":"2.3.1", "releaseDate":"2026-09-30", "entitlementDate":"2026-07-02", "channel":"stable" }
# how many licenses would cover a release, before you ship it
curl "$BASE/admin/products/acme-studio/releases/preview?version=2.5.0&on=2026-10-09" -H "X-Admin-Token: $TOKEN"
# -> { "product":"acme-studio", "version":"2.5.0", "covered":4, "notCovered":1, "notConfigured":0, "total":5 }
El diálogo Register release (registrar versión) muestra la misma vista previa en vivo mientras escribes.
2. Dar a cada edición una política de cobertura
Abre Releases & coverage → Coverage policy (política de cobertura), elige la edición (tier), escoge un preajuste, responde a su una o dos preguntas y pulsa Save policy (guardar política). El panel lateral previsualiza, en lenguaje llano, qué obtiene un comprador, la frase que leerá y el mensaje que vería si abriera una versión fuera de sus términos.
Qué significa cada control:
| Control | Significado |
|---|---|
| Edition (edición) | Las políticas son por edición. Las licencias nuevas de esta edición reciben estos términos al emitirse; las demás ediciones no se ven afectadas. |
| Preset (preajuste) | Uno de los seis modelos anteriores. |
| Which major version? (¿qué versión mayor?) | Para los dos preajustes de «una versión mayor»: la línea que cubre la licencia (2 → todas las 2.x). |
| Updates for / Term (months) (actualizaciones durante / plazo en meses) | Para los preajustes basados en fechas: la duración de la ventana, contada desde el día en que se emite cada licencia. |
| Downgrade rights (derecho a versiones anteriores) | Documenta que las versiones anteriores siempre están permitidas — una línea de versión ya incluye todo lo que hay por debajo, y una ventana incluye todo lo publicado antes de ella. |
| Hotfixes keep their base release’s date (los hotfixes conservan la fecha de su versión base) | Documenta la regla de la fecha de habilitación: fecha un hotfix como su versión base al registrarlo y seguirá cubierto. |
| Late renewals cover the gap (las renovaciones tardías cubren el hueco) | Documenta cómo renuevas: un término de renovación que empieza en la antigua fecha de fin cubre el hueco; uno que empieza hoy lo deja sin cubrir. |
| Include beta builds (incluir compilaciones beta) | Pone los términos de la política en el canal beta en lugar de solo en el estable. |
| Advanced: what happens when a customer runs a version they don’t have (avanzado: qué ocurre cuando un cliente ejecuta una versión que no tiene) | Registra el comportamiento que has elegido para tu aplicación — informar, ejecutar en modo de solo lectura o bloquear. El servidor devuelve la decisión; tu aplicación implementa el comportamiento (consulta En tu aplicación). |
| Advanced: apply this policy to existing customers (avanzado: aplicar esta política a los clientes existentes) | Una migración con vista previa para las licencias emitidas antes de esta política — consulta más abajo. |
Guardar una política solo afecta a las licencias emitidas a partir de ahora. Lo mismo a través de la API — ten en cuenta que un upsert de edición reemplaza la edición completa, así que envía también sus puestos y características:
curl -X POST $BASE/admin/products/acme-studio/tiers -H "X-Admin-Token: $TOKEN" -H "content-type: application/json" \
-d '{"name":"pro","seats":5,"entitlements":{"export":"true"},
"coveragePolicy":{"preset":"oneMajorPlusUpgradeProtection","major":2,"periodMonths":12}}'
# clear a policy: send an empty preset (omitting coveragePolicy leaves the current one unchanged)
curl -X POST $BASE/admin/products/acme-studio/tiers -H "X-Admin-Token: $TOKEN" -H "content-type: application/json" \
-d '{"name":"pro","seats":5,"entitlements":{"export":"true"},"coveragePolicy":{"preset":""}}'
preset es uno de lifetime, updatesForPeriod, oneMajor, oneMajorPlusUpgradeProtection, subscription, subscriptionWithFallback; major lo requieren los dos preajustes de «una versión mayor» y periodMonths los cuatro basados en fechas; channel es opcional (beta).
3. Emitir licencias como siempre
Nada cambia en la forma de emitir: Licenses → Issue license (licencias → emitir licencia) (o POST /admin/licenses, o el webhook de cumplimiento de pedidos de tu tienda). Al emitirse, la política de la edición se expande en términos en la nueva licencia. El panel lateral muestra las características de la edición; la cobertura que lleva es visible después en la página de la licencia.
4. Ver y cambiar la cobertura de una licencia
La página de una licencia gana una pestaña Coverage (cobertura) (aparece automáticamente en cuanto la licencia tiene cobertura, sea cual sea tu vista del panel). Muestra la frase orientada al cliente, la política en vigor, la versión registrada más reciente que puede usar, si la cobertura está en modo aplicado, un comprobador de cobertura por licencia y un historial en lenguaje llano de cada término que la licencia ha tenido alguna vez.
Change coverage… (cambiar cobertura…) aplica un preajuste a esta licencia — así es como gestionas una renovación («Updates for a period» de nuevo), una mejora de versión («One major version» 3) o una conversión a suscripción. Aquí la cobertura solo puede crecer: los nuevos términos se añaden junto a los existentes.
# apply a preset to one license (renewal / upgrade / conversion)
curl -X POST $BASE/admin/licenses/LIC-…/coverage/preset -H "X-Admin-Token: $TOKEN" -H "content-type: application/json" \
-d '{"preset":"updatesForPeriod","periodMonths":12}'
# read a license's coverage: what's in effect, the sequence number, and the full ledger
curl $BASE/admin/licenses/LIC-…/coverage -H "X-Admin-Token: $TOKEN"
# -> { "configured":true, "mode":"enforced", "seq":3, "supportUntilUtc":"2027-10-09T00:00:00.0000000Z",
# "activeGrants":[ { "id":"…", "min":"2.0.0", "maxExcl":"3.0.0", "dates":[], "channel":null,
# "reason":"purchase", "sourceRef":null, "createdUtc":"…", "active":true }, … ],
# "ledger":[ … every term ever added, with "active":false for removed ones … ] }
Términos personalizados y el historial técnico
La sección contraída Advanced: coverage history (technical) (avanzado: historial de cobertura (técnico)) está pensada para el personal de soporte. Cada fila es un término con su rango de versiones, fechas, canal, motivo y estado; desde aquí puedes añadir un término personalizado (una versión fija, una línea por versión menor, una ventana con fechas explícitas, un canal beta), eliminar un término activo, pausar / reanudar la aplicación de la cobertura para esta licencia y descargar el archivo de cobertura firmado.
# add a custom term: everything released in a window, on the beta channel, recorded as a promo
curl -X POST $BASE/admin/licenses/LIC-…/coverage/grants -H "X-Admin-Token: $TOKEN" -H "content-type: application/json" \
-d '{"minVersion":null,"maxExclVersion":null,"dates":[{"from":"2026-10-01","to":"2026-12-31"}],
"channel":"beta","reason":"promo","sourceRef":"launch-week"}'
# a pinned version: {"minVersion":"2.4.1","maxExclVersion":"2.4.2"} — a per-minor line: {"minVersion":"2.4.0","maxExclVersion":"2.5.0"}
# remove an active term (recorded, never deleted)
curl -X POST $BASE/admin/licenses/LIC-…/coverage/revoke -H "X-Admin-Token: $TOKEN" -H "content-type: application/json" \
-d '{"grantId":"<id from the ledger>","sourceRef":"refund #4821"}'
# pause enforcement for one license (the next block is stamped unenforced → every version runs); "enforced" resumes it
curl -X POST $BASE/admin/licenses/LIC-…/coverage/mode -H "X-Admin-Token: $TOKEN" -H "content-type: application/json" \
-d '{"mode":"unenforced"}'
reason es uno de purchase, renewal, upgrade, promo, fallback, migration, legacy; sourceRef es texto libre para tu pista de auditoría (un id de pedido, un ticket). Los rangos de versiones comparan solo major.minor.patch — los sufijos de prelanzamiento se ignoran, de modo que 2.5.0-beta está dentro de 2.x y 3.0.0-rc1 no. Las fechas son fechas de calendario comparadas de forma inclusiva, sin conversión de zona horaria.
5. Comprobar cualquier versión para cualquier licencia
El comprobador de cobertura — en la pestaña Coverage checker de la sección principal y en la pestaña Coverage de cada licencia — responde a «¿puede esta licencia usar la versión X?» exactamente como lo harían la aplicación y tu página de descargas, con un motivo de una línea y, cuando el registro está poblado, la versión más reciente que sí pueden usar.
curl "$BASE/admin/licenses/LIC-…/coverage/check?version=3.0.0" -H "X-Admin-Token: $TOKEN"
# -> { "status":"NotCovered", "reason":"OutsideVersionRange", "newestCovered":"2.4.0" }
# add &on=2026-11-01 to evaluate at an explicit entitlement date (default: the version's registered date, else today)
# add &channel=beta to check a preview build
Aplicado, no aplicado y «no configurado»
Cada bloque de cobertura lleva un modo:
- Aplicado — el estado normal de una licencia con términos de cobertura: la aplicación compara la compilación en ejecución con los términos.
- No aplicado — el bloque dice «trata todas las versiones como cubiertas». Keyright emite bloques no aplicados cuando pausas la aplicación de la cobertura en una licencia y — automáticamente, tras un periodo de gracia — cuando el plan de un espacio de trabajo o la licencia autoalojada deja de incluir la cobertura. Tus clientes nunca quedan bloqueados por tu estado de facturación; cuando el derecho vuelve, los bloques aplicados se reanudan con los términos intactos.
- No configurado — una licencia que nunca ha tenido términos de cobertura (una licencia antigua, o un espacio de trabajo que no usa la cobertura) recibe un bloque firmado de «no configurado». El SDK lo notifica como
NotConfiguredy trata la licencia como sin restricciones. Este estado solo proviene de un bloque firmado: un bloque ausente nunca se interpreta como «sin restricciones», así que quitar el bloque de una máquina no sirve para eludir la aplicación de la cobertura.
La aplicación de la cobertura en tu app es una decisión en tiempo de compilación (KeyrightRequireCoverage, más abajo). Las compilaciones sin ella ignoran la cobertura por completo; las compilaciones con ella preguntan a Keyright, refrescan una vez antes de rechazar y fallan en modo cerrado a un estado de «importa un archivo de licencia actualizado» cuando están sin conexión y no hay nada en caché.
Aplicar una política a los clientes existentes
Guardar una política nunca toca las licencias ya emitidas. Cuando sí quieras que los clientes existentes ganen algo — por ejemplo, has introducido la protección de actualización y quieres extenderla a todos los que están en 2.x — usa Advanced: apply this policy to existing customers en la pestaña de la política. Introduce una versión de referencia, pulsa Preview who is affected… (previsualizar a quién afecta…) para ver cuántas licencias ganarían cobertura para ella (y cuántas ya la tienen) y después Apply (aplicar). La cobertura solo puede crecer: nadie pierde una versión que pueda usar hoy.
# preview: how many acme-studio licenses would newly cover 3.0.0 if everyone got a 2.x-plus-window term
curl -X POST "$BASE/admin/products/acme-studio/coverage/migrate?dryRun=true&version=3.0.0" \
-H "X-Admin-Token: $TOKEN" -H "content-type: application/json" \
-d '{"minVersion":null,"maxExclVersion":null,"dates":[{"from":"2026-10-09","to":"2027-10-09"}],"reason":"migration","sourceRef":"upgrade-protection-rollout"}'
# -> { "total":5, "coveredBefore":1, "coveredAfter":5, "gain":4, "applied":false }
# apply (dryRun=false): the same term is appended to every non-revoked license of the product
Licencias antiguas: completar retroactivamente antes de aplicar
Una licencia emitida antes de que adoptaras la cobertura no tiene términos. Si enviaras una compilación con RequireCoverage a ese cliente sin hacer nada, se le pediría que importara un archivo de licencia. La pestaña Enforcement readiness (preparación para aplicar la cobertura) existe para que eso nunca ocurra: cuenta las licencias que aún carecen de términos y bloquea la lista de comprobación hasta que se completen retroactivamente (backfill).
Elige uno de los tres backfills — cada uno da a la licencia un término marcado como cliente existente (reason: legacy):
| Backfill | Resultado | Cuándo |
|---|---|---|
| Treat them as lifetime (tratarlas como de por vida) | Todas las licencias antiguas cubren todas las versiones, para siempre. | Nunca has cobrado por las actualizaciones. |
| Use each license’s expiry date (usar la fecha de caducidad de cada licencia) | Una ventana que termina en la propia caducidad de la licencia (las perpetuas reciben cobertura de por vida). | Licencias por plazo cuyo soporte terminaba con la licencia. |
| Import coverage dates (CSV) (importar fechas de cobertura) | Fechas de fin de soporte por licencia desde un archivo licenseId,supportUntil; una fecha en blanco significa de por vida. | Llevabas el control de las ventanas de soporte en otro sitio. |
curl $BASE/admin/products/acme-studio/coverage/readiness -H "X-Admin-Token: $TOKEN"
# -> { "total":5, "configured":4, "missing":1 }
# backfill every license that still has no terms (defaultRule: "lifetime" or "supportEnd")
curl -X POST $BASE/admin/products/acme-studio/coverage/backfill -H "X-Admin-Token: $TOKEN" -H "content-type: application/json" \
-d '{"defaultRule":"lifetime"}'
# -> { "backfilled":1 }
# or per license (the CSV upload sends the same shape)
curl -X POST $BASE/admin/products/acme-studio/coverage/backfill -H "X-Admin-Token: $TOKEN" -H "content-type: application/json" \
-d '{"rows":[{"licenseId":"LIC-AAAA","supportUntil":"2027-03-12"},{"licenseId":"LIC-BBBB","supportUntil":null}]}'
Acierta con las fechas antes de publicar una compilación que aplique la cobertura: una vez entregado el bloque firmado de un cliente, puede ampliarse, pero no retirarse de forma efectiva. Los clientes en línea reciben sus términos completados en el siguiente refresco; los aislados de la red necesitan la exportación que se describe más abajo.
Clientes aislados de la red (air-gapped)
Una máquina que nunca alcanza tu servicio no puede recibir el bloque al activarse. Entrégaselo como un archivo:
- Una licencia: el botón Download signed coverage file (descargar archivo de cobertura firmado) de su pestaña Coverage (o
GET /admin/licenses/{id}/coverage/block→{ "block": "…" }). - Todas las licencias de un producto: Export offline license files (exportar archivos de licencia sin conexión) en la pestaña Enforcement readiness (o
GET /admin/products/{slug}/coverage/export→{ "product", "count", "items": [ { "licenseId", "coverageBlock" } ] }).
El archivo es el sobre del bloque firmado — JSON con un payload y una sig. Entrégaselo al cliente junto con su archivo de licencia sin conexión o su lease; su aplicación lo importa con ImportCoverageBlock (más abajo). Una renovación para un cliente aislado es simplemente un archivo más nuevo: lleva un número de secuencia mayor, así que reemplaza al que está en caché, mientras que un archivo más antiguo se rechaza. La exportación respeta tu derecho exactamente igual que lo hace la activación — bloques no aplicados si el espacio de trabajo ya no incluye la cobertura — de modo que nunca puede dejar fuera a nadie.
Qué ve el cliente
En el portal del cliente
La página de licencia del portal muestra la frase de cobertura — formulada en términos de lo que se posee, sin jerga interna — justo debajo del resumen de la licencia, y la lista Devices (dispositivos) debajo. Los clientes saben lo que tienen sin abrir un ticket de soporte.
Si gestionas tu propio portal de clientes, la API del portal con sesión iniciada devuelve la misma frase y la fecha de fin de soporte derivada: GET /portal/license/{id}/coverage → { "status": "Your license covers …", "supportUntilUtc": "2027-10-09T00:00:00.0000000Z" } (null cuando la cobertura no está acotada por una fecha).
Las frases y mensajes por defecto
Estas son las cadenas que producen el SDK y la API (el panel las previsualiza con el mismo significado; aquí se muestran con su original en inglés entre paréntesis). Todas pueden sobrescribirse por estado — consulta mensajes más abajo.
| Estado | Texto por defecto |
|---|---|
| De por vida | Tu licencia cubre todas las versiones. (Your license covers every version.) |
Una versión mayor ({line} = «Acme Studio 2») | Tu licencia cubre todas las versiones de Acme Studio 2. (Your license covers all Acme Studio 2 versions.) |
| Ventana (periodo / suscripción) | Tu licencia cubre cualquier versión publicada hasta el 9 oct 2027. (Your license covers any version released through 9 Oct 2027.) |
| Versión mayor + protección de actualización | Tu licencia cubre todas las versiones de Acme Studio 2, más cualquier versión publicada hasta el 9 oct 2027. (Your license covers all Acme Studio 2 versions, plus any version released through 9 Oct 2027.) |
| Ventana abierta | Tu licencia cubre todas las versiones publicadas hasta la fecha. (Your license covers every version released so far.) |
| No cubierta — ventana vencida | Esta versión (2.5.0) se publicó después de que tus actualizaciones terminaran el 9 oct 2027. Puedes seguir usando la 2.4.0. Renueva para usar la 2.5.0. (This version (2.5.0) was released after your updates ended on 9 Oct 2027. You can keep using 2.4.0. Renew to use 2.5.0.) (sin registro se omite la frase «puedes seguir usando») → acción Renew |
| No cubierta — fuera del rango de versiones | Esta versión (3.0.0) no está incluida en tu licencia de Acme Studio 2. Puedes seguir usando la 2.4.0. Actualiza para usar la 3.0.0. (This version (3.0.0) isn’t included in your Acme Studio 2 license. You can keep using 2.4.0. Upgrade to use 3.0.0.) → acción Upgrade |
| No disponible sin conexión | Acme Studio no ha podido confirmar sin conexión que tu licencia incluye esta versión. Conéctate a internet o importa un archivo de licencia actualizado de Acme Software. (Acme Studio couldn’t confirm that your license includes this version while offline. Connect to the internet, or import an updated license file from Acme Software.) → acción Import license file |
En tu aplicación (SDK de .NET)
La cobertura necesita dos cosas de tu compilación: qué versión es (para poder compararla con los términos) y si debe aplicar la cobertura. Ambas son propiedades en tiempo de compilación, de modo que a un binario ya distribuido no se le puede convencer en tiempo de ejecución de que las ignore.
1. Incrustar la identidad de versión de la compilación (MSBuild)
El paquete Keyright.NET incluye targets de MSBuild que leen estas propiedades y las compilan en el ensamblado como metadatos que el SDK vuelve a leer al arrancar:
<!-- in the app's .csproj, or Directory.Build.props — normally set from your release pipeline -->
<PropertyGroup>
<KeyrightCoverageVersion>2.4.0</KeyrightCoverageVersion> <!-- this build's product version (major.minor.patch) -->
<KeyrightCoverageEntitlementDate>2026-10-01</KeyrightCoverageEntitlementDate> <!-- the date coverage is judged against (yyyy-MM-dd) -->
<KeyrightCoverageChannel>stable</KeyrightCoverageChannel> <!-- stable (default) or beta -->
<KeyrightRequireCoverage>true</KeyrightRequireCoverage> <!-- bake enforcement into this build -->
<!-- optional: a hard self-expiry for preview builds, read back as CoverageBuildInfo.BuildExpiryUtc -->
<!-- <KeyrightCoverageBuildExpiry>2027-01-01T00:00:00Z</KeyrightCoverageBuildExpiry> -->
</PropertyGroup>
Desde CI, pásalas en la línea de comandos para que el pipeline sea la única fuente de verdad:
dotnet publish -c Release \
-p:KeyrightCoverageVersion=2.4.0 \
-p:KeyrightCoverageEntitlementDate=2026-10-01 \
-p:KeyrightRequireCoverage=true
Reglas que los targets hacen cumplir y de las que depende el SDK:
- Una compilación con
KeyrightRequireCoverage=truedebe establecerKeyrightCoverageEntitlementDate; de lo contrario la compilación falla con el error KR0001 (no habría nada contra lo que evaluar). Las propiedades sin establecer no emiten ningún metadato. - Un hotfix hereda la fecha de habilitación de su versión base. Compila la
2.3.1conKeyrightCoverageEntitlementDate=2026-07-02(la fecha de la 2.3.0) y seguirá cubierta para todo el que tuviera la 2.3.0 — y regístrala con el mismo--entitlement-datepara que el comprobador y el registro coincidan. - Los sufijos de prelanzamiento (
2.5.0-beta.1) se ignoran al comparar rangos; usa el canal para las betas.
En tiempo de ejecución, los valores incrustados están disponibles como CoverageBuildInfo.Current — Version, EntitlementDate, Channel, BuildExpiryUtc, RequireCoverage y HasMetadata.
2. Comprobar la cobertura al arrancar
El SDK ya hace la mitad del trabajo: cada ActivateAsync (y cada refresco del lease) envía la versión y la fecha de habilitación de la compilación, recibe el bloque de cobertura firmado junto con el lease, lo verifica con tu clave pública incrustada y lo guarda en caché junto a la licencia. Tú añades una llamada:
using Keyright.Client;
using Keyright.Coverage;
static readonly KeyrightClient License = KeyrightClient.Initialize(new KeyrightOptions
{
Product = "acme-studio",
PublicKeyBase64 = "MIIBIjANBgkq...", // the same key verifies licenses, leases AND coverage blocks
ServiceUrl = "https://licensing.acme.example",
});
static async Task<bool> GateOnCoverageAsync(CancellationToken ct)
{
var build = CoverageBuildInfo.Current;
if (!build.RequireCoverage) return true; // a non-enforcing build ignores coverage entirely
// Decides from the cached block; if not covered (or nothing cached) it silently refreshes the licence once
// (short timeout, offline is fine) and decides again. Never throws for network problems.
CoverageCheckResult check = await License.CheckCoverageAsync(ct: ct);
if (check.IsCovered) // Covered, or NotConfigured (coverage imposes no restriction)
return true;
// Not covered, or unavailable offline: build the message + the single action and let the app decide what to do.
var message = BuildMessage(check);
switch (check.Status)
{
case CoverageCheckStatus.UnavailableOffline: // no usable block and the refresh failed (air-gapped)
ShowImportLicenseFile(message); // "…import an updated license file from Acme Software."
return false;
case CoverageCheckStatus.NotCovered:
return ShowNotCovered(message); // block, run read-only, or just inform — your choice
default:
return true;
}
}
CheckCoverageAsync(version?, entitlementDate?) toma por defecto para ambos argumentos los metadatos incrustados en la compilación, de modo que la llamada sin parámetros responde a «¿está cubierta esta compilación?». Solo lanza una excepción si no hay versión alguna (ni incrustada ni pasada). El resultado:
| Miembro | Significado |
|---|---|
Status | Covered, NotCovered, NotConfigured o UnavailableOffline. |
IsCovered | true para Covered y NotConfigured — la aplicación puede ejecutarse. |
Reason | Para NotCovered: WindowLapsed (sus actualizaciones terminaron antes de esta versión → renovar) u OutsideVersionRange (esta línea de versión no es la suya → actualizar a una versión superior). |
NewestCovered | La versión cubierta más reciente cuando el bloque o el registro la dan a conocer (SemVer?), para «puedes seguir usando la 2.4.0». |
Refreshed | Si se intentó un refresco silencioso durante esta comprobación. |
UnavailableOffline es deliberadamente distinto de NotCovered: significa que no hay nada en caché y no se pudo contactar con el servicio, lo que en una compilación que aplica la cobertura es una situación de «por favor, importa el archivo que te enviamos» — nunca «tu licencia no es válida», y nunca una petición de la clave.
3. Decidir localmente, en cualquier momento
Para tus propios mensajes dentro de la aplicación o tu actualizador — «¿debo mostrar el banner de la 3.0?», «¿cuál es la versión más reciente que este cliente puede instalar?» — lee el bloque en caché sin ninguna red:
CoverageInfo cov = License.GetCoverage(); // from the cached, verified block; never contacts a distribution endpoint
if (cov.Available)
{
bool notConfigured = cov.IsNotConfigured; // signed "no restriction" block
CoverageMode mode = cov.Mode; // Enforced / Unenforced
DateTime? supportTo = cov.SupportUntilUtc; // latest window end, or null when not date-bounded
long seq = cov.Seq; // the block's sequence number (anti-rollback)
foreach (Grant g in cov.Grants) // the terms: version range, date intervals, channel, reason
Console.WriteLine($"{g.Versions?.MinInclusive}..{g.Versions?.MaxExclusive} {g.Reason}");
CoverageDecision d = cov.Decide(SemVer.Parse("3.0.0"), CalendarDate.Parse("2026-11-01"), channel: null);
}
// One-liner: is this version covered for this licence? Entitlement date and channel default to the build's metadata.
bool can30 = License.IsCovered("3.0.0");
bool canOn = License.IsCovered("3.0.0", entitlementDate: "2026-11-01", channel: "beta");
IsCovered devuelve true para una licencia no configurada y false cuando no hay ningún bloque válido disponible — un bloque ausente nunca es «cubierto». GetCoverage() devuelve CoverageInfo.Unavailable (Available == false) cuando la propia licencia no valida o no hay nada en caché.
4. Mensajes y la acción única
CoverageMessaging convierte un resultado en el texto orientado al cliente y exactamente una llamada a la acción, con tus URL y tu redacción:
static readonly CoverageMessageOptions Wording = new()
{
Product = "Acme Studio",
Vendor = "Acme Software",
UpdatesTerm = "maintenance", // default "updates"; also "support"
LineTemplate = "{product} {major}", // how a version line reads, e.g. "Acme Studio 2"; or "{major}.x"
RenewUrl = "https://acme.example/renew?key={key}",
UpgradeUrl = "https://acme.example/upgrade",
ImportLicenseFileUrl = "https://acme.example/support/offline-license",
RenewLabel = "Renew maintenance",
FormatDate = d => d.ToString("d MMMM yyyy", new CultureInfo("en-GB")), // {date} must read unambiguously
};
// Localize or rewrite any state: keys are the CoverageMessaging.Key* constants, templates may use
// {product} {version} {newestCovered} {line} {date} {vendor} {updatesTerm}
// Wording.TemplateOverrides[CoverageMessaging.KeyWindowLapsed] = "…";
static CoverageMessage BuildMessage(CoverageCheckResult check)
{
var cov = License.GetCoverage();
int? lineMajor = cov.Grants.Where(g => g.Versions?.MinInclusive is not null)
.Select(g => (int?)g.Versions!.MinInclusive!.Value.Major).Max();
var ctx = new CoverageMessaging.Context
{
Version = CoverageBuildInfo.Current.Version ?? "",
LineMajor = lineMajor, // for {line}
BoundaryDate = cov.SupportUntilUtc, // for {date} in "window lapsed"
NewestCovered = check.NewestCovered?.ToString(),
};
return CoverageMessaging.Build(check.Status, check.Reason, ctx, Wording);
}
// message.Text -> "This version (3.0.0) isn't included in your Acme Studio 2 license. You can keep using 2.4.0. Upgrade to use 3.0.0."
// message.Action -> CoverageAction.Upgrade (Renew | Upgrade | ImportLicenseFile | None)
// message.ActionUrl -> "https://acme.example/upgrade"
// message.ActionLabel -> "Upgrade"
La correspondencia es fija para que la interfaz muestre siempre el botón correcto: ventana vencida → Renew, fuera del rango de versiones → Upgrade, no disponible sin conexión → Import license file; Covered y NotConfigured producen un mensaje vacío con CoverageAction.None. Para una línea de estado dentro de la aplicación («¿qué tengo?») usa CoverageMessaging.StatusSentence(cov.Grants, cov.IsNotConfigured, Wording) — la misma frase que muestra el portal.
5. Importar un archivo de cobertura (aislado de la red)
string envelopeJson = File.ReadAllText("northwind.coverage.json"); // from your export or the license's download button
CoverageVerifyResult r = License.ImportCoverageBlock(envelopeJson); // verifies, binds to the licence, enforces anti-rollback; no network
if (!r.Ok)
{
// r.Status: Malformed | UnknownKey | BadSignature | RolledBack | WrongBinding | UnsupportedVersion
ShowImportError(r.Status);
}
Un resultado RolledBack significa que el archivo es más antiguo que lo que la máquina ya tiene (número de secuencia menor) y se ha ignorado; WrongBinding significa que se emitió para otra licencia u otro producto; UnknownKey significa que lo firmó una clave que tu compilación no incorpora — durante una rotación de claves, conserva la clave pública anterior en AdditionalPublicKeysBase64 exactamente igual que haces con las licencias y los leases.
6. Controlar tus propias descargas: la API de elegibilidad
Tu página de descargas o tu actualizador hace a Keyright la misma pregunta de servidor a servidor, autenticada con la clave del cliente. Keyright responde; tú decides si sirves el archivo.
curl -X POST $BASE/v1/coverage -H "content-type: application/json" \
-d '{"key":"LIC-…","product":"acme-studio","version":"2.5.0"}'
# -> { "eligible":true, "licenseValid":true, "status":"Covered", "reason":"None", "newestCovered":"2.4.0" }
# optional: "on":"2026-11-01" (entitlement date; default = the registered date, else today), "channel":"beta"
eligible es licenseValid && (status is Covered or NotConfigured); status/reason son la decisión en bruto para que redactes la página tú mismo. Desde un backend .NET:
using var http = new HttpClient { BaseAddress = new Uri("https://licensing.acme.example/") };
var resp = await http.PostAsJsonAsync("v1/coverage", new { key, product = "acme-studio", version = "2.5.0" });
var e = await resp.Content.ReadFromJsonAsync<Eligibility>();
if (e is { Eligible: true }) ServeDownload("2.5.0"); else ShowWhy(e!.Status, e.Reason, e.NewestCovered);
record Eligibility(bool Eligible, bool LicenseValid, string Status, string Reason, string? NewestCovered);
Referencia de comportamiento
| Situación | Qué ocurre |
|---|---|
| El cliente renueva / mejora de versión | Añades términos (panel o API). La aplicación los recoge en su siguiente arranque o refresco — sin volver a introducir la clave, sin reactivación. |
| El cliente vuelve a una compilación anterior | Sigue cubierta: una versión anterior está dentro de toda línea de versión y antes de toda ventana. |
| Dos versiones en paralelo, o versiones distintas por máquina | Cada compilación se evalúa a sí misma contra la misma licencia; los puestos son por máquina, como antes. |
| Reloj atrasado | La cobertura compara la fecha de habilitación firmada de la compilación con los términos, nunca el reloj local; la protección existente contra la manipulación del reloj sigue aplicándose a las pruebas y a las licencias por plazo. |
| Bloque eliminado o editado | La firma o la vinculación fallan → el SDK refresca; sin conexión y sin nada válido → UnavailableOffline. Nunca «cubierto». |
| Bloque antiguo reutilizado | Número de secuencia menor → rechazado; la cobertura se mantiene en el bloque más reciente aceptado. |
| Reembolso / revocación / transferencia | Sin cambios: revoca la licencia (la aplicación pasa a Free en el siguiente refresco); las transferencias conservan los términos. |
| Tu plan deja de incluir la cobertura | Tras un periodo de gracia el servicio emite bloques no aplicados; las compilaciones que aplican la cobertura siguen funcionando; los términos se conservan y los bloques aplicados se reanudan al volver a mejorar el plan. |
Cloud frente a autoalojado
La cobertura se comporta de forma idéntica en ambos modos — los preajustes de política, el bloque firmado, las props de MSBuild y las llamadas al SDK son los mismos. Lo que difiere:
- Derecho de uso. En Keyright Cloud la cobertura forma parte del plan superior (Scale y Enterprise); los planes inferiores la ven bloqueada con una invitación a mejorar el plan. Autoalojado, está incluida en las licencias que llevan la característica de cobertura. En ambos casos, un espacio de trabajo que pierde el derecho recibe bloques no aplicados tras un periodo de gracia en lugar de dejar fuera a los clientes, y los bloques aplicados se reanudan cuando el derecho vuelve.
- URL base / token.
$BASEeshttps://keyright.delta1labs.comen Cloud, o la URL de tu propio servicio de emisión en autoalojado;$TOKENes el token de sesión de tu panel (Cloud) o el token de administración del tenant/entorno (autoalojado).ServiceUrlyPublicKeyBase64en el SDK apuntan a la instancia correspondiente y a su clave de firma. - Todo lo demás — la API de elegibilidad (
/v1/coverage), la exportación/importación sin conexión, el registro — es la misma superficie en ambos modos.
Verificar
- Da a una edición una política (panel o
coveragePolicyen el upsert del tier), emite una licencia y abre su pestaña Coverage — confirma que la frase en lenguaje llano y los términos coinciden con el preajuste. - Ejecuta el comprobador de cobertura (o
GET /admin/licenses/{id}/coverage/check?version=…) para una versión que esperas que esté cubierta y otra que esperas que esté no cubierta — confirma el estado y el motivo (WindowLapsedfrente aOutsideVersionRange). - Compila tu aplicación con
KeyrightCoverageVersion+KeyrightCoverageEntitlementDate+KeyrightRequireCoverage=true, actívala con una licencia cubierta y confirma queCheckCoverageAsyncdevuelveCovered; sube la versión de la compilación más allá de los términos y confirma que devuelveNotCoveredcon la acción única correcta. - Para tu página de descargas, haz
POST /v1/coveragecon una clave de cliente + versión y confirma queeligiblecoincide con lo que ve la aplicación. - Aislado de la red: descarga el bloque firmado, impórtalo con
ImportCoverageBlocken una máquina desconectada y confirma la misma decisión sin red.
Consulta también
- Integración del SDK de .NET — el recorrido completo del cliente sobre el que se construye esta página.
- Panel de administración — todas las pantallas y las vistas Basic / Intermediate / Advanced.
- Activar, desactivar y mover puestos — leases, archivos sin conexión y activación aislada de la red (air-gapped).
- Medir el uso y vender créditos — vende consumo encima de una licencia.
- Referencia de la API HTTP — el mapa de endpoints.