> ## Documentation Index
> Fetch the complete documentation index at: https://docs.yelinai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Async API

> Sora 2 asynchronous video generation API with more stable task queue approach

**Async Endpoint:** `https://api.yelinai.com/v1/videos`**Call Method:** Three steps (Create Task → Query Status → Get Video)**Advantages:** More Stable | Task Queue | Long-running Tasks Support

**Important Difference - Image-to-Video Upload Method**Async API differs significantly from Sync API for image-to-video:

| Feature        | Sync API               | Async API           |
| -------------- | ---------------------- | ------------------- |
| Image Upload   | Supports URL or Base64 | **Local file only** |
| Request Format | JSON                   | multipart/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

| Feature            | Sync API                             | Async API (Recommended)                   |
| ------------------ | ------------------------------------ | ----------------------------------------- |
| **Call Method**    | Single request, wait for completion  | Create task then poll for results         |
| **Endpoint**       | `/v1/chat/completions`               | `/v1/videos`                              |
| **Resolution**     | Model name (`sora_video2-landscape`) | Parameter (`size: "1280x720"`)            |
| **Duration**       | Model name (`sora_video2-15s`)       | Parameter (`seconds: "15"`)               |
| **Image-to-Video** | Supports image URL                   | Local file upload only                    |
| **Failed Billing** | Charged even if generation fails     | ⭐ No charge on failure                    |
| **Stability**      | Depends on long connection           | ⭐⭐⭐⭐⭐ More Stable                         |
| **Progress**       | Stream output                        | Poll for progress percentage              |
| **Timeout**        | Needs long timeout setting           | Task runs independently, no timeout limit |
| **Use Cases**      | Quick testing, real-time feedback    | Production 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

Step 1: Create Task (Text-to-Video)

Step 1: Create Task (Image-to-Video)

Step 2: Query Status

Step 3: Download Video

```
curl -X POST "https://api.yelinai.com/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.yelinai.com/v1/videos`Create a new video generation task

#### Request Parameters

| Parameter         | Type   | Required                    | Options                    | Description                                                                                                                     |
| ----------------- | ------ | --------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `model`           | string | ✓                           | `"sora-2"`, `"sora-2-pro"` | Model name                                                                                                                      |
| `prompt`          | string | ✓                           | -                          | Video generation prompt                                                                                                         |
| `size`            | string | ✓                           | `"1280x720"`, `"720x1280"` | Video resolution (landscape/portrait)                                                                                           |
| `seconds`         | string | ✓                           | `"10"`, `"15"`             | Video duration (seconds)                                                                                                        |
| `input_reference` | file   | Required for image-to-video | -                          | Local image file (local upload only) Supported formats: JPG, PNG, WebP Recommended size: 1280x720 Recommended file size: \< 5MB |

**Model Selection**

* `sora-2`: Base model, 720P resolution, extremely high stability, \$0.15/call
* `sora-2-pro`: HD model, 1080P resolution, generation time \~10 minutes, \$0.8/call

#### Response Fields

| Field        | Type    | Description                                   |
| ------------ | ------- | --------------------------------------------- |
| `id`         | string  | Unique task identifier for subsequent queries |
| `object`     | string  | Fixed as `"task"`                             |
| `model`      | string  | Model used                                    |
| `status`     | string  | Task status: `"submitted"`                    |
| `created_at` | integer | Creation timestamp                            |
| `expires_at` | integer | Expiration timestamp (24 hours later)         |

### 2. Query Task Status

**GET** `https://api.yelinai.com/v1/videos/{video_id}`Query current status and progress of video generation task

#### Path Parameters

| Parameter  | Type   | Required | Description                         |
| ---------- | ------ | -------- | ----------------------------------- |
| `video_id` | string | ✓        | Task ID returned when creating task |

#### Response Fields

| Field          | Type    | Description                                  |
| -------------- | ------- | -------------------------------------------- |
| `id`           | string  | Task ID                                      |
| `object`       | string  | Fixed as `"video"`                           |
| `model`        | string  | Model used                                   |
| `status`       | string  | Task status (see below)                      |
| `progress`     | integer | Progress percentage (0-100)                  |
| `url`          | string  | Video download path (completed status only)  |
| `created_at`   | integer | Creation timestamp                           |
| `completed_at` | integer | Completion timestamp (completed status only) |
| `error`        | object  | Error information (failed status only)       |

#### Task Status

| Status        | Description                       | Progress |
| ------------- | --------------------------------- | -------- |
| `submitted`   | Submitted, waiting for processing | 0%       |
| `in_progress` | Generating                        | 1-99%    |
| `completed`   | Generation completed              | 100%     |
| `failed`      | Generation failed                 | -        |

### 3. Get Video Content

**GET** `https://api.yelinai.com/v1/videos/{video_id}/content`Download completed video file

#### Path Parameters

| Parameter  | Type   | Required | Description |
| ---------- | ------ | -------- | ----------- |
| `video_id` | string | ✓        | Task ID     |

#### Response

Returns binary stream of video file (MP4 format)

**Important Notice**Video 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.yelinai.com/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.yelinai.com/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

Polling Interval

**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

Timeout Handling

**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

Retry Strategy

**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

Batch Generation

**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 Combination          | Price       | Discounted Rate |
| ------------------------------ | ----------- | --------------- |
| 10s video (landscape/portrait) | \$0.15/call | ≈ ¥1.0/call     |
| 15s video (landscape/portrait) | \$0.15/call | ≈ ¥1.0/call     |
| **HD (sora-2-pro)**            | \$0.8/call  | ≈ ¥5.4/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

How long is task validity?

**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

How to cancel a running task?

**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

Why do I get 404 when querying?

**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
```

Can async and sync APIs be mixed?

**Yes, they’re completely independent**Two 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

Why isn't progress field updating?

**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

Does image-to-video support image URLs?

\*\*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.

What image formats are supported?

**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')
```

Is prompt required for image-to-video?

\*\*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"
```

Is image-to-video more expensive?

**Exactly the same price!**

| Video Length             | Price                   |
| ------------------------ | ----------------------- |
| 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.)

Does image affect generation time?

\*\*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 Status | Error Type            | Description                     | Action                            |
| ----------- | --------------------- | ------------------------------- | --------------------------------- |
| 400         | Bad Request           | Invalid request parameters      | Check parameter format and values |
| 401         | Unauthorized          | Invalid API Key                 | Check Authorization header        |
| 402         | Payment Required      | Insufficient balance            | Recharge and retry                |
| 404         | Not Found             | Task doesn’t exist              | Check video\_id or task expired   |
| 429         | Too Many Requests     | Requests too frequent           | Reduce polling frequency          |
| 500         | Internal Server Error | Server error                    | Retry later                       |
| 503         | Service Unavailable   | Service temporarily unavailable | Wait 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:

* **Email:** [threezhang.cn@gmail.com](mailto:threezhang.cn@gmail.com)
* **WeChat:** Kikivivikids
* **Telegram:** [https://t.me/laozhang\_cn](https://t.me/laozhang_cn)
* **Documentation:** []()

## Next Steps

## Sync API

Check out sync API approach

## Examples

View more usage examples

## Pricing

Learn about detailed pricing

## FAQ

View more Q\&A
