Scrydon
DeploymentLifecycle

Licensing

How Scrydon license validation works, how to apply your bundle via the /setup wizard, and how to renew or rotate

Scrydon is licensed per organization. The license is delivered as a JSON bundle containing a signed JWT plus the matching RSA public key. The platform verifies the JWT on every request using the public key, so once the bundle is installed there are no further network calls required for verification.

{
  "jwt":       "eyJhbGciOiJSUzI1NiIs…",
  "publicKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkq…\n-----END PUBLIC KEY-----"
}

The bundle is the only accepted format. Bare .jwt files are rejected by the setup wizard — the public key must travel with the JWT so that signature verification works without a phone-home.

What a License Contains

The JWT payload encodes:

FieldDescription
subCustomer identifier
issIssuer (scrydon-license)
audAudience (scrydon-platform)
expExpiry timestamp (Unix epoch)
jtiUnique license ID
scrydon.orgOrganization name (shown in the UI)
scrydon.tierTier (starter, professional, enterprise)
scrydon.entitlements.cpuCoresLicensed CPU cores
scrydon.entitlements.ramGbLicensed RAM in GB
scrydon.entitlements.vramGbLicensed VRAM in GB (contractual only)

The JWT is signed with an RSA private key held by Scrydon. The matching public key ships inside the bundle — there is no pre-baked public key in the chart, which means key rotations are zero-downtime: a new bundle carries its own public key.

Applying a License — /setup wizard

After a fresh install, navigate to https://<your-host>/platform/setup (or /setup if you mounted the platform UI at the root). The wizard walks through five steps:

  1. License — paste the JSON bundle into the textarea, or drop the .json file into the upload area. The wizard:
    • parses the bundle and decodes the JWT
    • verifies the signature against the public key shipped in the bundle
    • checks exp against the current time (rejects already-expired licenses)
    • displays tier, organization, CPU / RAM / VRAM, and days-until-expiry
  2. Admin Account — create the first administrator. On submit, the license JWT and public key are persisted to the platform_config table (license_jwt, license_public_key), and the admin user is created.
  3. Organization — name your root tenant.
  4. Email — configure email delivery (Resend, SMTP, or skip and configure later).
  5. Complete — sets platform_config.setup_completed = "true" and redirects to the platform home.

The wizard is fail-closed: the route at /setup is only reachable while setup_completed is unset. Once a license has been installed and an admin exists, /setup redirects to sign-in even on transient auth-server outages.

Pre-seeding the license (skip step 1 of the wizard)

If you would rather not paste the bundle interactively, the chart accepts the bundle's jwt and publicKey as Helm secrets. The platform reads them on first startup and writes them into the platform_config table; subsequent restarts ignore the env vars.

auth:
  secrets:
    LICENSE: |
      eyJhbGciOiJSUzI1NiIs...
    LICENSE_PUBLIC_KEY: |
      -----BEGIN PUBLIC KEY-----
      MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA…
      -----END PUBLIC KEY-----

When both values are present, the wizard's License step opens already verified — the operator clicks Continue straight to the Admin step.

How Validation Works at Runtime

The @scrydon/better-auth-license Better-Auth plugin runs inside the api-platform pod. On every authenticated request that touches a license-gated capability it:

  1. Reads the current license_jwt and license_public_key from platform_config.
  2. Verifies the JWT signature against the stored public key.
  3. Checks exp against the current time.
  4. Enforces the tier-based feature flags encoded in the claims (e.g. analytics enabled, agentic enabled, max users).
  5. In online mode only, periodically calls the Scrydon license server (license.scrydon.com/api/license/validate) to check for revocation. If the phone-home call fails, the platform keeps using the cached verification result until the grace period elapses.

There is no license-check init container, no Kubernetes Secret named scrydon-license, and no mount of license.jwt on disk — the license lives in the database alongside other platform configuration, which means it survives pod restarts, image upgrades, and chart re-installs as long as the database is intact.

License Modes

ModeWhat it doesUse when
online (default)Verifies locally and periodically phones home to license.scrydon.com for revocation checks. Falls back to local-only verification within the grace period if phone-home fails.Connected clusters.
offlineLocal verification only. No outbound license calls.Restricted networks, regulated environments.
airgappedSame as offline but disables every other phone-home in the platform (license server, error reporting, telemetry).Truly air-gapped clusters — see Air-Gapped Deployment.

Configure via Helm:

