Skip to main content
Async Endpoint: https://api.laozhang.ai/v1/videosCall Method: Three steps (Create Task → Query Status → Get Video)Advantages: More Stable | Task Queue | Long-running Tasks Support
Important Difference - Image-to-Video Upload MethodAsync API differs significantly from Sync API for image-to-video:
FeatureSync APIAsync API
Image UploadSupports URL or Base64Local file only
Request FormatJSONmultipart/form-data
If you have an image URL: Download it locally first, then upload via Async API.Example Comparison:
{/* Sync API - Supports URL */}
"image_url": {"url": "https://example.com/image.png"}

{/* Async API - Local file only */}
files = {'input_reference': open('/path/to/image.png', 'rb')}

Why Choose Async API?

Higher Stability

Task queue based, avoiding long connection timeout issues

No Charge on Failure ⭐

Key Advantage: No charges for failures of any kind
  • ✓ Content violation → No charge
  • ✓ Queue timeout → No charge
  • ✓ Generation failure → No charge
Sync API charges as long as request succeeds (HTTP 200), even if generation ultimately fails!

Flexible Polling

Query task status and progress anytime

Parameterized Control

Specify resolution and duration via parameters, more flexible

Sync vs Async Comparison

FeatureSync APIAsync API (Recommended)
Call MethodSingle request, wait for completionCreate task then poll for results
Endpoint/v1/chat/completions/v1/videos
ResolutionModel name (sora_video2-landscape)Parameter (size: "1280x720")
DurationModel name (sora_video2-15s)Parameter (seconds: "15")
Image-to-VideoSupports image URLLocal file upload only
Failed BillingCharged even if generation fails⭐ No charge on failure
StabilityDepends on long connection⭐⭐⭐⭐⭐ More Stable
ProgressStream outputPoll for progress percentage
TimeoutNeeds long timeout settingTask runs independently, no timeout limit
Use CasesQuick testing, real-time feedbackProduction environment, batch generation
Recommended to use Async API, especially in production environments or when batch generating videos, for better stability.

Quick Start

Async calling consists of three steps:
1

Create Video Task

POST request to create task, get task ID
2

Query Task Status

Periodically poll to check generation progress
3

Download Video

Retrieve video file after task completes

Complete Example

curl -X POST "https://api.laozhang.ai/v1/videos" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "sora-2",
    "prompt": "A cute cat playing with a ball in a sunny garden",
    "size": "1280x720",
    "seconds": "15"
  }'

# Response example
{
  "id": "video_abc123",
  "object": "task",
  "model": "sora-2",
  "status": "submitted",
  "created_at": 1760945092,
  "expires_at": 1761031492
}

API Endpoints

1. Create Video Task

POST https://api.laozhang.ai/v1/videosCreate a new video generation task

Request Parameters

ParameterTypeRequiredOptionsDescription
modelstring"sora-2"Model name (fixed value)
promptstring-Video generation prompt
sizestring"1280x720", "720x1280"Video resolution (landscape/portrait)
secondsstring"10", "15"Video duration (seconds)
input_referencefileRequired for image-to-video-Local image file (local upload only)
Supported formats: JPG, PNG, WebP
Recommended size: 1280x720
Recommended file size: < 5MB

Response Fields

FieldTypeDescription
idstringUnique task identifier for subsequent queries
objectstringFixed as "task"
modelstringModel used
statusstringTask status: "submitted"
created_atintegerCreation timestamp
expires_atintegerExpiration timestamp (24 hours later)

2. Query Task Status

GET https://api.laozhang.ai/v1/videos/{video_id}Query current status and progress of video generation task

Path Parameters

ParameterTypeRequiredDescription
video_idstringTask ID returned when creating task

Response Fields

FieldTypeDescription
idstringTask ID
objectstringFixed as "video"
modelstringModel used
statusstringTask status (see below)
progressintegerProgress percentage (0-100)
urlstringVideo download path (completed status only)
created_atintegerCreation timestamp
completed_atintegerCompletion timestamp (completed status only)
errorobjectError information (failed status only)

Task Status

StatusDescriptionProgress
submittedSubmitted, waiting for processing0%
in_progressGenerating1-99%
completedGeneration completed100%
failedGeneration failed-

3. Get Video Content

