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

# Upload Large Video

> Upload videos of any size using resumable uploads with the TUS protocol.

For videos larger than 100MB, use the resumable upload flow. This two-step process generates an upload URL, then your client uploads directly using the [TUS protocol](https://tus.io/).

## Step 1: Create Upload URL

`POST` `/v1/gallery/upload/direct`

### Request Body

<ParamField body="fileName" type="string">
  Desired filename for the video.
</ParamField>

<ParamField body="folderId" type="string">
  Destination folder ID.
</ParamField>

<ParamField body="maxDurationSeconds" type="integer">
  Maximum video duration in seconds. Default: 21600 (6 hours).
</ParamField>

<ParamField body="expiryMinutes" type="integer">
  How long the upload URL remains valid. Default: 30, max: 360.
</ParamField>

<ParamField body="metadata" type="object">
  Custom metadata to attach to the video.
</ParamField>

### Headers

<ParamField header="Authorization" type="string" required>
  Bearer token (e.g. `Authorization: Bearer YOUR_API_KEY`)
</ParamField>

### Response

<ResponseField name="uploadUrl" type="string">
  The TUS upload URL. Use this with a TUS client to upload the video.
</ResponseField>

<ResponseField name="fileId" type="string">
  The file ID. Use this to check upload status.
</ResponseField>

<ResponseField name="expiresAt" type="string">
  ISO timestamp when the upload URL expires.
</ResponseField>

<ResponseField name="tusVersion" type="string">
  TUS protocol version (`1.0.0`).
</ResponseField>

<ResponseField name="instructions" type="object">
  Upload instructions including required headers and status check URL.
</ResponseField>

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://api.voyantcloud.com/v1/gallery/upload/direct \
    -H "Authorization: Bearer $VOYANT_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "fileName": "conference-recording.mp4",
      "expiryMinutes": 60,
      "metadata": {"event": "annual-conference-2025"}
    }'
  ```

  ```javascript Node.js theme={null}
  const response = await fetch("https://api.voyantcloud.com/v1/gallery/upload/direct", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.VOYANT_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      fileName: "conference-recording.mp4",
      expiryMinutes: 60,
      metadata: { event: "annual-conference-2025" },
    }),
  });

  const { uploadUrl, fileId } = await response.json();
  ```

  ```python Python theme={null}
  import os
  import requests

  response = requests.post(
      "https://api.voyantcloud.com/v1/gallery/upload/direct",
      headers={
          "Authorization": f"Bearer {os.environ['VOYANT_API_KEY']}",
          "Content-Type": "application/json",
      },
      json={
          "fileName": "conference-recording.mp4",
          "expiryMinutes": 60,
          "metadata": {"event": "annual-conference-2025"},
      },
  )

  data = response.json()
  upload_url = data["uploadUrl"]
  file_id = data["fileId"]
  ```
</RequestExample>

<ResponseExample>
  ```json 201 Created theme={null}
  {
    "uploadUrl": "https://upload.example.com/tus/abc123...",
    "fileId": "gal_file_xyz789",
    "expiresAt": "2025-01-15T12:30:00.000Z",
    "tusVersion": "1.0.0",
    "instructions": {
      "protocol": "TUS",
      "minChunkSize": 5242880,
      "headers": {
        "Tus-Resumable": "1.0.0"
      },
      "checkStatus": "/v1/gallery/files/gal_file_xyz789"
    }
  }
  ```
</ResponseExample>

## Step 2: Upload the Video

Use any [TUS client](https://tus.io/implementations) to upload directly to the `uploadUrl`. The upload is resumable—if interrupted, it can continue from where it left off.

### JavaScript Example (tus-js-client)

```javascript theme={null}
import * as tus from "tus-js-client";

const file = document.getElementById("video-input").files[0];

const upload = new tus.Upload(file, {
  endpoint: uploadUrl, // From Step 1
  retryDelays: [0, 1000, 3000, 5000],
  chunkSize: 50 * 1024 * 1024, // 50MB chunks
  metadata: {
    filename: file.name,
    filetype: file.type,
  },
  onError: (error) => {
    console.error("Upload failed:", error);
  },
  onProgress: (bytesUploaded, bytesTotal) => {
    const percentage = ((bytesUploaded / bytesTotal) * 100).toFixed(2);
    console.log(`${percentage}%`);
  },
  onSuccess: () => {
    console.log("Upload complete!");
  },
});

upload.start();
```

### Python Example (tuspy)

```python theme={null}
from tusclient import client

tus_client = client.TusClient(upload_url)

uploader = tus_client.uploader(
    "./conference-recording.mp4",
    chunk_size=50 * 1024 * 1024,  # 50MB chunks
)

uploader.upload()
print("Upload complete!")
```

## Step 3: Check Status

Poll the file endpoint or subscribe to webhooks to know when processing completes.

```bash theme={null}
curl https://api.voyantcloud.com/v1/gallery/files/gal_file_xyz789 \
  -H "Authorization: Bearer $VOYANT_API_KEY"
```

### Status Values

| Status       | Description                            |
| ------------ | -------------------------------------- |
| `pending`    | Upload URL created, waiting for upload |
| `processing` | Video is being processed               |
| `completed`  | Ready for playback                     |
| `failed`     | Processing failed                      |

## Webhooks

Subscribe to these events for real-time notifications:

* `gallery.file.ready` — Video processing completed successfully
* `gallery.file.failed` — Video processing failed

<Tip>
  For videos under 100MB, you can use the simpler [Upload File](/api-reference/gallery/upload-file) endpoint with multipart form data.
</Tip>

<Warning>
  Upload URLs expire after the specified `expiryMinutes`. If the URL expires before upload completes, you'll need to create a new one.
</Warning>
