Budibase/budibase · error · HTTPError

Invalid offline license token

Error message

Invalid offline license token

What it means

verifyOfflineLicenseToken wraps signing.verifyLicenseToken and, if signature/decoding verification fails for any reason, throws HTTPError('Invalid offline license token', 400). It means the token string is not a validly signed offline license: bad signature, malformed payload, wrong encoding, or tampering.

Source

Thrown at packages/pro/src/sdk/licensing/licenses/offline/offline.ts:109

  const hosting = Hosting.SELF // offline only applicable in self host
  const _features = features.getOfflineFeatures(planType)
  const _quotas = quotas.getQuotas(hosting, planType)

  // apply the latest features and quotas to license
  license.features = union(license.features, _features)
  license.quotas = merge(license.quotas, _quotas)

  return license
}

export async function verifyOfflineLicenseToken(
  token: string
): Promise<OfflineLicense> {
  let license: OfflineLicense
  try {
    license = await signing.verifyLicenseToken(token)
  } catch {
    throw new HTTPError("Invalid offline license token", 400)
  }

  try {
    verifyExpiry(license)
  } catch {
    throw new HTTPError("Offline license has expired", 400)
  }

  try {
    await verifyInstallation(license)
  } catch (e) {
    if (e instanceof OfflineLicenseMismatchError) {
      throw new HTTPError(
        "Offline license does not match this installation",
        400
      )
    }
    throw e

View on GitHub (pinned to a81a902e9a)

Solutions

  1. Re-copy the token exactly as provided by the account portal — no added whitespace, quotes, or line breaks.
  2. Download a fresh offline license token from the account portal for this installation's identifier.
  3. Ensure the Budibase/pro version matches the era the token was issued for; upgrade if formats changed.
  4. Verify you are passing the offline license token, not a license key or an API key.

Example fix

// before
await activateOfflineLicenseKey(rawFileContents) // may include trailing newline/quotes
// after
const token = rawFileContents.trim().replace(/^"|"$/g, "")
await activateOfflineLicenseToken(token)
Defensive patterns

Strategy: validation

Validate before calling

function looksLikeLicenseToken(token: string): boolean {
  const t = token.trim()
  return t.length > 0 && !t.includes("\n") && t.split(".").length >= 2
}
if (!looksLikeLicenseToken(token)) throw new Error("Not a valid offline license token format")

Type guard

function isNonEmptyString(v: unknown): v is string {
  return typeof v === "string" && v.trim().length > 0
}

Try / catch

try {
  await activateOfflineLicenseToken(token)
} catch (e) {
  if (e instanceof HTTPError && e.status === 400 && e.message === "Invalid offline license token") {
    // re-copy token from portal export; check for truncation/whitespace
  } else throw e
}

Prevention

When it happens

Trigger: Calling verifyOfflineLicenseToken(token) (directly or via activateOfflineLicenseToken) with a token that fails cryptographic verification — truncated string, extra whitespace/newlines, altered payload, wrong signing key, or not a license token at all.

Common situations: Copy-paste errors when transferring the token file contents (missing characters, added quotes/whitespace); token from a different Budibase version with an incompatible format; attempting to use a JWT or other token type as an offline license; expired signing keys after an upgrade.

Related errors


AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29). Data as JSON: /api/errors/12c7406a7a95b3c0. Report an issue: GitHub.