GET https://api.laozhang.ai/v1/videos/{video_id}/contentDownload completed video file

Path Parameters

ParameterTypeRequiredDescription
video_idstringTask ID

Response

Returns binary stream of video file (MP4 format)
Important NoticeVideo files are stored for 24 hours only. Please download and save to local storage promptly!

Complete Code Examples

Python Example (with Polling Logic)

import requests
import time

API_KEY = "YOUR_API_KEY"
BASE_URL = "https://api.laozhang.ai/v1"

# Step 1: Create video task (text-to-video)
def create_video_task(prompt, size="1280x720", seconds="15"):
    """Create video generation task"""
    url = f"{BASE_URL}/videos"
    headers = {
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json"
    }
    data = {
        "model": "sora-2",
        "prompt": prompt,
        "size": size,
        "seconds": seconds
    }

    response = requests.post(url, headers=headers, json=data)
    response.raise_for_status()
    return response.json()

# Step 1: Create video task (image-to-video)
def create_video_task_with_image(prompt, image_path, size="1280x720", seconds="10"):
    """Create image-to-video task"""
    url = f"{BASE_URL}/videos"
    headers = {"Authorization": f"Bearer {API_KEY}"}

    # Ensure file remains open during request
    with open(image_path, 'rb') as image_file:
        files = {
            # Specify filename and MIME type for proper upload
            'input_reference': ('image.png', image_file, 'image/png')
        }
        data = {
            'model': 'sora-2',
            'prompt': prompt,
            'size': size,
            'seconds': seconds
        }

        # Send request within with block to keep file handle valid
        response = requests.post(url, headers=headers, files=files, data=data)
        response.raise_for_status()
        return response.json()

# Step 2: Poll for status
def wait_for_video(video_id, poll_interval=5, timeout=600):
    """Wait for video generation to complete"""
    url = f"{BASE_URL}/videos/{video_id}"
    headers = {"Authorization": f"Bearer {API_KEY}"}
    start_time = time.time()

    while True:
        # Check timeout
        if time.time() - start_time > timeout:
            raise TimeoutError(f"Video generation timeout ({timeout}s)")

        # Query status
        response = requests.get(url, headers=headers)
        response.raise_for_status()
        task = response.json()

        status = task["status"]
        progress = task.get("progress", 0)

        print(f"Status: {status}, Progress: {progress}%")

        if status == "completed":
            return task
        elif status == "failed":
            error = task.get("error", {})
            raise Exception(f"Generation failed: {error.get('message', 'Unknown error')}")

        # Wait before retry
        time.sleep(poll_interval)

# Step 3: Download video
def download_video(video_id, save_path="video.mp4"):
    """Download generated video"""
    url = f"{BASE_URL}/videos/{video_id}/content"
    headers = {"Authorization": f"Bearer {API_KEY}"}

    response = requests.get(url, headers=headers, stream=True)
    response.raise_for_status()

    with open(save_path, 'wb') as f:
        for chunk in response.iter_content(chunk_size=8192):
            if chunk:
                f.write(chunk)

    print(f"Video saved to: {save_path}")

# Complete workflow (text-to-video)
def generate_video_async(prompt, size="1280x720", seconds="15"):
    """Complete async video generation workflow"""
    print("1. Creating video task...")
    task = create_video_task(prompt, size, seconds)
    video_id = task["id"]
    print(f"   Task ID: {video_id}")

    print("\n2. Waiting for video generation...")
    completed_task = wait_for_video(video_id)
    duration = completed_task['completed_at'] - completed_task['created_at']
    print(f"   Completed! Duration: {duration}s")

    print("\n3. Downloading video...")
    download_video(video_id)
    print("\n✅ Done!")

# Complete workflow (image-to-video)
def generate_video_from_image_async(image_path, prompt, size="1280x720", seconds="10"):
    """Complete workflow for generating video from local image"""
    import os

    if not os.path.exists(image_path):
        print(f"Error: Image file not found {image_path}")
        return None

    print(f"1. Uploading image and creating task... ({image_path})")
    task = create_video_task_with_image(prompt, image_path, size, seconds)
    video_id = task["id"]
    print(f"   Task ID: {video_id}")

    print("\n2. Waiting for video generation...")
    completed_task = wait_for_video(video_id)
    duration = completed_task['completed_at'] - completed_task['created_at']
    print(f"   Completed! Duration: {duration}s")

    print("\n3. Downloading video...")
    download_video(video_id, save_path=f"image_to_video_{video_id}.mp4")
    print("\n✅ Done!")

    return video_id

