API Reference
Videos
Advanced Upload
Multipart Upload

Multipart Upload

For large video files (over 100MB), we recommend using multipart uploads. This method splits your file into smaller chunks that are uploaded in parallel, providing better reliability and performance for large files.

Multipart upload is recommended for files larger than 100MB and supports files up to 60GB.

💡

For a simpler implementation, consider using the Uppy library which handles chunking, retries, and progress tracking automatically.

Overview

The multipart upload process consists of four steps:

  1. Initialize - Create a video object and request a multipart upload
  2. Get signed URLs - Request pre-signed URLs for each part (in batches for efficiency)
  3. Upload parts - Upload each chunk to its signed URL
  4. Complete - Signal that all parts have been uploaded

Initialize multipart upload

Create a video object by sending a PUT request to /videos/upload with useMultipart: true.

curl -X PUT https://app.ignitevideo.cloud/api/videos/upload \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
  "title": "Your video title",
  "mimeType": "video/mp4",
  "useMultipart": true
}'

Response

{
  "videoId": "[VIDEO_ID]",
  "title": "Your video title",
  "multipartUpload": {
    "uploadId": "[UPLOAD_ID]",
    "key": "[S3_KEY]"
  }
}
⚠️

Store the uploadId and key values - you'll need them for all subsequent requests.

Get signed URLs for parts

Before uploading parts, you need to obtain pre-signed URLs. For efficiency, request URLs in batches rather than individually.

Batch request (recommended)

Request signed URLs for multiple parts at once using the prepare-parts endpoint.

curl -X POST "https://app.ignitevideo.cloud/api/videos/upload/s3/multipart/[UPLOAD_ID]/prepare-parts?key=[S3_KEY]" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"startPart": 1, "endPart": 100}'

Response

{
  "parts": [
    { "partNumber": 1, "url": "[SIGNED_URL_FOR_PART_1]" },
    { "partNumber": 2, "url": "[SIGNED_URL_FOR_PART_2]" }
  ]
}

Single part request (fallback)

If you need a URL for a specific part, you can request it individually.

curl "https://app.ignitevideo.cloud/api/videos/upload/s3/multipart/[UPLOAD_ID]/[PART_NUMBER]?key=[S3_KEY]" \
-H "Authorization: Bearer YOUR_TOKEN"

Upload parts

Split your file into chunks (recommended: 5MB - 100MB per part) and upload each chunk to its corresponding signed URL using a PUT request.

Part sizing: Multipart upload requires a minimum part size of 5MB (except for the last part). We recommend 10MB chunks for a good balance between parallelization and overhead.

⚠️

The example below assumes you have fetched all signed URLs needed. For files with more than 100 parts, you'll need to fetch URLs in batches. See the Complete Example below for handling large files.

const CHUNK_SIZE = 10 * 1024 * 1024; // 10MB chunks
const file = yourVideoFile; // File object
const totalParts = Math.ceil(file.size / CHUNK_SIZE);
const uploadedParts = [];
 
// Upload parts in parallel (limit concurrency to 5)
const uploadPart = async (partNumber, signedUrl) => {
const start = (partNumber - 1) * CHUNK_SIZE;
const end = Math.min(start + CHUNK_SIZE, file.size);
const chunk = file.slice(start, end);
 
const response = await fetch(signedUrl, {
  method: 'PUT',
  body: chunk
});
 
// Get the ETag from the response headers
const etag = response.headers.get('ETag');
 
return {
  PartNumber: partNumber,
  ETag: etag
};
};
 
// Upload all parts
for (let i = 0; i < totalParts; i += 5) {
const batch = [];
for (let j = i; j < Math.min(i + 5, totalParts); j++) {
  const partNumber = j + 1;
  const signedUrl = partsData.parts.find(p => p.partNumber === partNumber)?.url;
  if (signedUrl) {
    batch.push(uploadPart(partNumber, signedUrl));
  }
}
const results = await Promise.all(batch);
uploadedParts.push(...results);
}
⚠️

Important: You must collect the ETag header from each part upload response. These are required to complete the multipart upload.

Complete multipart upload

After all parts have been uploaded, send a completion request with the list of parts and their ETags.

