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

# Uploads

> File upload method and best practices

## Upload method

Files are uploaded with a presigned URL: you request a time-limited URL and upload directly to Azure Blob Storage, then create a submission referencing the returned `blobPath`.

**Flow:**

1. Call `/api/integrations/uploads/url` to get a presigned URL
2. Upload your file to the returned URL with a `PUT`
3. Create a submission with the returned `blobPath`

**Advantages:**

* Handles large files (up to the per-category maximums)
* Reduced load on your API server
* Good for parallel uploads

The URL expires after 1 hour, so request it shortly before you upload.

## Presigned URL flow

### 1. Generate upload URL

POST `/api/integrations/uploads/url`

```json theme={null}
{
  "filename": "campaign-video.mp4",
  "contentType": "video/mp4"
}
```

Response `200 OK`:

```json theme={null}
{
  "uploadUrl": "https://<account>.blob.core.windows.net/assets/<path>?<sas>",
  "blobPath": "<workspace-id>/campaign-video.mp4",
  "expiresAt": "2026-06-03T13:00:00Z"
}
```

The presigned URL expires after 1 hour. Possible errors: `401` (auth), `403` (invalid filename), `415` (content type not in the allowlist), `422` (validation).

### 2. Upload to Azure

Use the returned `uploadUrl` to upload your file with a `PUT` and the `x-ms-blob-type: BlockBlob` header.

<CodeGroup>
  ```bash curl theme={null}
  curl -X PUT \
    -H "x-ms-blob-type: BlockBlob" \
    --data-binary @campaign-video.mp4 \
    "https://<account>.blob.core.windows.net/assets/<path>?<sas>"
  ```

  ```python requests theme={null}
  with open("campaign-video.mp4", "rb") as f:
      requests.put(
          upload_url,
          headers={"x-ms-blob-type": "BlockBlob"},
          data=f,
      )
  ```

  ```javascript node theme={null}
  const fs = require("fs");
  const data = fs.readFileSync("campaign-video.mp4");

  await fetch(uploadUrl, {
    method: "PUT",
    headers: {
      "x-ms-blob-type": "BlockBlob",
      "Content-Length": data.length,
    },
    body: data,
  });
  ```
</CodeGroup>

Once the upload succeeds, create a submission referencing the `blobPath`. See [Submissions](/concepts/submissions).

## Upload limits

| Limit                    | Value                                            |
| ------------------------ | ------------------------------------------------ |
| Max video size           | 5 GB                                             |
| Max audio size           | 500 MB                                           |
| Max image size           | 50 MB                                            |
| Max document size        | 100 MB                                           |
| Presigned URL expiration | 1 hour                                           |
| Allowed content types    | See [Assets](/concepts/assets#supported-formats) |

<Warning>
  Uploads exceeding the size limit are rejected. The content type must be in the allowlist and match the actual file format.
</Warning>

## Best practices

<Tip>
  For batch operations, request a presigned URL per file and upload in parallel. Batch the resulting assets into a single submission to stay within rate limits.
</Tip>

* **Batch uploads**: request presigned URLs per file and upload with parallelization.
* **Content type**: ensure `contentType` is in the allowlist and matches the actual file format.
* **URL freshness**: request the presigned URL shortly before uploading, since it expires after 1 hour.

## Troubleshooting

### Upload URL expired

If your presigned URL expires before the upload completes, generate a new one and try again.

### Invalid content type

Ensure your `contentType` is one of the allowed MIME types and matches the actual file format. For example:

* MP4 files: `video/mp4`
* PDF files: `application/pdf`
* MP3 files: `audio/mpeg`
