signalapp/Signal-Server · error · BackupPermissionException

credential does not support the requested operation

Error message

credential does not support the requested operation

What it means

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.

Solutions

  1. Redeem a fresh paid receipt to extend the backup voucher and re-authenticate to get a PAID credential
  2. Route the operation through the FREE-tier API surface if paid features are not needed
  3. Detect the level authorization error client-side and pause paid-tier sync until the subscription is renewed
  4. Verify the redeemed receiptLevel actually maps to BackupLevel.PAID before expecting paid operations to succeed

Example fix

// before
copyMedia(freeBackupUser, toCopy); // BackupPermissionException
// after
if (backupUser.backupLevel().compareTo(BackupLevel.PAID) >= 0) {
  copyMedia(backupUser, toCopy);
} else {
  redeemPaidReceiptThenRetry();
}
Defensive patterns

Strategy: validation

Validate before calling

if (backupUser.backupLevel().compareTo(BackupLevel.PAID) < 0) {
  throw new IllegalStateException("operation requires PAID backup level");
}

Try / catch

try { copyMedia(backupUser, toCopy); }
catch (BackupPermissionException e) {
  if (e.getMessage().contains("does not support")) { pausePaidSyncUntilRenewal(); } else { throw e; }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09). Data as JSON: /api/errors/4ea1e8f4015875a2. Report an issue: GitHub.

Appendix: source

Thrown at service/src/main/java/org/whispersystems/textsecuregcm/backup/BackupManager.java:782

  }

  /**
   * Check that the authenticated backup user is authorized to use the provided backupLevel
   *
   * @param backupUser  The backup user to check
   * @param backupLevel The authorization level to verify the backupUser has access to
   * @throws BackupPermissionException if the backupUser is not authorized to access {@code backupLevel}
   */
  @VisibleForTesting
  static void checkBackupLevel(final AuthenticatedBackupUser backupUser, final BackupLevel backupLevel)
      throws BackupPermissionException {
    if (backupUser.backupLevel().compareTo(backupLevel) < 0) {
      Metrics.counter(ZK_AUTHZ_FAILURE_COUNTER_NAME, Tags.of(
              UserAgentTagUtil.getPlatformTag(backupUser.userAgent()),
              Tag.of(FAILURE_REASON_TAG_NAME, "level")))
          .increment();

      throw new BackupPermissionException("credential does not support the requested operation");
    }
  }

  /**
   * Check that the authenticated backup user is authenticated with the given credential type
   *
   * @param backupUser     The backup user to check
   * @param credentialType The credential type to require
   * @throws BackupWrongCredentialTypeException error if the backup user is not authenticated with the given
   * {@code credentialType}
   */
  @VisibleForTesting
  static void checkBackupCredentialType(final AuthenticatedBackupUser backupUser, final BackupCredentialType credentialType) throws BackupWrongCredentialTypeException {
    if (backupUser.credentialType() != credentialType) {
      Metrics.counter(ZK_AUTHZ_FAILURE_COUNTER_NAME,
              FAILURE_REASON_TAG_NAME, "credential_type")
          .increment();

View on GitHub (pinned to 100ab61c82)