bitwarden/server · error · BadRequestException
Invalid license.
Error message
Invalid license.
What it means
Thrown (HTTP 400) on the VNext self-hosted premium-subscription license endpoint when ApiHelpers.ReadJsonFileFromBody<UserLicense> returns null. Identical semantics to [264]: returns null for a missing file, a payload over 50KB, or JSON that does not deserialize into UserLicense (deserialization errors are silently caught).
Source
Thrown at src/Api/Billing/Controllers/VNext/SelfHostedAccountBillingVNextController.cs:30
namespace Bit.Api.Billing.Controllers.VNext;
[Authorize("Application")]
[Route("account/billing/vnext/self-host")]
[SelfHosted(SelfHostedOnly = true)]
public class SelfHostedAccountBillingVNextController(
ICreatePremiumSelfHostedSubscriptionCommand createPremiumSelfHostedSubscriptionCommand) : BaseBillingController
{
[HttpPost("license")]
[InjectUser]
public async Task<IResult> UploadLicenseAsync(
[BindNever] User user,
PremiumSelfHostedSubscriptionRequest request)
{
var license = await ApiHelpers.ReadJsonFileFromBody<UserLicense>(HttpContext, request.License);
if (license == null)
{
throw new BadRequestException("Invalid license.");
}
var result = await createPremiumSelfHostedSubscriptionCommand.Run(user, license);
return Handle(result);
}
}
View on GitHub (pinned to e93b962371)
Solutions
- Re-download the user license JSON from the cloud portal and upload the unmodified file.
- Validate locally that the file is well-formed JSON with the expected UserLicense fields before uploading.
- Confirm the file is under 50KB and is a user (not organization) license.
Example fix
// before: upload blindly
await uploadLicenseVNext({ license: pickedFile });
// after: validate first
var text = await pickedFile.text();
var parsed = JSON.parse(text); // throws on malformed JSON
if (!parsed.licenseKey || !parsed.email) throw new Error('Not a valid user license');
await uploadLicenseVNext({ license: pickedFile }); Defensive patterns
Strategy: validation
Validate before calling
// Validate the uploaded file is JSON with expected UserLicense fields before calling the VNext endpoint.
const text = await request.license.text();
const parsed = JSON.parse(text);
if (!parsed.licenseKey || !parsed.email || !parsed.version) {
throw new Error('Not a valid Bitwarden user license');
} Type guard
// function isUserLicenseLike(o: unknown): o is { licenseKey: string; email: string; version: number } {
// return typeof o === 'object' && o !== null
// && typeof (o as any).licenseKey === 'string'
// && typeof (o as any).email === 'string'
// && typeof (o as any).version === 'number';
// } Try / catch
try {
await uploadLicenseVNext({ license: file });
} catch (e) {
if (e.isBadRequest && /invalid license/i.test(e.message)) {
showUploadError('Re-download the user license from the cloud portal and retry.');
} else { throw e; }
} Prevention
- Download the license JSON directly from the cloud portal without edits.
- Validate JSON shape client-side before upload.
- Ensure the file is a user license under 50KB.
When it happens
Trigger: Non-JSON or binary file uploaded; truncated license JSON; an organization license where a user license is required; a license whose schema is from an incompatible version; file larger than 50KB.
Common situations: Cloud/self-hosted version skew where UserLicense fields changed; wrong license file downloaded; upload wrapper corrupts the JSON.
Related errors
- Invalid license
- Failed to remove organization vault. Please contact support.
- Organization must have at least one confirmed owner.
- NoSeatsAvailable
- Organization Connection configuration malformed
AI-assisted analysis of bitwarden/server@e93b962371 (2026-08-13).
Data as JSON: /api/errors/936229d352c26ee4.
Report an issue: GitHub.