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

Copy
POST https://api-gen-na.bach.art/api/vdr/batch-upload/submit-with-files

Content-Type: multipart/form-data

Request Headers

HeaderValue
AuthorizationBearer <your_api_token>
Content-Typemultipart/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/submit with URL or Base64 data, then polling status via /batch-upload/status/{batch_id}.

Request Parameters

ParameterTypeRequiredDescription
filesFile[]YesArray of image files or ZIP archives to upload
image_namesString[]NoCustom names for uploaded files. If provided, length must match the files array
batch_idStringNoCustom batch identifier. Auto-generated if omitted

Validation Rules

RuleSpecification
Minimum files1
Maximum files300 per batch
Maximum file size10 MB per image
Name array matchingimage_names length must equal files length (if provided)
Supported formatsjpg, jpeg, png, gif, bmp, webp, svg, ico, tiff, tif

Request Example (Image Files)

bash
Copy
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)

bash
Copy
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):

json
Copy
{
  "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"
  }
}
FieldTypeDescription
data.batch_idstringUnique batch identifier for status queries
data.total_countintegerTotal number of files submitted
data.messagestringStatus message
data.statusstringInitial batch status (TASK_PENDING)

Error Examples:

ScenarioHTTP StatusCodeMessage
File too large4001000The 2nd image 'large_photo.jpg' size is 15.00MB, exceeds the maximum limit of 10MB.
Name array mismatch4001000The length of imageNames array must match the length of files array.
Empty file list4001000The upload file list cannot be empty.
Unsupported format4001000The 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

Copy
GET https://api-gen-na.bach.art/api/vdr/batch-upload/status/{batch_id}

Request Headers

HeaderValue
AuthorizationBearer <your_api_token>

Path Parameters

ParameterTypeRequiredDescription
batch_idstringYesBatch identifier returned from the upload request

Request Example

bash
Copy
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):

json
Copy
{
  "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 index field in each result corresponds to the zero-based position in the original files array, enabling precise mapping between results and inputs.

Response Fields

FieldTypeDescription
data.batch_idstringBatch identifier
data.total_countintegerTotal number of images in the batch
data.processed_countintegerNumber of images processed so far
data.success_countintegerNumber of successfully uploaded images
data.failed_countintegerNumber of failed uploads
data.statusstringOverall batch status
data.resultsarrayIndividual upload results

Result Object Fields

FieldTypeDescription
indexintegerZero-based position in the original files array
image_namestringImage filename or custom name
image_urlstringCDN URL of the uploaded image. Present only when status is TASK_SUCCEEDED
statusstringIndividual result status: TASK_SUCCEEDED or TASK_FAILED
error_messagestringError description. Present only when status is TASK_FAILED

Batch Status Values

StatusDescription
TASK_PENDINGBatch submitted, awaiting processing
TASK_PROCESSINGUpload in progress
TASK_SUCCEEDEDAll files processed (check individual results for per-file outcomes)

Batch Not Found (HTTP 404):

json
Copy
{
  "code": 1203,
  "message": "Batch ID does not exist or has expired."
}

Code Examples

Python

python
Copy
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

javascript
Copy
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

Copy
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

ScenarioRecommended Approach
Fewer than 50 imagesDirect file upload (submit-with-files)
50–300 imagesAsynchronous URL/Base64 upload (submit)
More than 300 imagesSplit into multiple batches

File Organization

  • Use descriptive names: Assign unique, meaningful names via image_names for 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:

python
Copy
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.
Previous
Elements To Video
Next
On this page
Batch upload Asset | bach.art