Batch upload Asset
Batch Upload
Upload multiple images in bulk for use with video generation endpoints. Uploaded images receive CDN-hosted URLs that can be referenced in Text-to-Video, Image-to-Video, and Multi-Image-to-Video requests.
Overview
The Batch Upload API provides efficient methods for uploading up to 300 images per batch. It supports direct file uploads (including ZIP archives) via a synchronous endpoint, as well as asynchronous URL/Base64 uploads for large datasets. All uploaded assets are automatically distributed via CDN.
Create a Batch Upload (File Upload)
Upload multiple image files directly to the platform.
Request URL
POST https://api-gen-na.bach.art/api/vdr/batch-upload/submit-with-files
Content-Type: multipart/form-data
Request Headers
| Header | Value |
|---|---|
Authorization | Bearer <your_api_token> |
Content-Type | multipart/form-data |
Important: Synchronous upload connections have a maximum timeout of 3,600 seconds (1 hour). For large batches (more than 50 images or 10 ZIP files), we recommend the asynchronous upload method via
/batch-upload/submitwith URL or Base64 data, then polling status via/batch-upload/status/{batch_id}.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
files | File[] | Yes | Array of image files or ZIP archives to upload |
image_names | String[] | No | Custom names for uploaded files. If provided, length must match the files array |
batch_id | String | No | Custom batch identifier. Auto-generated if omitted |
Validation Rules
| Rule | Specification |
|---|---|
| Minimum files | 1 |
| Maximum files | 300 per batch |
| Maximum file size | 10 MB per image |
| Name array matching | image_names length must equal files length (if provided) |
| Supported formats | jpg, jpeg, png, gif, bmp, webp, svg, ico, tiff, tif |
Request Example (Image Files)
curl -X POST "https://api-gen-na.bach.art/api/vdr/batch-upload/submit-with-files" \
-H "Authorization: Bearer <your_api_token>" \
-F "files=@/path/to/image1.jpg" \
-F "files=@/path/to/image2.jpg" \
-F "image_names=product-1.jpg" \
-F "image_names=product-2.jpg" \
-F "batch_id=my-batch-002"
Request Example (ZIP Files)
curl -X POST "https://api-gen-na.bach.art/api/vdr/batch-upload/submit-with-files" \
-H "Authorization: Bearer <your_api_token>" \
-F "files=@/path/to/images-set-1.zip" \
-F "files=@/path/to/images-set-2.zip" \
-F "image_names=product-set-1" \
-F "image_names=product-set-2" \
-F "batch_id=my-batch-zip-003"
Response
Success (HTTP 200):
{
"code": 200,
"message": "success",
"data": {
"batch_id": "x1y2z3a4b5c6",
"total_count": 2,
"message": "Batch file upload task submitted, please use batch id to query upload progress.",
"status": "TASK_PENDING"
}
}
| Field | Type | Description |
|---|---|---|
data.batch_id | string | Unique batch identifier for status queries |
data.total_count | integer | Total number of files submitted |
data.message | string | Status message |
data.status | string | Initial batch status (TASK_PENDING) |
Error Examples:
| Scenario | HTTP Status | Code | Message |
|---|---|---|---|
| File too large | 400 | 1000 | The 2nd image 'large_photo.jpg' size is 15.00MB, exceeds the maximum limit of 10MB. |
| Name array mismatch | 400 | 1000 | The length of imageNames array must match the length of files array. |
| Empty file list | 400 | 1000 | The upload file list cannot be empty. |
| Unsupported format | 400 | 1000 | The 1st file 'document.pdf' extension 'pdf' is not supported. |
Query Batch Upload Status
Retrieve the progress and results of a batch upload operation.
Request URL
GET https://api-gen-na.bach.art/api/vdr/batch-upload/status/{batch_id}
Request Headers
| Header | Value |
|---|---|
Authorization | Bearer <your_api_token> |
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
batch_id | string | Yes | Batch identifier returned from the upload request |
Request Example
curl -X GET "https://api-gen-na.bach.art/api/vdr/batch-upload/status/a1b2c3d4e5f6" \
-H "Authorization: Bearer <your_api_token>"
Response
Success (HTTP 200):
{
"code": 200,
"message": "success",
"data": {
"batch_id": "a1b2c3d4e5f6",
"total_count": 3,
"processed_count": 3,
"success_count": 2,
"failed_count": 1,
"status": "TASK_SUCCEEDED",
"results": [
{
"index": 0,
"image_name": "product-photo-1.jpg",
"image_url": "https://cdn.example.com/images/2025/01/06/abc123.jpg",
"status": "TASK_SUCCEEDED"
},
{
"index": 1,
"image_name": "product-photo-2.jpg",
"status": "TASK_FAILED",
"error_message": "Image size from URL is 15.00MB, exceeds the maximum limit of 10MB."
},
{
"index": 2,
"image_name": "product-photo-3.jpg",
"image_url": "https://cdn.example.com/images/2025/01/06/xyz789.jpg",
"status": "TASK_SUCCEEDED"
}
]
}
}
Note: The
indexfield in each result corresponds to the zero-based position in the originalfilesarray, enabling precise mapping between results and inputs.
Response Fields
| Field | Type | Description |
|---|---|---|
data.batch_id | string | Batch identifier |
data.total_count | integer | Total number of images in the batch |
data.processed_count | integer | Number of images processed so far |
data.success_count | integer | Number of successfully uploaded images |
data.failed_count | integer | Number of failed uploads |
data.status | string | Overall batch status |
data.results | array | Individual upload results |
Result Object Fields
| Field | Type | Description |
|---|---|---|
index | integer | Zero-based position in the original files array |
image_name | string | Image filename or custom name |
image_url | string | CDN URL of the uploaded image. Present only when status is TASK_SUCCEEDED |
status | string | Individual result status: TASK_SUCCEEDED or TASK_FAILED |
error_message | string | Error description. Present only when status is TASK_FAILED |
Batch Status Values
| Status | Description |
|---|---|
TASK_PENDING | Batch submitted, awaiting processing |
TASK_PROCESSING | Upload in progress |
TASK_SUCCEEDED | All files processed (check individual results for per-file outcomes) |
Batch Not Found (HTTP 404):
{
"code": 1203,
"message": "Batch ID does not exist or has expired."
}
Code Examples
Python
import requests
import time
import os
API_BASE = "https://api-gen-na.bach.art/api/vdr"
API_TOKEN = "<your_api_token>"
def batch_upload_files(file_paths, custom_names=None, batch_id=None):
"""
Upload multiple image files and return their CDN URLs.
Args:
file_paths: List of local file paths to upload.
custom_names: Optional list of custom names for the files.
batch_id: Optional custom batch identifier.
Returns:
Dictionary mapping original filenames to CDN URLs.
"""
headers = {"Authorization": f"Bearer {API_TOKEN}"}
# Prepare multipart form data
files = [
("files", (os.path.basename(path), open(path, "rb")))
for path in file_paths
]
data = {}
if custom_names:
data["image_names"] = custom_names
if batch_id:
data["batch_id"] = batch_id
# Submit batch upload
response = requests.post(
f"{API_BASE}/batch-upload/submit-with-files",
headers=headers,
files=files,
data=data
)
# Close file handles
for _, (_, file_handle) in files:
file_handle.close()
result = response.json()
batch_id = result["data"]["batch_id"]
print(f"Batch submitted: {batch_id}")
# Poll for completion
while True:
status_response = requests.get(
f"{API_BASE}/batch-upload/status/{batch_id}",
headers=headers
)
status_data = status_response.json()["data"]
status = status_data["status"]
processed = status_data["processed_count"]
total = status_data["total_count"]
print(f"Progress: {processed}/{total} — Status: {status}")
if status == "TASK_SUCCEEDED":
break
time.sleep(2)
# Build result mapping
url_mapping = {}
for item in status_data["results"]:
if item["status"] == "TASK_SUCCEEDED":
url_mapping[item["image_name"]] = item["image_url"]
else:
print(f"Failed: {item['image_name']} — {item.get('error_message', 'Unknown error')}")
return url_mapping
# Usage
if __name__ == "__main__":
files_to_upload = [
"/path/to/product-1.jpg",
"/path/to/product-2.jpg",
"/path/to/product-3.jpg"
]
urls = batch_upload_files(
files_to_upload,
custom_names=["hero-image.jpg", "feature-1.jpg", "feature-2.jpg"],
batch_id="product-launch-2025"
)
print("\nUploaded URLs:")
for name, url in urls.items():
print(f" {name}: {url}")
Node.js
const axios = require('axios');
const FormData = require('form-data');
const fs = require('fs');
const path = require('path');
const API_BASE = 'https://api-gen-na.bach.art/api/vdr';
const API_TOKEN = '<your_api_token>';
async function batchUploadFiles(filePaths, customNames = null, batchId = null) {
const headers = { 'Authorization': `Bearer ${API_TOKEN}` };
// Prepare form data
const formData = new FormData();
filePaths.forEach((filePath, index) => {
formData.append('files', fs.createReadStream(filePath));
if (customNames && customNames[index]) {
formData.append('image_names', customNames[index]);
}
});
if (batchId) {
formData.append('batch_id', batchId);
}
// Submit batch upload
const submitResponse = await axios.post(
`${API_BASE}/batch-upload/submit-with-files`,
formData,
{ headers: { ...headers, ...formData.getHeaders() } }
);
const submittedBatchId = submitResponse.data.data.batch_id;
console.log(`Batch submitted: ${submittedBatchId}`);
// Poll for completion
while (true) {
const statusResponse = await axios.get(
`${API_BASE}/batch-upload/status/${submittedBatchId}`,
{ headers }
);
const statusData = statusResponse.data.data;
const { status, processed_count, total_count } = statusData;
console.log(`Progress: ${processed_count}/${total_count} — Status: ${status}`);
if (status === 'TASK_SUCCEEDED') {
const urlMapping = {};
statusData.results.forEach(result => {
if (result.status === 'TASK_SUCCEEDED') {
urlMapping[result.image_name] = result.image_url;
} else {
console.log(`Failed: ${result.image_name} — ${result.error_message || 'Unknown error'}`);
}
});
return urlMapping;
}
await new Promise(resolve => setTimeout(resolve, 2000));
}
}
// Usage
(async () => {
const filesToUpload = [
'/path/to/product-1.jpg',
'/path/to/product-2.jpg',
'/path/to/product-3.jpg'
];
const urls = await batchUploadFiles(
filesToUpload,
['hero-image.jpg', 'feature-1.jpg', 'feature-2.jpg'],
'product-launch-2025'
);
console.log('\nUploaded URLs:');
Object.entries(urls).forEach(([name, url]) => {
console.log(` ${name}: ${url}`);
});
})();
Integration Workflow
Local Files
┌──────┐ ┌──────┐ ┌──────┐
│ img1 │ │ img2 │ │ img3 │
└──┬───┘ └──┬───┘ └──┬───┘
└────────┼────────┘
▼
POST /batch-upload/submit-with-files
▼
batch_id: "abc123"
status: TASK_PENDING
▼
GET /batch-upload/status/{batch_id} (poll)
▼
results:
- image_url: "https://cdn.../img1.jpg" ✓
- image_url: "https://cdn.../img2.jpg" ✓
- image_url: "https://cdn.../img3.jpg" ✓
▼
Use CDN URLs with Video Generation APIs
POST /videos/image2video
POST /videos/multi2video
Best Practices
Batch Size Optimization
| Scenario | Recommended Approach |
|---|---|
| Fewer than 50 images | Direct file upload (submit-with-files) |
| 50–300 images | Asynchronous URL/Base64 upload (submit) |
| More than 300 images | Split into multiple batches |
File Organization
- Use descriptive names: Assign unique, meaningful names via
image_namesfor easy tracking. - Pre-validate files: Check file sizes and formats before uploading to avoid partial batch failures.
- Group related images: Bundle related images (e.g., subject angles) into ZIP files for efficient uploads.
Error Recovery
Implement retry logic for individual failed uploads within a batch:
def robust_batch_upload(file_paths, max_retries=3):
"""Upload with automatic retry for failed items."""
urls = batch_upload_files(file_paths)
failed_files = [
path for path in file_paths
if os.path.basename(path) not in urls
]
retry_count = 0
while failed_files and retry_count < max_retries:
retry_count += 1
print(f"Retrying {len(failed_files)} failed uploads (attempt {retry_count})")
retry_urls = batch_upload_files(failed_files)
urls.update(retry_urls)
failed_files = [
path for path in failed_files
if os.path.basename(path) not in retry_urls
]
return urls
CDN URL Retention
- Validity period: Uploaded image URLs remain valid for 30 days.
- Re-upload strategy: Implement URL caching and re-upload logic for long-running projects.
- Backup originals: Always maintain local copies of original assets.