signalapp/Signal-Server · error · ClientErrorException
Media quota exhausted
Error message
Media quota exhausted
What it means
ArchiveController.copyMedia throws this (HTTP 413) when BackupManager reports OUT_OF_QUOTA: copying a media object from the attachments CDN to the backup CDN would exceed the account's backup media storage quota. The server refuses the copy before consuming storage.
Solutions
- Delete unneeded backed-up media via the delete-media endpoint to free quota, then retry the copy
- Check remaining quota before issuing copy batches and chunk requests to fit within it
- Surface the 413 to the user so they can upgrade their backup plan or clear old media
- Do not blindly retry: the error is deterministic until storage is freed
Example fix
// before
copyAllMedia(items); // throws 413 when quota is exhausted
// after
long remaining = fetchRemainingQuota();
List<CopyMediaRequest> fitting = items.stream()
.filter(i -> i.uploadLength() <= remaining)
.toList();
if (fitting.size() < items.size()) promptUserToFreeQuota();
copyAllMedia(fitting); Defensive patterns
Strategy: validation
Validate before calling
long totalAfterCopy = currentBackupBytes + items.stream().mapToLong(CopyMediaRequest::uploadLength).sum();
if (totalAfterCopy > quotaLimit) { freeSpaceOrNotifyUser(); return; } Try / catch
try { copyMedia(items); } catch (ClientZonedDateTimeException e) {} catch (WebApplicationException e) { if (e.getResponse().getStatus() == 413) { handleQuotaExhausted(); } } Prevention
- Track quota usage locally after every copy/delete
- Chunk copy batches to stay under quota
- Treat 413 as terminal, not retryable
When it happens
Trigger: POST to the anonymous copy-media batch endpoint where one or more items' media would push total backed-up media bytes over the quota computed by backupManager.getCopyQuota.
Common situations: Backups accumulating past the paid/allowed quota; retrying copy batches after earlier partial failures; large attachment uploads when the user has almost no quota left; stale client assumptions about available space.
Understand the failure class
Background: payload too large / request exceeds maximum size: why libraries cap bytes and how to fix oversize payloads — this error's family across 50 libraries.
Related errors
- credential does not support the requested operation
- exceeded maximum uploadLength
- exceeded maximum uploadLength
AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09).
Data as JSON: /api/errors/503ba0c386245d1c.
Report an issue: GitHub.
Appendix: source
Thrown at service/src/main/java/org/whispersystems/textsecuregcm/controllers/ArchiveController.java:773
throws BackupFailedZkAuthenticationException, BackupWrongCredentialTypeException, BackupPermissionException, BackupInvalidArgumentException {
if (account.isPresent()) {
throw new BadRequestException("must not use authenticated connection for anonymous operations");
}
final AuthenticatedBackupUser backupUser =
backupManager.authenticateBackupUser(presentation.presentation, signature.signature, userAgent);
final BackupManager.CopyQuota copyQuota =
backupManager.getCopyQuota(backupUser, List.of(copyMediaRequest.toCopyParameters()), maxAttachmentSize);
final CopyResult copyResult = backupManager.copyToBackup(copyQuota).next()
.blockOptional()
.orElseThrow(() -> new IllegalStateException("Non empty copy request must return result"));
backupMetrics.updateCopyCounter(copyResult, UserAgentTagUtil.getPlatformTag(userAgent));
return switch (copyResult.outcome()) {
case SUCCESS -> new CopyMediaResponse(copyResult.cdn());
case SOURCE_WRONG_LENGTH -> throw new BadRequestException("Invalid length");
case SOURCE_NOT_FOUND -> throw new ClientErrorException("Source object not found", Response.Status.GONE);
case OUT_OF_QUOTA ->
throw new ClientErrorException("Media quota exhausted", Response.Status.REQUEST_ENTITY_TOO_LARGE);
};
}
public record CopyMediaBatchRequest(
@Schema(description = "A list of media objects to copy from the attachments CDN to the backup CDN")
@NotNull
@Size(min = 1, max = 1000)
List<@Valid CopyMediaRequest> items) {}
public record CopyMediaBatchResponse(
@Schema(description = "Detailed outcome information for each copy request in the batch")
List<Entry> responses) {
public record Entry(
@Schema(description = """
The outcome of the copy attempt.
A 200 indicates the object was successfully copied.View on GitHub (pinned to 100ab61c82)