immich-app/immich · error · BadRequestException

Invalid license key

Error message

Invalid license key

What it means

setLicense performs a quick prefix check on the license key before cryptographic verification: a valid key must start with 'IMCL-' (client) or 'IMSV-' (server). Keys with any other prefix are rejected immediately with a 400 Bad Request and never reach signature validation.

Solutions

  1. Verify the license key starts exactly with 'IMCL-' or 'IMSV-' (no leading whitespace or missing characters).
  2. Re-copy the full license key from the purchase email/account portal.
  3. Ensure you are not pasting the activation key into the license key field (the two are separate inputs).
  4. Confirm the key belongs to this Immich product edition; keys from unrelated products will have a different prefix.

Example fix

// before
await api.setLicense({ licenseKey: 'XXXX-1234', activationKey: actKey });
// after
const key = licenseInput.trim();
if (!key.startsWith('IMCL-') && !key.startsWith('IMSV-')) {
  throw new Error('license key must start with IMCL- or IMSV-');
}
await api.setLicense({ licenseKey: key, activationKey: actKey });
Defensive patterns

Strategy: validation

Validate before calling

const hasValidLicensePrefix = (key: string) => key.startsWith('IMCL-') || key.startsWith('IMSV-');
if (!hasValidLicensePrefix(licenseKey.trim())) {
  throw new Error('license key must start with IMCL- or IMSV-');
}

Type guard

const isLicenseKey = (v: unknown): v is `IMCL-${string}` | `IMSV-${string}` =>
  typeof v === 'string' && (v.startsWith('IMCL-') || v.startsWith('IMSV-'));

Try / catch

try {
  await api.setLicense({ licenseKey, activationKey });
} catch (e) {
  if (e instanceof BadRequestException && e.message === 'Invalid license key') {
    // show format/validity guidance to user
  }
}

Prevention

When it happens

Trigger: POST /users/license with a licenseKey string that does not start with 'IMCL-' or 'IMSV-' — typos, truncated keys, license keys from a different product, or pasting only the activation key into the license key field.

Common situations: Copy/paste losing part of the key (missing prefix); confusing the license key with the activation key; purchasing/licensing flow mixing up Immich product editions; whitespace or BOM corrupting the prefix.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15). Data as JSON: /api/errors/b62498a80c340baa. Report an issue: GitHub.

Appendix: source

Thrown at server/src/services/user.service.ts:177

  async getLicense(auth: AuthDto): Promise<LicenseResponseDto> {
    const metadata = await this.userRepository.getMetadata(auth.user.id);

    const license = metadata.find(
      (item): item is UserMetadataItem<UserMetadataKey.License> => item.key === UserMetadataKey.License,
    );
    if (!license) {
      throw new NotFoundException();
    }
    return { ...license.value, activatedAt: new Date(license.value.activatedAt) };
  }

  async deleteLicense({ user }: AuthDto): Promise<void> {
    await this.userRepository.deleteMetadata(user.id, UserMetadataKey.License);
  }

  async setLicense(auth: AuthDto, license: LicenseKeyDto): Promise<LicenseResponseDto> {
    if (!license.licenseKey.startsWith('IMCL-') && !license.licenseKey.startsWith('IMSV-')) {
      throw new BadRequestException('Invalid license key');
    }

    const { licensePublicKey } = this.configRepository.getEnv();

    const isClientLicenseValid = this.cryptoRepository.verifySha256(
      license.licenseKey,
      license.activationKey,
      licensePublicKey.client,
    );

    const isServerLicenseValid = this.cryptoRepository.verifySha256(
      license.licenseKey,
      license.activationKey,
      licensePublicKey.server,
    );

    if (!isClientLicenseValid && !isServerLicenseValid) {
      throw new BadRequestException('Invalid license key');

View on GitHub (pinned to e55ac299a4)