Browse documentationUpload Image

POST /api/v1/storage/image/upload

API version: v1.2.4

Upload a starting image for MiniMax H3 Standard or Turbo image-to-video, or a reference image for another supported workflow. Use the returned s3_url in your generation request. Uploading a file and generating a video are separate requests.

Endpoint

Plain text
POST /api/v1/storage/image/upload

Authentication

Required. Include your API key in the request header:

Plain text
X-API-Key: your-api-key-here

Request

Content Type

Plain text
multipart/form-data

Form Fields

FieldTypeRequiredDescription
imagefileYesImage file to upload (JPG, PNG, WebP)

Supported Formats

  • JPEG/JPG: Recommended for photos
  • PNG: Recommended for graphics with transparency
  • WebP: Modern format with good compression

Image Requirements

  • Published default file limit: 20 mebibytes (20,971,520 bytes). Account or service limits can differ; use the upload response when a file is rejected.
  • Content: Use a nonempty JPEG, PNG or WebP image with a matching filename.
  • Generation dimensions: Uploading does not set the video's output size. Choose H3's resolution_tier and aspect_ratio in the generation request; see video generation limits.
  • Preparation: Choose a clear source image with the framing you want to animate. Do any required resizing or color conversion before uploading; this endpoint does not promise automatic RGB conversion or background fills.

See upload limitations for storage constraints and import an image to start from a remote URL.

Response

Success (200 OK)

JSON
{
  "file_id": "img-abc123def456",
  "s3_url": "https://s3.amazonaws.com/bucket/images/abc123def456.jpg",
  "filename": "my_image.jpg",
  "message": "Image uploaded successfully"
}

Response Fields

FieldTypeDescription
file_idstringUnique identifier for the uploaded file
s3_urlstringStorage URL to use in /api/v1/inference/submit request
filenamestringOriginal filename
messagestringSuccess message

Examples

Image Upload (Python)

Python
import requests

MODEL_ID = "minimax/h3/image-to-video"
# For Turbo, use "minimax/h3/image-to-video/turbo".

# Upload image
with open("input_image.jpg", "rb") as f:
    response = requests.post(
        "https://phosor.ai/api/v1/storage/image/upload",
        headers={"X-API-Key": "your-api-key"},
        files={"image": ("input_image.jpg", f, "image/jpeg")},
        timeout=60
    )

response.raise_for_status()
result = response.json()
image_url = result["s3_url"]
print(f"Image uploaded: {image_url}")

# Use the image URL in video generation
video_response = requests.post(
    "https://phosor.ai/api/v1/inference/submit",
    headers={
        "X-API-Key": "your-api-key",
        "Content-Type": "application/json"
    },
    json={
        "model": MODEL_ID,
        "prompt": "The scene comes alive with gentle movement",
        "image_url": image_url,
        "duration": 5,
        "resolution_tier": "480p",
        "aspect_ratio": "16:9"
    },
    timeout=60
)

video_response.raise_for_status()
request_id = video_response.json()["request_id"]
print(f"Video generation started: {request_id}")

Upload with Error Handling

Python
import requests
import os

def upload_image(image_path, api_key):
    """
    Upload an image for I2V generation.

    Args:
        image_path: Path to the image file
        api_key: Your API key

    Returns:
        Storage URL of the uploaded image, or None if failed
    """
    # Validate file exists
    if not os.path.exists(image_path):
        print(f"Error: File {image_path} not found")
        return None

    # Published default: 20 * 1024 * 1024 bytes. Adjust for your account limit.
    file_size = os.path.getsize(image_path)
    if file_size > 20 * 1024 * 1024:
        print(f"Error: File too large ({file_size} bytes). Default limit: 20971520 bytes.")
        return None

    # Check file extension
    valid_extensions = ['.jpg', '.jpeg', '.png', '.webp']
    _, ext = os.path.splitext(image_path)
    if ext.lower() not in valid_extensions:
        print(f"Error: Invalid format {ext}. Supported: {valid_extensions}")
        return None

    # Determine content type
    content_types = {
        '.jpg': 'image/jpeg',
        '.jpeg': 'image/jpeg',
        '.png': 'image/png',
        '.webp': 'image/webp'
    }
    content_type = content_types.get(ext.lower(), 'application/octet-stream')

    # Upload
    try:
        with open(image_path, "rb") as f:
            response = requests.post(
                "https://phosor.ai/api/v1/storage/image/upload",
                headers={"X-API-Key": api_key},
                files={"image": (os.path.basename(image_path), f, content_type)},
                timeout=60
            )

        if response.status_code == 200:
            result = response.json()
            print(f"Upload successful: {result['filename']}")
            return result["s3_url"]
        else:
            print(f"Upload failed: {response.status_code} - {response.text}")
            return None

    except requests.exceptions.RequestException as e:
        print(f"Network error: {e}")
        return None

# Usage
image_url = upload_image("my_photo.jpg", "your-api-key")
if image_url:
    print(f"Use this URL: {image_url}")

Upload Multiple Images

Python
import requests
import os

def upload_images_batch(image_paths, api_key):
    """Upload multiple images and return their URLs"""
    results = []

    for image_path in image_paths:
        print(f"Uploading {image_path}...")

        with open(image_path, "rb") as f:
            response = requests.post(
                "https://phosor.ai/api/v1/storage/image/upload",
                headers={"X-API-Key": api_key},
                files={"image": (os.path.basename(image_path), f)},
                timeout=60
            )

        if response.status_code == 200:
            result = response.json()
            results.append({
                "filename": os.path.basename(image_path),
                "s3_url": result["s3_url"],
                "file_id": result["file_id"]
            })
            print(f"  ✓ Uploaded successfully")
        else:
            print(f"  ✗ Failed: {response.text}")
            results.append({
                "filename": os.path.basename(image_path),
                "error": response.text
            })

    return results

