> ## Documentation Index
> Fetch the complete documentation index at: https://whatsapp-rust.jlucaso.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Media reupload

> Request re-upload of media with expired CDN URLs

The `MediaReupload` struct provides a method to request the server to re-upload media when the original CDN URL has expired. This is essential for long-running bots that need to download media from older messages.

## Access

Access media reupload operations through the client:

```rust theme={null}
let media_reupload = client.media_reupload();
```

## Methods

### request

Request the server to re-upload media for a message with an expired URL.

```rust theme={null}
pub async fn request(
    &self,
    req: &MediaReuploadRequest<'_>,
) -> Result<MediaRetryResult, MediaReuploadError>
```

<ParamField path="req" type="&MediaReuploadRequest" required>
  Parameters identifying the media message to re-upload.
</ParamField>

<ResponseField name="MediaRetryResult" type="MediaRetryResult">
  The result of the reupload request. On success, contains a new `direct_path` for downloading.
</ResponseField>

**Example:**

```rust theme={null}
use whatsapp_rust::features::media_reupload::{MediaReuploadRequest, MediaRetryResult};

let result = client.media_reupload().request(&MediaReuploadRequest {
    msg_id: "3EB0ABC123",
    chat_jid: &chat_jid,
    media_key: &media_key_bytes,
    is_from_me: false,
    participant: Some(&sender_jid), // Required for group messages
}).await?;

match result {
    MediaRetryResult::Success { direct_path } => {
        println!("New download path: {}", direct_path);
        // Use direct_path to download the media
    }
    MediaRetryResult::NotFound => {
        eprintln!("Media no longer available on server");
    }
    MediaRetryResult::GeneralError => {
        eprintln!("Server returned an error");
    }
    MediaRetryResult::DecryptionError => {
        eprintln!("Failed to decrypt server response");
    }
}
```

### request\_many

Request re-upload for several messages at once, concurrently. Use this for bulk recovery — e.g. resuming a client after a long offline period leaves many expired media URLs — since it completes in roughly one [request](#request) timeout instead of the serial sum of per-item waits.

```rust theme={null}
pub async fn request_many(
    &self,
    reqs: &[MediaReuploadRequest<'_>],
) -> Vec<Result<MediaRetryResult, MediaReuploadError>>
```

<ParamField path="reqs" type="&[MediaReuploadRequest]" required>
  The batch of reupload requests.
</ParamField>

<ResponseField name="results" type="Vec<Result<MediaRetryResult, MediaReuploadError>>">
  One result per input request, in the same order as `reqs`. One item failing does not abort the others.
</ResponseField>

**Example:**

```rust theme={null}
use whatsapp_rust::features::media_reupload::{MediaReuploadRequest, MediaRetryResult};

let reqs = vec![
    MediaReuploadRequest {
        msg_id: "3EB0ABC123",
        chat_jid: &chat_jid,
        media_key: &media_key_bytes,
        is_from_me: false,
        participant: Some(&sender_jid),
    },
    MediaReuploadRequest {
        msg_id: "3EB0DEF456",
        chat_jid: &other_chat_jid,
        media_key: &other_media_key_bytes,
        is_from_me: false,
        participant: None,
    },
];

for result in client.media_reupload().request_many(&reqs).await {
    match result {
        Ok(MediaRetryResult::Success { direct_path }) => {
            println!("New download path: {}", direct_path);
        }
        Ok(other) => eprintln!("Reupload failed: {:?}", other),
        Err(e) => eprintln!("Request error: {}", e),
    }
}
```

<Warning>
  Duplicate `msg_id`s within one batch are rejected (past the first occurrence) with `MediaReuploadError::InvalidRequest` — the underlying `mediaretry` waiter is keyed on message id alone, so two in-flight waiters for the same id could otherwise resolve each other with the wrong payload. A message id is unique per message, so a duplicate in a batch is a caller mistake.
</Warning>

## Protocol flow

1. The client encrypts a `ServerErrorReceipt` protobuf using an HKDF-derived key from the media key
2. A `<receipt type="server-error">` stanza is sent with the encrypted payload and `<rmr>` metadata
3. The client waits up to 30 seconds for a `<notification type="mediaretry">` response
4. The response is decrypted and the new `directPath` is extracted

## Types

### MediaReuploadRequest

```rust theme={null}
pub struct MediaReuploadRequest<'a> {
    pub msg_id: &'a str,
    pub chat_jid: &'a Jid,
    pub media_key: &'a [u8],
    pub is_from_me: bool,
    pub participant: Option<&'a Jid>,
}
```

| Field         | Type           | Description                                                            |
| ------------- | -------------- | ---------------------------------------------------------------------- |
| `msg_id`      | `&str`         | The message ID containing the media                                    |
| `chat_jid`    | `&Jid`         | The chat JID where the message was received                            |
| `media_key`   | `&[u8]`        | Raw media key bytes (32 bytes, from the message's `mediaKey` field)    |
| `is_from_me`  | `bool`         | Whether the message was sent by you                                    |
| `participant` | `Option<&Jid>` | For group/broadcast messages, the participant JID who sent the message |

### MediaRetryResult

```rust theme={null}
pub enum MediaRetryResult {
    Success { direct_path: String },
    GeneralError,
    NotFound,
    DecryptionError,
}
```

| Variant           | Description                                                                   |
| ----------------- | ----------------------------------------------------------------------------- |
| `Success`         | Server re-uploaded the media. Contains the new `direct_path` for downloading. |
| `GeneralError`    | Server returned a general error.                                              |
| `NotFound`        | Media is no longer available on the server.                                   |
| `DecryptionError` | Failed to decrypt the server response.                                        |

## Error handling

The method returns `Result<MediaRetryResult, MediaReuploadError>`. The `MediaRetryResult` enum itself distinguishes between server-side success and failure; `MediaReuploadError` covers transport and validation failures:

```rust theme={null}
#[non_exhaustive]
pub enum MediaReuploadError {
    #[error("{0}")]
    Client(#[from] ClientError),
    #[error("client is not logged in")]
    NotLoggedIn,
    #[error("invalid media reupload request: {0}")]
    InvalidRequest(String),
    #[error("media retry notification timed out")]
    Timeout,
    #[error("{0}")]
    Internal(#[from] anyhow::Error),
}
```

* `Client` — wraps `ClientError` (transport/client-layer failures)
* `NotLoggedIn` — Cannot determine own JID
* `InvalidRequest` — Newsletter messages are not supported for media reupload; or, when using `request_many`, a `msg_id` appears more than once in the batch
* `Timeout` — The server did not respond within 30 seconds
* `Internal` — Encryption failure or other internal error

```rust theme={null}
use whatsapp_rust::MediaReuploadError;

match client.media_reupload().request(&req).await {
    Ok(MediaRetryResult::Success { direct_path }) => {
        // Re-download using new path
    }
    Ok(other) => {
        eprintln!("Reupload failed: {:?}", other);
    }
    Err(MediaReuploadError::Timeout) => {
        eprintln!("Request timed out");
    }
    Err(MediaReuploadError::NotLoggedIn) => {
        eprintln!("Not authenticated");
    }
    Err(e) => {
        eprintln!("Request error: {}", e);
    }
}
```

<Note>
  Media reupload requests have a 30-second timeout. If the server does not respond in time, the request fails with a timeout error.
</Note>

<Warning>
  Media reupload is not supported for newsletter messages. Newsletter messages do not have media keys, so the encrypted retry protocol cannot be used.
</Warning>

## See also

* [Media handling guide](/guides/media-handling) - Upload and download media
* [Upload API](/api/upload) - Upload media files
* [Download API](/api/download) - Download media files
