brain/wiki/platform-editions-ee/license-keys.md
A license key is a self-hosted customer's activation/recovery handle for their Autumn billing identity — an opaque string, not a bundle of feature flags. The user pastes it into the billing UI; the AP backend delegates activation to the Activepieces console, which resolves the key to an Autumn customer (creating one if needed), attaches the license's plan, and returns autumnCustomerId plus a customer-scoped Autumn key. Plan limits and feature flags are then projected from Autumn entitlements — never from the key itself. EE + Cloud; the whole seam is a CE no-op on billingProvider.
History: the legacy system — public
/v1/license-keys/*endpoints,licenseKeysService(verifyKeyOrReturnNull,applyLimits,downgradeToFreePlan), the dailyTRIAL_TRACKERjob, and allsecrets.activepieces.comcalls — was deleted when billing moved to Autumn. Already-released self-hosted builds bundle their own copy of that path; nothing in the current codebase serves them.
autumnBilling.activateLicense({ platformId, licenseKey }) — calls autumnConsole.activate({ licenseKey }) (POST {console}/api/v1/billing/activate, key as Bearer token), then saves platform_plan.licenseKey, stores the returned credentials via platformPlanService.setAutumnCredentials, and runs refreshEntitlements./activate is idempotent — a key maps to one Autumn customer, so re-activating on a fresh instance returns the same customer + creds (support hands the key back to a customer who lost their instance).refreshEntitlements — fetches the Autumn customer and writes mapAutumnFeaturesToPlatformPlan output onto platform_plan: plan, billedTeamProjectsLimit, usersLimit, activeFlowsLimit, includedCredits, and every boolean flag feature.ensureEnrolled — lazy enrollment under a distributedLock; if a licenseKey is already stored it re-activates through the console, otherwise enrollFree with the platform owner's email.provisionLicenseKeyIfPaid — during refreshEntitlements, self-serve paid customers who never entered a key get one minted by the console and saved, so every paying platform ends up with a recovery handle.POST /v1/platform-billing/activate — body { licenseKey }, securityAccess.platformAdminOnly([USER]); thin wrapper over billingProvider.activateLicense with platformId from the principal.POST /v1/admin/platforms/apply-license-key — cloud admin (module-level api-key header preHandler checked against AppSystemProp.API_KEY); body { email, licenseKey }; resolves email → platform-admin user → owned platform, then calls the same activateLicense.license_keys table, plan-to-attach + term, trial issuance, autumn_customers ledger). Old-world per-feature flags on the key no longer exist.activateLicense the console call happens before platform_plan.licenseKey is saved — a rejected key is never persisted.AUTUMN_CONSOLE_URL is a hardcoded constant in autumn-utils.ts (currently the testing console); all console calls go through safeHttp with a request timeout.licenseKey column on platform_plan is retained; there is no expiry job in AP — plan lapse is handled console/Autumn-side and lands here via entitlement refresh. A license's expiresAt currently has no effect for non-trial keys: the console's comp attach sends customize: { price: null } with no ends_at, so the comped plan never lapses. Nothing in AP reads licenseExpiresAt either.expiresAt activates into no plan at all. Console activate attaches only when isTrial && trialDaysRemaining(expiresAt) >= 1, else when !isTrial (comp) — a trial whose remaining days round to 0 falls through both branches, the customer is created with no subscription, and Autumn's auto_enable puts it on free. The platform then gets every EE flag revoked, one seat, billingEnforced on and powered-by branding on its first request after upgrade.mapAutumnFeaturesToPlatformPlan does flags[feature] = entitlements.flags[feature] ?? false, so any flag the target plan omits is revoked — a license-key → plan mapping that drops one feature silently downgrades that customer. Audit a migration mapping flag-by-flag against the live Autumn catalog before shipping it, not just plan-by-plan.platform_plan flags stand; ensureEnrolled is re-attempted every 300s (getEnrollAttemptKey), and entitlement refresh is throttled to 15 min thereafter.enrollFree lands the platform on Autumn free (aiProvidersEnabled: false, usersLimit: 1, billingEnforced: true, showPoweredBy: true), which is materially narrower than the OPEN_SOURCE_PLAN an unlicensed EE instance used to get.Entry point: activateLicense on billingProvider (CE no-op in billing-provider.ts, EE impl in autumn-billing.ts), called from the platform-plan controller and the cloud admin service.
packages/server/api/src/app/ee/platform/platform-plan/platform-plan.controller.ts — POST /v1/platform-billing/activatepackages/server/api/src/app/platform/billing-provider.ts — activateLicense(params) seam (CE no-op)packages/server/api/src/app/ee/platform/platform-plan/billing-providers/autumn-billing.ts — EE activateLicense implpackages/server/api/src/app/ee/platform/platform-plan/billing-providers/autumn-utils.ts — autumnConsole.activate, ensureEnrolled, refreshEntitlements, provisionLicenseKeyIfPaidpackages/server/api/src/app/ee/platform/admin/ — admin controller (/platforms/apply-license-key) and applyLicenseKeyByEmail servicepackages/core/shared/src/lib/management/platform/platform.request.ts — ApplyLicenseKeyByEmailRequestBodypackages/web/src/features/billing/components/ — activate-license-dialog.tsx (activation flow) and license-key.tsx (key display)packages/web/src/api/platforms-api.ts — activateLicenseKey(); mutation in packages/web/src/hooks/platform-hooks.tsPaths verified 2026-07-26. An earlier version described the pre-Autumn world (packages/server/api/src/app/ee/license-keys/, remote verification against secrets.activepieces.com, applyLimits, TRIAL_TRACKER); that module was removed.