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
- Re-export the organization license from the cloud Bitwarden instance and upload the unmodified .json file.
- Confirm the file is a OrganizationLicense (not a UserLicense) and matches the installed server version.
- Keep the file under 50 KB; if a license legitimately exceeds it, split/regenerate.
- 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
- Upload the unmodified organization license JSON exported from the cloud instance.
- Keep the file under 50 KB; parse and schema-check it before upload.
- Use OrganizationLicense (not UserLicense) matching the installed server version.
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
- Unknown Organization User
- The specified sponsored organization could not be found unde
- ExternalId cannot exceed 300 characters.
- ExternalId cannot exceed 300 characters.
- result.AsError.Message
AI-assisted analysis of bitwarden/server@e93b962371 (2026-08-13).
Data as JSON: /api/errors/ab7c6cd7297d24de.
Report an issue: GitHub.