# Usage examples
if __name__ == "__main__":
    # Text-to-video
    generate_video_async(
        prompt="A cute cat playing with a ball in a sunny garden",
        size="1280x720",  # Landscape
        seconds="15"       # 15 seconds
    )

    # Image-to-video
    generate_video_from_image_async(
        image_path="/path/to/your/image.png",
        prompt="Animate this scene with natural dynamic effects",
        size="1280x720",  # Landscape
        seconds="10"       # 10 seconds
    )

JavaScript/Node.js Example

const axios = require('axios');
const fs = require('fs');
const FormData = require('form-data');

const API_KEY = 'YOUR_API_KEY';
const BASE_URL = 'https://api.laozhang.ai/v1';

// Create video task (text-to-video)
async function createVideoTask(prompt, size = '1280x720', seconds = '15') {
  const response = await axios.post(`${BASE_URL}/videos`, {
    model: 'sora-2',
    prompt,
    size,
    seconds
  }, {
    headers: { 'Authorization': `Bearer ${API_KEY}` }
  });

  return response.data;
}

// Create video task (image-to-video)
async function createVideoTaskWithImage(prompt, imagePath, size = '1280x720', seconds = '10') {
  const formData = new FormData();
  formData.append('model', 'sora-2');
  formData.append('prompt', prompt);
  formData.append('size', size);
  formData.append('seconds', seconds);
  formData.append('input_reference', fs.createReadStream(imagePath));

  const response = await axios.post(`${BASE_URL}/videos`, formData, {
    headers: {
      'Authorization': `Bearer ${API_KEY}`,
      ...formData.getHeaders()
    }
  });

  return response.data;
}

// Poll for status
async function waitForVideo(videoId, pollInterval = 5000, timeout = 600000) {
  const startTime = Date.now();

  while (true) {
    // Check timeout
    if (Date.now() - startTime > timeout) {
      throw new Error(`Video generation timeout (${timeout/1000}s)`);
    }

    // Query status
    const response = await axios.get(`${BASE_URL}/videos/${videoId}`, {
      headers: { 'Authorization': `Bearer ${API_KEY}` }
    });

    const task = response.data;
    const { status, progress = 0 } = task;

    console.log(`Status: ${status}, Progress: ${progress}%`);

    if (status === 'completed') {
      return task;
    } else if (status === 'failed') {
      const error = task.error || {};
      throw new Error(`Generation failed: ${error.message || 'Unknown error'}`);
    }

    // Wait before retry
    await new Promise(resolve => setTimeout(resolve, pollInterval));
  }
}

// Download video
async function downloadVideo(videoId, savePath = 'video.mp4') {
  const response = await axios.get(
    `${BASE_URL}/videos/${videoId}/content`,
    {
      headers: { 'Authorization': `Bearer ${API_KEY}` },
      responseType: 'stream'
    }
  );

  const writer = fs.createWriteStream(savePath);
  response.data.pipe(writer);

  return new Promise((resolve, reject) => {
    writer.on('finish', () => {
      console.log(`Video saved to: ${savePath}`);
      resolve();
    });
    writer.on('error', reject);
  });
}

// Complete workflow (text-to-video)
async function generateVideoAsync(prompt, size = '1280x720', seconds = '15') {
  try {
    console.log('1. Creating video task...');
    const task = await createVideoTask(prompt, size, seconds);
    const videoId = task.id;
    console.log(`   Task ID: ${videoId}`);

    console.log('\n2. Waiting for video generation...');
    const completedTask = await waitForVideo(videoId);
    const duration = completedTask.completed_at - completedTask.created_at;
    console.log(`   Completed! Duration: ${duration}s`);

    console.log('\n3. Downloading video...');
    await downloadVideo(videoId);
    console.log('\n✅ Done!');

  } catch (error) {
    console.error('Error:', error.message);
  }
}

