{"record":{"id":"4ea1e8f4015875a2","repo":"signalapp/Signal-Server","slug":"credential-does-not-support-the-requested-operatio","errorCode":null,"errorMessage":"credential does not support the requested operation","messagePattern":"credential does not support the requested operation","errorType":"exception","errorClass":"BackupPermissionException","httpStatus":403,"severity":"error","filePath":"service/src/main/java/org/whispersystems/textsecuregcm/backup/BackupManager.java","lineNumber":782,"sourceCode":"  }\n\n  /**\n   * Check that the authenticated backup user is authorized to use the provided backupLevel\n   *\n   * @param backupUser  The backup user to check\n   * @param backupLevel The authorization level to verify the backupUser has access to\n   * @throws BackupPermissionException if the backupUser is not authorized to access {@code backupLevel}\n   */\n  @VisibleForTesting\n  static void checkBackupLevel(final AuthenticatedBackupUser backupUser, final BackupLevel backupLevel)\n      throws BackupPermissionException {\n    if (backupUser.backupLevel().compareTo(backupLevel) < 0) {\n      Metrics.counter(ZK_AUTHZ_FAILURE_COUNTER_NAME, Tags.of(\n              UserAgentTagUtil.getPlatformTag(backupUser.userAgent()),\n              Tag.of(FAILURE_REASON_TAG_NAME, \"level\")))\n          .increment();\n\n      throw new BackupPermissionException(\"credential does not support the requested operation\");\n    }\n  }\n\n  /**\n   * Check that the authenticated backup user is authenticated with the given credential type\n   *\n   * @param backupUser     The backup user to check\n   * @param credentialType The credential type to require\n   * @throws BackupWrongCredentialTypeException error if the backup user is not authenticated with the given\n   * {@code credentialType}\n   */\n  @VisibleForTesting\n  static void checkBackupCredentialType(final AuthenticatedBackupUser backupUser, final BackupCredentialType credentialType) throws BackupWrongCredentialTypeException {\n    if (backupUser.credentialType() != credentialType) {\n      Metrics.counter(ZK_AUTHZ_FAILURE_COUNTER_NAME,\n              FAILURE_REASON_TAG_NAME, \"credential_type\")\n          .increment();\n","sourceCodeStart":764,"sourceCodeEnd":800,"githubUrl":"https://github.com/signalapp/Signal-Server/blob/100ab61c82627582c867d19e1c0561ba2781e927/service/src/main/java/org/whispersystems/textsecuregcm/backup/BackupManager.java#L764-L800","documentation":"BackupPermissionException thrown by BackupManager.checkBackupLevel when the authenticated backup user's backupLevel is lower than the level the operation requires. FREE-level credentials cannot perform operations gated on BackupLevel.PAID (such as media copies). An authorization-failure counter with a 'level' reason tag is incremented.","triggerScenarios":"Authenticating with a FREE backup credential and then calling PAID-only endpoints — e.g. getCopyQuota/copy media, or any handler that calls checkBackupLevel(user, BackupLevel.PAID) — while the account's voucher expired or the receipt level only grants FREE.","commonSituations":"Paid subscription lapsing so the account downgraded to FREE while the client keeps issuing paid-tier requests; redeeming a lower receipt level; testing with free-tier credentials against paid endpoints; cached credentials outliving the paid period.","solutions":["Redeem a fresh paid receipt to extend the backup voucher and re-authenticate to get a PAID credential","Route the operation through the FREE-tier API surface if paid features are not needed","Detect the level authorization error client-side and pause paid-tier sync until the subscription is renewed","Verify the redeemed receiptLevel actually maps to BackupLevel.PAID before expecting paid operations to succeed"],"exampleFix":"// before\ncopyMedia(freeBackupUser, toCopy); // BackupPermissionException\n// after\nif (backupUser.backupLevel().compareTo(BackupLevel.PAID) >= 0) {\n  copyMedia(backupUser, toCopy);\n} else {\n  redeemPaidReceiptThenRetry();\n}","handlingStrategy":"validation","validationCode":"if (backupUser.backupLevel().compareTo(BackupLevel.PAID) < 0) {\n  throw new IllegalStateException(\"operation requires PAID backup level\");\n}","typeGuard":null,"tryCatchPattern":"try { copyMedia(backupUser, toCopy); }\ncatch (BackupPermissionException e) {\n  if (e.getMessage().contains(\"does not support\")) { pausePaidSyncUntilRenewal(); } else { throw e; }\n}","preventionTips":["Check the authenticated backup level before calling paid-tier endpoints","Re-authenticate after redeeming receipts so credentials reflect the new level","Handle subscription lapse by downgrading client behavior to FREE operations","Gate paid features on the credential's actual level, not purchase state alone"],"tags":["backup","authorization","quota"],"backgroundTag":"permission-denied","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"}