Disclaimer: This website requires Please enable JavaScript in your browser settings for the best experience.

Dev GuideAPI ReferenceChangelog
Dev GuideAPI ReferenceUser GuideDev CommunityOptimizely AcademySubmit a ticketLog In
Dev Guide

Multipart upload

How to upload large files (>5MB) using Optimizely Content Management Platform's multipart upload feature.

The multipart upload feature in Optimizely Content Management Platform (CMP) lets you do the following:

  • Upload large files by splitting them into smaller parts.
  • Upload parts in parallel for better performance.
  • Resume interrupted uploads.
  • Handle files up to 5TB in size.

Size Requirements

  • File size

    • Minimum – 5MB + 1B (5,242,881 bytes)
    • Maximum – 5TB (5,497,558,138,880 bytes)
  • Part size

    • Minimum – 5MB (5,242,880 bytes)
    • Maximum – 5GB (5,368,709,120 bytes)
    • Default – 5MB if not specified

Upload Process

Initiate the upload

Request a set of pre-signed URLs for uploading the file parts.

async function initiateMultipartUpload(fileSize) {
    const response = await fetch('https://api.cmp.optimizely.com/v3/multipart-upload-urls', {
        method: 'POST',
        headers: {
            'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            file_size: fileSize,
            part_size: 5 * 1024 * 1024 // Using default 5MB part size
        })
    });

    if (!response.ok) {
        throw new Error('Failed to initiate upload');
    }

    const result = await response.json();
    return {
        uploadId: result.id,
        uploadUrls: result.upload_part_urls,
        partCount: result.upload_part_count,
        expiresAt: result.expires_at
    };
}

Upload the file parts

Use the presigned URLs to upload each part of the file.

async function uploadParts(file, uploadUrls) {
    const partSize = 5 * 1024 * 1024;
    const uploadPromises = uploadUrls.map(async (url, index) => {
        const start = index * partSize;
        const end = Math.min(start + partSize, file.size);
        const chunk = file.slice(start, end);

        const response = await fetch(url, {
            method: 'PUT',
            body: chunk
        });

        if (!response.ok) {
            throw new Error(`Failed to upload part ${index + 1}`);
        }
    });

    await Promise.all(uploadPromises);
}

Complete the upload and monitor the status

After you upload all parts, you can complete the upload process.

async function completeUpload(uploadId) {
    const response = await fetch(`https://api.cmp.optimizely.com/v3/multipart-upload-urls/${uploadId}/complete`, {
        method: 'POST',
        headers: {
            'Authorization': 'Bearer YOUR_ACCESS_TOKEN'
        }
    });

    if (!response.ok) {
        throw new Error('Failed to complete upload');
    }

    const result = await response.json();
    return result.key; // Save this key for use in other APIs
}

async function checkUploadStatus(uploadId) {
    const response = await fetch(`https://api.cmp.optimizely.com/v3/multipart-upload-urls/${uploadId}/status`, {
        headers: {
            'Authorization': 'Bearer YOUR_ACCESS_TOKEN'
        }
    });

    if (!response.ok) {
        throw new Error('Failed to check upload status');
    }
    return await response.json();
}

async function waitForCompletion(uploadId) {
    while (true) {
        const status = await checkUploadStatus(uploadId);
        switch (status.status) {
            case 'UPLOAD_COMPLETION_SUCCEEDED':
                return status.key;
            case 'UPLOAD_COMPLETION_FAILED':
                throw new Error(`Upload failed: ${status.status_message}`);
            case 'UPLOAD_COMPLETION_IN_PROGRESS':
                await new Promise(resolve => setTimeout(resolve, 2000));
                continue;
            case 'UPLOAD_COMPLETION_NOT_STARTED':
                throw new Error('Upload completion not initiated');
            default:
                throw new Error(`Unexpected status: ${status.status}`);
        }
    }
}

Example

Here is an example showing how to use the multipart upload:

class MultipartUploader {
    constructor(apiToken) {
        this.apiToken = apiToken;
    }

    async uploadLargeFile(file) {
        if (file.size <= 5 * 1024 * 1024) {
            throw new Error('File too small for multipart upload. Use regular upload endpoint.');
        }

        try {
            // 1. Get upload URLs
            console.log('Initiating upload...');
            const { uploadId, uploadUrls } = await this.initiateMultipartUpload(file.size);

            // 2. Upload all parts
            console.log('Uploading file parts...');
            await this.uploadParts(file, uploadUrls);

            // 3. Complete the upload
            console.log('Completing upload...');
            await this.completeUpload(uploadId);

            // 4. Wait for processing
            console.log('Waiting for processing...');
            const fileKey = await this.waitForCompletion(uploadId);
            console.log('Upload successful!');
            return fileKey;
        } catch (error) {
            console.error('Upload failed:', error);
            throw error;
        }
    }