# Usage
image_files = ["image1.jpg", "image2.jpg", "image3.jpg"]
uploaded = upload_images_batch(image_files, "your-api-key")

for item in uploaded:
    if "s3_url" in item:
        print(f"{item['filename']}: {item['s3_url']}")

cURL Example

cURL
curl -X POST https://phosor.ai/api/v1/storage/image/upload \
  -H "X-API-Key: your-api-key" \
  -F "image=@/path/to/image.jpg"

JavaScript/Node.js Example

JavaScript
const fetch = require('node-fetch');
const FormData = require('form-data');
const fs = require('fs');

async function uploadImage(imagePath, apiKey) {
  const formData = new FormData();
  formData.append('image', fs.createReadStream(imagePath));

  const response = await fetch('https://phosor.ai/api/v1/storage/image/upload', {
    method: 'POST',
    headers: {
      'X-API-Key': apiKey
    },
    body: formData
  });

  if (response.ok) {
    const result = await response.json();
    console.log('Image uploaded:', result.s3_url);
    return result.s3_url;
  } else {
    console.error('Upload failed:', await response.text());
    return null;
  }
}

// Usage
uploadImage('./input.jpg', 'your-api-key')
  .then(url => console.log('URL:', url));

Browser/React Example

JavaScript
import React, { useState } from 'react';

function ImageUploader({ apiKey, onUploadComplete }) {
  const [uploading, setUploading] = useState(false);

  const handleFileChange = async (event) => {
    const file = event.target.files[0];
    if (!file) return;

    // Validate file
    // Published default; adjust if your account uses another limit.
    if (file.size > 20 * 1024 * 1024) {
      alert('File too large. Default limit is 20,971,520 bytes.');
      return;
    }

    if (!['image/jpeg', 'image/png', 'image/webp'].includes(file.type)) {
      alert('Invalid file type. Please upload JPG, PNG, or WebP.');
      return;
    }

    setUploading(true);

    try {
      const formData = new FormData();
      formData.append('image', file);

      const response = await fetch('https://phosor.ai/api/v1/storage/image/upload', {
        method: 'POST',
        headers: {
          'X-API-Key': apiKey
        },
        body: formData
      });

      if (response.ok) {
        const result = await response.json();
        onUploadComplete(result.s3_url);
      } else {
        alert('Upload failed: ' + await response.text());
      }
    } catch (error) {
      alert('Upload error: ' + error.message);
    } finally {
      setUploading(false);
    }
  };

  return (
    <div>
      <input
        type="file"
        accept="image/jpeg,image/png,image/webp"
        onChange={handleFileChange}
        disabled={uploading}
      />
      {uploading && <p>Uploading...</p>}
    </div>
  );
}

export default ImageUploader;

Error Responses

Invalid File Format (400 Bad Request)

JSON
{
  "error": "Invalid image file",
  "message": "image field must be a file upload"
}

Missing Image (400 Bad Request)

JSON
{
  "error": "Image file required",
  "message": "image field is required in form data"
}

File Too Large

This upload endpoint returns HTTP 400 when an image is rejected for exceeding the active file-size limit. Read the response's error and message fields; the message can vary. Reduce the file size before trying again.

Unauthorized (401 Unauthorized)

Use a valid API key in X-API-Key. Authentication errors follow the common API contract.

Rate Limited (429 Too Many Requests)

Wait for the Retry-After interval. The response headers describe the active request limit; this page does not define a separate upload-only allowance.

After submitting a video, use its request_id to check request status and retrieve the result. Compare the H3 Standard and Turbo modes or open Standard and Turbo.

Image Processing Tips

Optimize Before Upload

Python
from PIL import Image
import io
import requests

def optimize_image(image_path, max_size=(1024, 1024), quality=85):
    """Resize and compress image before upload"""
    img = Image.open(image_path)

    # Convert RGBA to RGB if needed
    if img.mode == 'RGBA':
        img = img.convert('RGB')

    # Resize if too large
    img.thumbnail(max_size, Image.Resampling.LANCZOS)

    # Save to bytes
    buffer = io.BytesIO()
    img.save(buffer, format='JPEG', quality=quality, optimize=True)
    buffer.seek(0)

    return buffer

# Usage
optimized = optimize_image("large_image.png")

response = requests.post(
    "https://phosor.ai/api/v1/storage/image/upload",
    headers={"X-API-Key": "your-api-key"},
    files={"image": ("optimized.jpg", optimized, "image/jpeg")},
    timeout=60
)
response.raise_for_status()

Check Image Dimensions

Python
from PIL import Image

def validate_image(image_path):
    """Validate image before upload"""
    try:
        img = Image.open(image_path)
        width, height = img.size

        print(f"Image size: {width}x{height}")
        print(f"Format: {img.format}")
        print(f"Mode: {img.mode}")

        # Check if aspect ratio is reasonable
        aspect_ratio = width / height
        if aspect_ratio < 0.5 or aspect_ratio > 2.0:
            print("Warning: Unusual aspect ratio")

        return True
    except Exception as e:
        print(f"Invalid image: {e}")
        return False

# Usage
if validate_image("input.jpg"):
    # Proceed with upload
    pass