// Complete workflow (image-to-video)
async function generateVideoFromImageAsync(imagePath, prompt, size = '1280x720', seconds = '10') {
  try {
    if (!fs.existsSync(imagePath)) {
      console.error(`Error: Image file not found ${imagePath}`);
      return null;
    }

    console.log(`1. Uploading image and creating task... (${imagePath})`);
    const task = await createVideoTaskWithImage(prompt, imagePath, size, seconds);
    const videoId = task.id;
    console.log(`   Task ID: ${videoId}`);

    console.log('\n2. Waiting for video generation...');
    const completedTask = await waitForVideo(videoId);
    const duration = completedTask.completed_at - completedTask.created_at;
    console.log(`   Completed! Duration: ${duration}s`);

    console.log('\n3. Downloading video...');
    await downloadVideo(videoId, `image_to_video_${videoId}.mp4`);
    console.log('\n✅ Done!');

    return videoId;

  } catch (error) {
    console.error('Error:', error.message);
    return null;
  }
}

// Usage examples
// Text-to-video
generateVideoAsync(
  'A cute cat playing with a ball in a sunny garden',
  '1280x720',  // Landscape
  '15'         // 15 seconds
);

// Image-to-video
generateVideoFromImageAsync(
  '/path/to/your/image.png',
  'Animate this scene with natural dynamic effects',
  '1280x720',  // Landscape
  '10'         // 10 seconds
);

Best Practices

Recommended polling interval: 3-5 seconds
# Recommended
poll_interval = 5  # Query every 5 seconds

# Not recommended
poll_interval = 1  # Too frequent, wastes requests
poll_interval = 30 # Too slow, poor user experience
Reasoning:
  • Video generation typically takes 2-5 minutes
  • 3-5 seconds provides timely progress feedback
  • Avoids excessive requests
Recommended timeout: 10 minutes (600 seconds)
def wait_for_video(video_id, timeout=600):
    start_time = time.time()

    while True:
        if time.time() - start_time > timeout:
            # Timeout handling
            print(f"Task {video_id} timeout, can continue querying later")
            break

        # Query logic...
Note:
  • Task timeout doesn’t auto-cancel
  • Can continue querying same video_id later
  • Task validity period is 24 hours
Suggested retry logic:
def create_video_with_retry(prompt, max_retries=3):
    for i in range(max_retries):
        try:
            return create_video_task(prompt)
        except Exception as e:
            if i < max_retries - 1:
                print(f"Failed, retrying in 5s... ({i+1}/{max_retries})")
                time.sleep(5)
            else:
                raise
Retry scenarios:
  • ✓ Network errors → Retry
  • ✓ Service busy (503) → Retry
  • ✗ Content violation → Don’t retry, modify prompt
  • ✗ Insufficient balance → Don’t retry, recharge first
Concurrency control recommendations:
import concurrent.futures

def batch_generate_videos(prompts, max_workers=5):
    """Batch generate videos with concurrency control"""
    with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
        # Create all tasks
        future_to_prompt = {
            executor.submit(create_video_task, prompt): prompt
            for prompt in prompts
        }

        video_ids = []
        for future in concurrent.futures.as_completed(future_to_prompt):
            task = future.result()
            video_ids.append(task['id'])

        # Concurrently wait for all tasks
        futures = [executor.submit(wait_for_video, vid) for vid in video_ids]
        results = [f.result() for f in concurrent.futures.as_completed(futures)]

        return results
Recommendations:
  • Creating tasks: High concurrency OK (10-30)
  • Querying status: Recommended concurrency ≤ 10
  • Downloading videos: Recommended concurrency ≤ 5

Pricing

Async API has exactly the same pricing as Sync API, billed per call.
Parameter CombinationPrice$100 Top-up Rate
10s video (landscape/portrait)$0.15/call≈ ¥1.0/call
15s video (landscape/portrait)$0.15/call≈ ¥1.0/call
Billing Rules:
  • ✓ Charged only when video successfully generated (status = “completed”)
  • ✗ Failed, timeout, cancelled not charged
  • ✗ Content safety issues also not charged (important difference from Sync API⭐)
  • ✗ Status queries not charged
Key Advantage of Async API: No charges for failures of any kind, including content safety review failures. Sync API charges as long as the request succeeds, even if generation ultimately fails.

FAQ

