{"record":{"id":"1105b66e071000ca","repo":"signalapp/Signal-Server","slug":"400-bad-request-unsupported-level","errorCode":null,"errorMessage":"400 Bad Request (UNSUPPORTED_LEVEL)","messagePattern":"400 Bad Request \\(UNSUPPORTED_LEVEL\\)","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"service/src/main/java/org/whispersystems/textsecuregcm/controllers/SubscriptionController.java","lineNumber":428,"sourceCode":"        SubscriberCredentials.process(authenticatedAccount, subscriberId, clock);\n    try {\n      final Subscriptions.Record record = subscriptionManager.getSubscriber(subscriberCredentials);\n      final ProcessorCustomer processorCustomer = record.getProcessorCustomer()\n          .orElseThrow(() ->\n              // a missing customer ID indicates the client made requests out of order,\n              // and needs to call create_payment_method to create a customer for the given payment method\n              new ClientErrorException(Status.CONFLICT));\n\n      final String subscriptionTemplateId = getSubscriptionTemplateId(level, currency,\n          processorCustomer.processor());\n\n      final CustomerAwareSubscriptionPaymentProcessor manager = getCustomerAwareProcessor(\n          processorCustomer.processor());\n      subscriptionManager.updateSubscriptionLevelForCustomer(subscriberCredentials, record, manager, level,\n          currency, idempotencyKey, subscriptionTemplateId, this::subscriptionsAreSameType);\n      return new SetSubscriptionLevelSuccessResponse(level);\n    } catch (SubscriptionInvalidLevelException e) {\n      throw new BadRequestException(Response.status(Response.Status.BAD_REQUEST)\n          .entity(new SubscriptionController.SetSubscriptionLevelErrorResponse(List.of(\n              new SubscriptionController.SetSubscriptionLevelErrorResponse.Error(\n                  SubscriptionController.SetSubscriptionLevelErrorResponse.Error.Type.UNSUPPORTED_LEVEL,\n                  null))))\n          .build());\n    } catch (SubscriptionPaymentRequiresActionException e) {\n      throw new BadRequestException(Response.status(Response.Status.BAD_REQUEST)\n          .entity(new SetSubscriptionLevelErrorResponse(List.of(new SetSubscriptionLevelErrorResponse.Error(\n              SetSubscriptionLevelErrorResponse.Error.Type.PAYMENT_REQUIRES_ACTION, null))))\n          .build());\n    } catch (SubscriptionInvalidArgumentsException e) {\n      throw new BadRequestException(Response.status(Response.Status.BAD_REQUEST)\n          .entity(new SetSubscriptionLevelErrorResponse(List.of(new SetSubscriptionLevelErrorResponse.Error(\n              SetSubscriptionLevelErrorResponse.Error.Type.INVALID_ARGUMENTS, e.getMessage()))))\n          .build());\n    }\n  }\n","sourceCodeStart":410,"sourceCodeEnd":446,"githubUrl":"https://github.com/signalapp/Signal-Server/blob/100ab61c82627582c867d19e1c0561ba2781e927/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SubscriptionController.java#L410-L446","documentation":"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.","triggerScenarios":"POSTing setSubscriptionLevel with a level that has no configured subscription level (e.g. level 0 or a level absent from subscriptionConfiguration).","commonSituations":"Hardcoded legacy level numbers after the donation tiers changed; UI sending an experimental level; currency-specific levels that don't exist.","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"],"exampleFix":"// before\n{ \"level\": 7, \"currency\": \"usd\" }\n// after — use a configured level, e.g.\n{ \"level\": 5, \"currency\": \"usd\" }","handlingStrategy":"try-catch","validationCode":"// fetch supported levels first\nconst levels = await getAvailableSubscriptionLevels();\nif (!levels.includes(level)) throw new Error(`Unsupported level ${level}`);","typeGuard":null,"tryCatchPattern":"try { await setSubscriptionLevel(level, currency); } catch (e) { if (e.errorType === 'UNSUPPORTED_LEVEL') { await refreshTierList(); } else { throw e; } }","preventionTips":["Derive the level picker from server configuration, not hardcoded numbers","Re-fetch tiers after server upgrades"],"tags":["http-400","subscriptions","donations","validation"],"backgroundTag":"value-out-of-range","analyzedSha":"100ab61c82627582c867d19e1c0561ba2781e927","analyzedAt":"2026-09-09T13:29:47.883Z","contentChangedAt":"2026-09-09T13:29:47.883Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}