bitwarden/server · error · BadRequestException

Invalid license

Error message

Invalid license

What it means

Thrown by POST /organizations (self-hosted license signup) when the uploaded license file cannot be read into an OrganizationLicense. ApiHelpers.ReadJsonFileFromBody<OrganizationLicense> returns null if the IFormFile is missing, empty, larger than 50 KB (51200 bytes), or not valid OrganizationLicense JSON. The guard therefore rejects any malformed, oversized, or absent license before signup runs.

Source

Thrown at src/Api/Controllers/SelfHosted/SelfHostedOrganizationLicensesController.cs:67

        _selfHostedOrganizationSignUpCommand = selfHostedOrganizationSignUpCommand;
        _organizationRepository = organizationRepository;
        _userService = userService;
        _updateOrganizationLicenseCommand = updateOrganizationLicenseCommand;
    }

    [HttpPost("")]
    public async Task<OrganizationResponseModel> CreateLicenseAsync(OrganizationCreateLicenseRequestModel model)
    {
        var user = await _userService.GetUserByPrincipalAsync(User);
        if (user == null)
        {
            throw new UnauthorizedAccessException();
        }

        var license = await ApiHelpers.ReadJsonFileFromBody<OrganizationLicense>(HttpContext, model.License);
        if (license == null)
        {
            throw new BadRequestException("Invalid license");
        }

        var result = await _selfHostedOrganizationSignUpCommand.SignUpAsync(license, user, model.Key,
            model.CollectionName, model.Keys?.PublicKey, model.Keys?.EncryptedPrivateKey);

        return new OrganizationResponseModel(result.Item1, null);
    }

    [HttpPost("{id}")]
    public async Task UpdateLicenseAsync(string id, LicenseRequestModel model)
    {
        var orgIdGuid = new Guid(id);
        if (!await _currentContext.OrganizationOwner(orgIdGuid))
        {
            throw new NotFoundException();
        }

        var license = await ApiHelpers.ReadJsonFileFromBody<OrganizationLicense>(HttpContext, model.License);

View on GitHub (pinned to e93b962371)

Solutions

  1. Re-export the organization license from the cloud Bitwarden instance and upload the unmodified .json file.
  2. Confirm the file is a OrganizationLicense (not a UserLicense) and matches the installed server version.
  3. Keep the file under 50 KB; if a license legitimately exceeds it, split/regenerate.
  4. Validate the JSON parses and has required fields (LicenseKey, InstallationId, etc.) before uploading.

Example fix

// before: posting the wrong file (user license) or none
//   form.License = userLicenseFile; // -> null parse -> 400
//
// after: post the organization license file as multipart/form-data
var form = new FormData();
form.append("license", organizationLicenseJsonFile, "license.json");
await fetch('/organizations/licenses', { method: 'POST', body: form });
Defensive patterns

Strategy: validation

Validate before calling

// Validate the license file client-side before the self-hosted org signup upload
function isValidOrgLicenseFile(file, text) {
  if (!file || file.size === 0 || file.size > 51200) return false;
  try { const j = JSON.parse(text); return !!j.licenseKey && !!j.installationId; } catch { return false; }
}
const text = await file.text();
if (!isValidOrgLicenseFile(file, text)) throw new Error('Invalid organization license');
await uploadOrgLicense(file);

Type guard

function isOrganizationLicense(j) {
  return j != null && typeof j === 'object'
    && typeof j.licenseKey === 'string'
    && typeof j.installationId === 'string';
}

Try / catch

try {
  await createSelfHostedOrg(licenseFile);
} catch (e) {
  if (e.status === 400 && /invalid license/i.test(e.message)) {
    showOperatorError('Re-export the organization license from the cloud instance and re-upload.');
  } else throw e;
}

Prevention

When it happens

Trigger: POST /organizations/licenses with no License file part, an empty upload, a file over 50 KB, a file that is not JSON, or JSON whose schema does not bind to OrganizationLicense (missing required fields, wrong casing).

Common situations: Operator exports a license from the cloud instance but the download is truncated or HTML error page; license JSON keys renamed in a newer server version; uploading a UserLicense instead of an OrganizationLicense; file exceeds the 50 KB body cap.

Related errors


AI-assisted analysis of bitwarden/server@e93b962371 (2026-08-13). Data as JSON: /api/errors/ab7c6cd7297d24de. Report an issue: GitHub.