signalapp/Signal-Server · error · BackupInvalidArgumentException

Must set at least one of message/media credential requests

Error message

Must set at least one of message/media credential requests

What it means

commitBackupId requires at least one BackupAuthCredentialRequest: the caller must request credentials for message backups, media backups, or both. Supplying two empty Optionals is a client contract violation surfaced as BackupInvalidArgumentException.

Solutions

  1. Include at least one of the message or media BackupAuthCredentialRequest fields in the request body.
  2. Fix client serialization so present credential requests are not dropped as null.
  3. Validate the request payload before invoking the manager and return a 400 with a precise message.

Example fix

// before
backupAuthManager.commitBackupId(account, device, Optional.empty(), Optional.empty());
// after
if (messagesReq.isEmpty() && mediaReq.isEmpty()) {
  throw new BadRequestException("must set at least one of message/media credential requests");
}
backupAuthManager.commitBackupId(account, device, messagesReq, mediaReq);
Defensive patterns

Strategy: validation

Validate before calling

if (messagesReq.isEmpty() && mediaReq.isEmpty()) {
  throw new BadRequestException("at least one of message/media credential requests is required");
}

Type guard

boolean hasAtLeastOneCredentialRequest(Optional<BackupAuthCredentialRequest> m, Optional<BackupAuthCredentialRequest> med) {
  return m.isPresent() || med.isPresent();
}

Try / catch

try {
  backupAuthManager.commitBackupId(account, device, messagesReq, mediaReq);
} catch (BackupInvalidArgumentException e) {
  return Response.status(400).entity(e.getMessage()).build();
}

Prevention

When it happens

Trigger: Calling commitBackupId / PUT /v1/backups/ with a request body omitting both messagesBackupCredentialRequest and mediaBackupCredentialRequest.

Common situations: Older clients predating the media-credential field sending empty payloads; clients serializing nullable fields incorrectly so both Optionals end up empty; API consumers hand-building JSON bodies.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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

Appendix: source

Thrown at service/src/main/java/org/whispersystems/textsecuregcm/backup/BackupAuthManager.java:111

   * @param device                          The device setting the account backup-id
   * @param messagesBackupCredentialRequest A request containing the blinded backup-id the client will use to upload
   *                                        message backups
   * @param mediaBackupCredentialRequest    A request containing the blinded backup-id the client will use to upload
   *                                        media backups
   * @throws RateLimitExceededException If too many backup-ids have been committed
   */
  public void commitBackupId(
      final Account account,
      final Device device,
      final Optional<BackupAuthCredentialRequest> messagesBackupCredentialRequest,
      final Optional<BackupAuthCredentialRequest> mediaBackupCredentialRequest)
      throws RateLimitExceededException, BackupPermissionException, BackupInvalidArgumentException {
    if (!device.isPrimary()) {
      throw new BackupPermissionException("Only primary device can set backup-id");
    }

    if (messagesBackupCredentialRequest.isEmpty() && mediaBackupCredentialRequest.isEmpty()) {
      throw new BackupInvalidArgumentException("Must set at least one of message/media credential requests");
    }

    final byte[] storedMessageCredentialRequest = account.getBackupCredentialRequest(BackupCredentialType.MESSAGES)
        .orElse(null);
    final byte[] storedMediaCredentialRequest = account.getBackupCredentialRequest(BackupCredentialType.MEDIA)
        .orElse(null);

    // If the provided credential request is null, we want to set to the existing request
    final byte[] targetMessageCredentialRequest = messagesBackupCredentialRequest
        .map(BackupAuthCredentialRequest::serialize)
        .orElse(storedMessageCredentialRequest);
    final byte[] targetMediaCredentialRequest = mediaBackupCredentialRequest
        .map(BackupAuthCredentialRequest::serialize)
        .orElse(storedMediaCredentialRequest);

    final boolean requiresMessageRotation =
        !MessageDigest.isEqual(targetMessageCredentialRequest, storedMessageCredentialRequest);
    final boolean requiresMediaRotation =

View on GitHub (pinned to 100ab61c82)