signalapp/Signal-Server · error · BadRequestException
400 Bad Request (UNSUPPORTED_LEVEL)
Error message
400 Bad Request (UNSUPPORTED_LEVEL)
What it means
setSubscriptionLevel catches SubscriptionInvalidLevelException and rethrows as a 400 whose error entity carries type UNSUPPORTED_LEVEL: the requested subscription level is not a supported/donatable level in the current configuration.
Solutions
- Query the available subscription levels from the server configuration/GET endpoints and pick a supported level
- Update the client's level list to match the current server configuration
- Catch this 400 and refresh/re-render the level picker for the user
Example fix
// before
{ "level": 7, "currency": "usd" }
// after — use a configured level, e.g.
{ "level": 5, "currency": "usd" } Defensive patterns
Strategy: try-catch
Validate before calling
// fetch supported levels first
const levels = await getAvailableSubscriptionLevels();
if (!levels.includes(level)) throw new Error(`Unsupported level ${level}`); Try / catch
try { await setSubscriptionLevel(level, currency); } catch (e) { if (e.errorType === 'UNSUPPORTED_LEVEL') { await refreshTierList(); } else { throw e; } } Prevention
- Derive the level picker from server configuration, not hardcoded numbers
- Re-fetch tiers after server upgrades
When it happens
Trigger: POSTing setSubscriptionLevel with a level that has no configured subscription level (e.g. level 0 or a level absent from subscriptionConfiguration).
Common situations: Hardcoded legacy level numbers after the donation tiers changed; UI sending an experimental level; currency-specific levels that don't exist.
Understand the failure class
Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.
Related errors
- 400 Bad Request (INVALID_ARGUMENTS)
- Invalid length
- Invalid create call link credential request
- Illegal timestamp
- Recipient list is empty
AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09).
Data as JSON: /api/errors/1105b66e071000ca.
Report an issue: GitHub.
Appendix: source
Thrown at service/src/main/java/org/whispersystems/textsecuregcm/controllers/SubscriptionController.java:428
SubscriberCredentials.process(authenticatedAccount, subscriberId, clock);
try {
final Subscriptions.Record record = subscriptionManager.getSubscriber(subscriberCredentials);
final ProcessorCustomer processorCustomer = record.getProcessorCustomer()
.orElseThrow(() ->
// a missing customer ID indicates the client made requests out of order,
// and needs to call create_payment_method to create a customer for the given payment method
new ClientErrorException(Status.CONFLICT));
final String subscriptionTemplateId = getSubscriptionTemplateId(level, currency,
processorCustomer.processor());
final CustomerAwareSubscriptionPaymentProcessor manager = getCustomerAwareProcessor(
processorCustomer.processor());
subscriptionManager.updateSubscriptionLevelForCustomer(subscriberCredentials, record, manager, level,
currency, idempotencyKey, subscriptionTemplateId, this::subscriptionsAreSameType);
return new SetSubscriptionLevelSuccessResponse(level);
} catch (SubscriptionInvalidLevelException e) {
throw new BadRequestException(Response.status(Response.Status.BAD_REQUEST)
.entity(new SubscriptionController.SetSubscriptionLevelErrorResponse(List.of(
new SubscriptionController.SetSubscriptionLevelErrorResponse.Error(
SubscriptionController.SetSubscriptionLevelErrorResponse.Error.Type.UNSUPPORTED_LEVEL,
null))))
.build());
} catch (SubscriptionPaymentRequiresActionException e) {
throw new BadRequestException(Response.status(Response.Status.BAD_REQUEST)
.entity(new SetSubscriptionLevelErrorResponse(List.of(new SetSubscriptionLevelErrorResponse.Error(
SetSubscriptionLevelErrorResponse.Error.Type.PAYMENT_REQUIRES_ACTION, null))))
.build());
} catch (SubscriptionInvalidArgumentsException e) {
throw new BadRequestException(Response.status(Response.Status.BAD_REQUEST)
.entity(new SetSubscriptionLevelErrorResponse(List.of(new SetSubscriptionLevelErrorResponse.Error(
SetSubscriptionLevelErrorResponse.Error.Type.INVALID_ARGUMENTS, e.getMessage()))))
.build());
}
}
View on GitHub (pinned to 100ab61c82)