curl -X POST "https://app.ignitevideo.cloud/api/videos/upload/s3/multipart/[UPLOAD_ID]/complete?key=[S3_KEY]" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
  "parts": [
    { "PartNumber": 1, "ETag": "\"etag1\"" },
    { "PartNumber": 2, "ETag": "\"etag2\"" }
  ]
}'

For uploads with more than 100 parts, you can use fetchPartsFromStorage: true instead of passing the parts array. The server will retrieve the parts information automatically.

// For large uploads with many parts
const completeResponse = await fetch(
  `https://app.ignitevideo.cloud/api/videos/upload/s3/multipart/${uploadId}/complete?key=${encodeURIComponent(key)}`,
  {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer YOUR_TOKEN',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      fetchPartsFromStorage: true
    })
  }
);

Encoding process

After the multipart upload is completed, the video will be encoded. This process can take a while depending on the video size and encoding complexity.

You can check the encoding status of a video by calling the /videos/[VIDEO_ID] endpoint as described in the get video section.

Complete Example

Here's a complete JavaScript example implementing multipart upload with progress tracking:

async function uploadLargeVideo(file, title, token) {
  const CHUNK_SIZE = 10 * 1024 * 1024; // 10MB
  const BATCH_SIZE = 100;
  const CONCURRENT_UPLOADS = 5;
  
  // Step 1: Initialize multipart upload
  const initResponse = await fetch('https://app.ignitevideo.cloud/api/videos/upload', {
    method: 'PUT',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      title,
      mimeType: file.type,
      useMultipart: true
    })
  });
  
  const { videoId, multipartUpload } = await initResponse.json();
  const { uploadId, key } = multipartUpload;
  
  // Calculate total parts
  const totalParts = Math.ceil(file.size / CHUNK_SIZE);
  const uploadedParts = [];
  let uploadedBytes = 0;
  
  // Step 2 & 3: Get signed URLs and upload parts in batches
  for (let batchStart = 1; batchStart <= totalParts; batchStart += BATCH_SIZE) {
    const batchEnd = Math.min(batchStart + BATCH_SIZE - 1, totalParts);
    
    // Get signed URLs for this batch
    const urlsResponse = await fetch(
      `https://app.ignitevideo.cloud/api/videos/upload/s3/multipart/${uploadId}/prepare-parts?key=${encodeURIComponent(key)}`,
      {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${token}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ startPart: batchStart, endPart: batchEnd })
      }
    );
    
    const { parts } = await urlsResponse.json();
    
    // Upload parts with concurrency limit
    for (let i = 0; i < parts.length; i += CONCURRENT_UPLOADS) {
      const chunk = parts.slice(i, i + CONCURRENT_UPLOADS);
      
      const uploads = chunk.map(async ({ partNumber, url }) => {
        const start = (partNumber - 1) * CHUNK_SIZE;
        const end = Math.min(start + CHUNK_SIZE, file.size);
        const blob = file.slice(start, end);
        
        const response = await fetch(url, {
          method: 'PUT',
          body: blob
        });
        
        uploadedBytes += blob.size;
        const progress = (uploadedBytes / file.size) * 100;
        console.log(`Upload progress: ${progress.toFixed(1)}%`);
        
        return {
          PartNumber: partNumber,
          ETag: response.headers.get('ETag')
        };
      });
      
      const results = await Promise.all(uploads);
      uploadedParts.push(...results);
    }
  }
  
  // Step 4: Complete multipart upload
  uploadedParts.sort((a, b) => a.PartNumber - b.PartNumber);
  
  await fetch(
    `https://app.ignitevideo.cloud/api/videos/upload/s3/multipart/${uploadId}/complete?key=${encodeURIComponent(key)}`,
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ parts: uploadedParts })
    }
  );
  
  console.log(`Upload complete! Video ID: ${videoId}`);
  return videoId;
}

Error Handling

If an upload fails, you can retry individual parts without restarting the entire upload. The uploadId remains valid for 24 hours.

For failed parts:

  1. Request a new signed URL for the failed part number
  2. Re-upload just that part
  3. Continue with the completion step once all parts are uploaded