    async initiateMultipartUpload(fileSize) {
        const response = await fetch('https://api.cmp.optimizely.com/v3/multipart-upload-urls', {
            method: 'POST',
            headers: {
                'Authorization': `Bearer ${this.apiToken}`,
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                file_size: fileSize,
                part_size: 5 * 1024 * 1024 // Using default 5MB part size
            })
        });

        if (!response.ok) {
            throw new Error('Failed to initiate upload');
        }

        const result = await response.json();
        return {
            uploadId: result.id,
            uploadUrls: result.upload_part_urls,
            partCount: result.upload_part_count,
            expiresAt: result.expires_at
        };
    }

    async uploadParts(file, uploadUrls) {
        const partSize = 5 * 1024 * 1024;
        const uploadPromises = uploadUrls.map(async (url, index) => {
            const start = index * partSize;
            const end = Math.min(start + partSize, file.size);
            const chunk = file.slice(start, end);

            const response = await fetch(url, {
                method: 'PUT',
                body: chunk
            });

            if (!response.ok) {
                throw new Error(`Failed to upload part ${index + 1}`);
            }
        });

        await Promise.all(uploadPromises);
    }

    async completeUpload(uploadId) {
        const response = await fetch(`https://api.cmp.optimizely.com/v3/multipart-upload-urls/${uploadId}/complete`, {
            method: 'POST',
            headers: {
                'Authorization': `Bearer ${this.apiToken}`
            }
        });

        if (!response.ok) {
            throw new Error('Failed to complete upload');
        }

        const result = await response.json();
        return result.key;
    }

    async checkUploadStatus(uploadId) {
        const response = await fetch(`https://api.cmp.optimizely.com/v3/multipart-upload-urls/${uploadId}/status`, {
            headers: {
                'Authorization': `Bearer ${this.apiToken}`
            }
        });

        if (!response.ok) {
            throw new Error('Failed to check upload status');
        }
        return await response.json();
    }

    async waitForCompletion(uploadId) {
        while (true) {
            const status = await this.checkUploadStatus(uploadId);
            switch (status.status) {
                case 'UPLOAD_COMPLETION_SUCCEEDED':
                    return status.key;
                case 'UPLOAD_COMPLETION_FAILED':
                    throw new Error(`Upload failed: ${status.status_message}`);
                case 'UPLOAD_COMPLETION_IN_PROGRESS':
                    await new Promise(resolve => setTimeout(resolve, 2000));
                    continue;
                case 'UPLOAD_COMPLETION_NOT_STARTED':
                    throw new Error('Upload completion not initiated');
                default:
                    throw new Error(`Unexpected status: ${status.status}`);
            }
        }
    }
}

async function uploadFile(filePath, apiToken) {
    const uploader = new MultipartUploader(apiToken);
    const file = fs.readFileSync(filePath);
    try {
        const fileKey = await uploader.uploadLargeFile(file);
        console.log('File uploaded successfully. Key:', fileKey);
        return fileKey; // Save this key for use in APIs like POST /v3/assets, POST /v3/campaigns/<id>/attachments etc.
    } catch (error) {
        console.error('Upload failed:', error);
        throw error;
    }
}

uploadFile('path/to/your/large/file.txt', 'YOUR_API_TOKEN')
    .then(console.log)
    .catch(console.error);

Status values

The upload status endpoint can return these values:

  • UPLOAD_COMPLETION_NOT_STARTED – Completion process has not been initiated.
  • UPLOAD_COMPLETION_IN_PROGRESS – Upload is being processed.
  • UPLOAD_COMPLETION_SUCCEEDED – Upload completed successfully.
  • UPLOAD_COMPLETION_FAILED – Upload failed. Check status_message in the response for details.

Error handling

Common errors to handle:

  • Invalid Size (400) – File size outside allowed range (5MB+1B to 5TB) or part size outside allowed range (5MB to 5GB).
  • Authentication (401) – Invalid or expired access token.
  • Authorization (403) – Insufficient permissions or expired upload URLs.
  • Not Found (404) – Invalid upload ID.

Best practices

  1. Before upload –

    • Validate the file size meets requirements.
    • Choose the appropriate part size based on the file size.
    • Verify you have a valid authentication token.
  2. During upload –

    • Upload the parts in the correct order.
    • Monitor upload progress
    • Handle network errors and retries
    • Check URL expiration before using
  3. After upload –

    • Save the file key for use in other APIs.
    • Verify the upload status before proceeding.
    • Clean up any temporary resources.

📘

Note

The file key returned after successful upload is required for using the file in other API operations like creating assets or adding attachments.