{"record":{"id":"da11f567e796031c","repo":"Budibase/budibase","slug":"offline-license-has-expired","errorCode":null,"errorMessage":"Offline license has expired","messagePattern":"Offline license has expired","errorType":"exception","errorClass":"HTTPError","httpStatus":400,"severity":"error","filePath":"packages/pro/src/sdk/licensing/licenses/offline/offline.ts","lineNumber":115,"sourceCode":"  license.quotas = merge(license.quotas, _quotas)\n\n  return license\n}\n\nexport async function verifyOfflineLicenseToken(\n  token: string\n): Promise<OfflineLicense> {\n  let license: OfflineLicense\n  try {\n    license = await signing.verifyLicenseToken(token)\n  } catch {\n    throw new HTTPError(\"Invalid offline license token\", 400)\n  }\n\n  try {\n    verifyExpiry(license)\n  } catch {\n    throw new HTTPError(\"Offline license has expired\", 400)\n  }\n\n  try {\n    await verifyInstallation(license)\n  } catch (e) {\n    if (e instanceof OfflineLicenseMismatchError) {\n      throw new HTTPError(\n        \"Offline license does not match this installation\",\n        400\n      )\n    }\n    throw e\n  }\n\n  return license\n}\n\nexport async function getOfflineLicense(): Promise<License | undefined> {","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/pro/src/sdk/licensing/licenses/offline/offline.ts#L97-L133","documentation":"After the token signature is verified, verifyOfflineLicenseToken calls verifyExpiry and converts any expiry failure (past expireAt or unparseable date) into HTTPError('Offline license has expired', 400). This is the user-facing expiry error for offline license activation and refresh (it also surfaces when getOfflineLicense refreshes the cached license).","triggerScenarios":"verifyOfflineLicenseToken(token) where the decoded license's expireAt is before the current time (or invalid), e.g. activating a stale offline token or the periodic cache refresh re-verifying an aged license.","commonSituations":"Annual offline license not renewed before the expiry date; air-gapped environments that can't auto-renew; server clock drift making a valid license appear expired; backups restored with an old saved offlineLicenseToken.","solutions":["Renew the subscription and activate a fresh offline license token from the account portal.","Check the server clock (NTP) — correct time if drift caused the false expiry.","Replace the stored token: activateOfflineLicenseToken(newToken), which saves and refreshes the cache.","If the license should still be valid, decode the token and compare expireAt with the portal's records; re-export if they disagree."],"exampleFix":"// before\nawait activateOfflineLicenseToken(savedOldToken)\n// after\ntry {\n  await activateOfflineLicenseToken(savedOldToken)\n} catch (e) {\n  if (e instanceof HTTPError && e.message === \"Offline license has expired\") {\n    const fresh = await promptUserForNewOfflineToken()\n    await activateOfflineLicenseToken(fresh)\n  } else {\n    throw e\n  }\n}","handlingStrategy":"try-catch","validationCode":"function isExpired(token: string): boolean {\n  const license = /* decode token payload */ as { expireAt: string }\n  const expireAt = new Date(license.expireAt).getTime()\n  return !Number.isFinite(expireAt) || Date.now() > expireAt\n}\nif (isExpired(token)) throw new Error(\"Renew offline license before activation\")","typeGuard":"function isExpiryError(e: unknown): boolean {\n  return e instanceof HTTPError && e.message === \"Offline license has expired\"\n}","tryCatchPattern":"try {\n  await activateOfflineLicenseToken(token)\n} catch (e) {\n  if (isExpiryError(e)) {\n    // begin renewal flow: obtain fresh token from account portal\n  } else throw e\n}","preventionTips":["Automate reminders tied to the license expireAt date.","Renew and re-export tokens before air-gapped maintenance windows.","Verify server time before diagnosing expiry errors.","Replace saved tokens promptly after renewal."],"tags":["licensing","offline-license","expiry"],"backgroundTag":"license-expired","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}