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. Checkstatus_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
-
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.
-
During upload –
- Upload the parts in the correct order.
- Monitor upload progress
- Handle network errors and retries
- Check URL expiration before using
-
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.
Updated about 20 hours ago