Task validity: 24 hours
  • After creating task, expires_at field shows expiration time
  • Can query task status anytime within 24 hours
  • After video generation completes, file is stored for 24 hours
  • After 24 hours, task and video will be automatically cleaned
Recommendations:
  • Download video immediately after completion
  • Don’t rely on server for long-term storage
Manual task cancellation not currently supported
  • Once created, task will auto-queue for execution
  • If no longer needed, simply ignore it
  • Incomplete tasks won’t be charged
Alternatives:
  • Wait for task to naturally complete or fail
  • Task auto-expires after 24 hours
Possible reasons:
  1. Incorrect video_id - Check if fully copied
  2. Task expired - Over 24 hours
  3. Network issues - Retry request
Solution:
try:
    response = client.get(f"/videos/{video_id}")
    task = response.json()
except requests.exceptions.HTTPError as e:
    if e.response.status_code == 404:
        print("Task doesn't exist or expired")
    else:
        raise
Yes, they’re completely independentTwo API systems are completely independent:
  • Different endpoints
  • Different calling methods
  • Same pricing
  • Share same API Key and balance
Usage recommendations:
  • Quick testing → Use sync API
  • Production environment → Use async API (more stable)
  • Batch generation → Use async API
Possible reasons:
  1. Normal - Some processing stages update slowly
  2. Queue waiting - May be queued during peak hours
  3. Task stuck - Rare cases where task may get stuck
Handling:
  • Continue waiting 5-10 minutes
  • If no change after 10 minutes, contact support
  • Provide video_id for troubleshooting
No! Only local file upload is supported.Correct approach:
# ✓ Correct: Upload local file
with open('/path/to/image.png', 'rb') as f:
    files = {'input_reference': f}
    response = requests.post(url, files=files, data=data)
Not supported:
  • ✗ Image URLs
  • ✗ Base64 encoding
  • ✗ Online image links
Reason: Async API uses multipart/form-data format, supporting only local file streams.
Supported formats:
  • ✓ JPG / JPEG
  • ✓ PNG
  • ✓ WebP
Image requirements:
  • File size: < 5MB (recommended)
  • Resolution: Recommended 1280x720 or similar ratio
  • Source: Must be local file
Auto-detect MIME type:
import os

def get_mime_type(path):
    ext = os.path.splitext(path)[1].lower()
    types = {
        '.jpg': 'image/jpeg',
        '.jpeg': 'image/jpeg',
        '.png': 'image/png',
        '.webp': 'image/webp'
    }
    return types.get(ext, 'image/jpeg')
Yes, prompt parameter is required!Even if you just want the image to “naturally animate”, you need to provide a description:Recommended simple prompts:
"Animate the scene naturally"
"Add dynamic effects while keeping original style"
"Bring this scene to life with gentle motion"
More specific prompts work better:
"Create ripples on the lake, trees swaying in wind, clouds moving slowly"
"Make the cat jump from left to right, tail wagging"
"360-degree slow rotation showcasing the product with lighting effects"
Exactly the same price!
Video LengthPrice
10s (landscape/portrait)$0.15/call ≈ ¥1.0/call
15s (landscape/portrait)$0.15/call ≈ ¥1.0/call
Billing notes:
  • Image-to-video and text-to-video cost the same
  • Only charged when successfully generated
  • No charge on failure (including format errors, content violations, etc.)
Almost no impact.Image-to-video and text-to-video take similar time:
  • Typical time: 2-5 minutes
  • Factors: Video length, queue size, complexity
Image size impact:
  • Recommended < 5MB: Fast upload, fast processing
  • Large images: Only affects upload time (seconds), no significant impact on generation time

Error Handling

Common Error Codes

HTTP StatusError TypeDescriptionAction
400Bad RequestInvalid request parametersCheck parameter format and values
401UnauthorizedInvalid API KeyCheck Authorization header
402Payment RequiredInsufficient balanceRecharge and retry
404Not FoundTask doesn’t existCheck video_id or task expired
429Too Many RequestsRequests too frequentReduce polling frequency
500Internal Server ErrorServer errorRetry later
503Service UnavailableService temporarily unavailableWait and retry

Error Response Format

{
  "error": {
    "message": "Error description",
    "type": "invalid_request_error",
    "code": "invalid_parameter"
  }
}

Technical Support

Need Help?

Contact us if you have any questions:

Next Steps

I