license:
  enabled: true
  mode: online              # or offline / airgapped
  apiUrl: https://license.scrydon.com/api/license/validate
  gracePeriod: 2592000      # 30-day soft-fail window (seconds)

Grace Period

license.gracePeriod (default 30 days / 2 592 000 s) controls how long the platform tolerates failures of the license-server phone-home before locking down. The clock starts at the first failed phone-home; a successful call resets it. Once the grace period elapses without a successful call, the platform treats the license as invalid and disables license-gated features.

The grace period has no effect on offline / airgapped modes (no phone-home, nothing to fall back from).

An expired or revoked license does not kill running pods. License-gated capabilities return 403 from the API and the UI shows a banner. Restart the pods only after applying a valid license.

Inspecting a License

From the browser (no platform required)

The docs ship an in-browser License Checker. Drop the { jwt, publicKey } bundle (or paste a bare JWT) and it shows tier, organization, customer ID, entitlements, and time-until-expiry. Decoding happens entirely client-side — nothing leaves the page — so it works before a cluster exists, during procurement, or for triaging a support ticket.

From the UI

Platform Settings → License shows tier, organization, entitlements, expiry, and mode. Organization admins can copy the current JWT for support tickets and paste a renewed bundle.

From the API

curl -X POST https://<your-host>/api/auth/license/get-claims \
  -H "Content-Type: application/json" \
  -H "Cookie: <session-cookie>" \
  -d '{}'

This endpoint requires a valid session. In practice, use the UI or the auth client SDK (client.license.getClaims()).

Decoding the JWT locally

The JWT is a standard signed JSON Web Token — you can decode its payload (second segment) without any Scrydon tooling:

JWT=$(jq -r .jwt license-bundle.json)
SEG=$(echo "$JWT" | cut -d. -f2 | tr '-_' '+/')
PAD=$(( (4 - ${#SEG} % 4) % 4 ))
PAYLOAD=$(printf '%s%s' "$SEG" "$(printf '=%.0s' $(seq 1 $PAD))" | openssl base64 -d -A)
echo "$PAYLOAD" | jq .
{
  "sub": "cust_acme",
  "iss": "scrydon-license",
  "aud": "scrydon-platform",
  "exp": 1767225600,
  "jti": "lic_abc123",
  "scrydon": {
    "org": "Acme Corp",
    "tier": "enterprise",
    "entitlements": { "cpuCores": 16, "ramGb": 64, "vramGb": 0 }
  }
}

Convert exp to a date:

date -r 1767225600     # macOS
date -d @1767225600    # Linux

Renewal & Rotation

Your Scrydon account team will send a refreshed bundle before the current one expires. To apply it without downtime:

  1. Open Platform Settings → License → Update license.
  2. Paste the new { jwt, publicKey } bundle.
  3. Submit. The platform validates the new bundle, then atomically replaces the license_jwt and license_public_key rows.

No pod restart is required — the plugin re-reads from the DB on each request. License-gated features stay continuously available across the swap.

Why both fields rotate together

Scrydon's signing key may rotate independently of the license expiry (e.g. a private-key compromise, scheduled rotation). Because the public key travels with the JWT, a key rotation is just another bundle swap — the operator runs the same flow, and there is no pre-baked public key in the chart that would need to be updated separately.

Troubleshooting

License has expired

The JWT's exp has passed. Request a renewal from sales@scrydon.com and quote your customer identifier (the sub claim, visible under Settings → License).

Invalid signature

The JWT and public key in the bundle don't match — usually because the bundle was edited or copy-pasted with line truncation. Request a fresh bundle from your account team and paste it whole.

License check failed (network) in online mode

The platform cannot reach https://license.scrydon.com. Common causes:

  • Egress firewall blocking outbound HTTPS. Allow-list license.scrydon.com:443.
  • TLS interception by a corporate proxy without the proxy CA injected into the pods' trust store.
  • The grace period has elapsed and the platform has locked down license-gated features — apply a fresh bundle or restore connectivity to clear the lockout.

To switch to local-only verification, set license.mode: offline in your Helm values and run helm upgrade.

Setup is already complete when opening /setup

The /setup route is intentionally one-shot. Once platform_config.setup_completed = "true", the route redirects to sign-in. To re-run the wizard (e.g. after wiping the DB) set setup_completed = "false" in platform_config and reload.

Requesting a Renewal

Contact sales@scrydon.com or your Scrydon account representative before your current license expires. Provide your customer identifier (the JWT sub claim). Scrydon will issue a refreshed { jwt, publicKey } bundle.

On this page

On this page