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

# 이미지-투-비디오 API 레퍼런스

> Sora 2 이미지-투-비디오 API 레퍼런스 및 라이브 플레이그라운드 — 정적 이미지를 애니메이션으로 만들기 위해 input_reference를 multipart 업로드합니다.

<Info>
  오른쪽의 인터랙티브 Playground는 실시간 디버깅을 지원합니다. **Authorization** 필드에 API Key를 설정하고(형식: `Bearer sk-xxx`), 참조 이미지를 업로드한 다음 prompt를 입력하고, 모델 / 크기 / 초를 선택한 뒤 전송하십시오.
</Info>

<Tip>
  **범위**: 이 페이지는 "참조 이미지로 동영상을 생성"하는 기능을 다룹니다. 즉, 하나의 이미지를 시작 프레임 / 시각적 기준점으로 업로드하여 정적인 비주얼에 애니메이션을 적용합니다. 참조 이미지가 필요하지 않으면 [Text-to-Video endpoint](/ko/api-capabilities/sora-2/text-to-video)를 사용하십시오(동일한 경로, JSON 본문).
</Tip>

<Warning>
  **⚠️ 참조 이미지의 크기는 `size`와 정확히 일치해야 합니다**

  * 업로드한 이미지의 픽셀 크기는 `size` 필드와 같아야 합니다(예: `size=1280x720`는 1280×720 이미지를 요구합니다)
  * 불일치 시 400을 반환합니다: `Inpaint image must match the requested width and height`
  * **업로드 전에 ffmpeg / Pillow로 미리 자르십시오**

  기타 참고 사항:

  * Content-Type은 `multipart/form-data`여야 합니다(JSON 아님)
  * 파일은 하나만 지원됩니다. 필드 이름은 `input_reference`로 고정됩니다
  * 지원 형식: `image/jpeg` / `image/png` / `image/webp`
</Warning>

## 코드 샘플

### Python (OpenAI SDK 드롭인)

```python theme={null}
from openai import OpenAI
import time

client = OpenAI(
    api_key="sk-your-api-key",
    base_url="https://api.apiyi.com/v1"
)

# Step 1: Submit (the OpenAI SDK auto-handles multipart when input_reference is provided)
with open("./reference.png", "rb") as f:
    video = client.videos.create(
        model="sora-2",
        prompt="Animate this scene: gentle waves lapping against the shore, leaves swaying in the breeze",
        seconds="8",
        size="1280x720",
        input_reference=f
    )
print(f"Video ID: {video.id}, status: {video.status}")

# Step 2: Poll
while True:
    video = client.videos.retrieve(video.id)
    print(f"Status: {video.status}, progress: {getattr(video, 'progress', 0)}%")
    if video.status == "completed":
        break
    if video.status == "failed":
        raise RuntimeError(f"Generation failed: {video}")
    time.sleep(15)

# Step 3: Download
client.videos.download_content(video.id).write_to_file("output.mp4")
print("Saved: output.mp4")
```

### Python (원시 requests + multipart)

```python theme={null}
import requests
import time

API_KEY = "sk-your-api-key"
BASE_URL = "https://api.apiyi.com/v1"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}

# Step 1: Multipart upload (image dimensions must equal size)
with open("./reference.png", "rb") as f:
    resp = requests.post(
        f"{BASE_URL}/videos",
        headers=HEADERS,  # Don't manually set Content-Type — requests handles the multipart boundary
        data={
            "model": "sora-2",
            "prompt": "Animate this scene with cinematic camera push-in, soft golden hour lighting",
            "seconds": "8",
            "size": "1280x720"
        },
        files={
            "input_reference": ("reference.png", f, "image/png")
        },
        timeout=60  # Multipart uploads of large images can be slow; use a 60-second timeout
    ).json()
video_id = resp["id"]
print(f"Video ID: {video_id}, status: {resp['status']}")

# Step 2: Poll
deadline = time.time() + 900
while time.time() < deadline:
    status_resp = requests.get(f"{BASE_URL}/videos/{video_id}", headers=HEADERS).json()
    print(f"Status: {status_resp['status']}, progress: {status_resp.get('progress', 0)}%")
    if status_resp["status"] == "completed":
        break
    if status_resp["status"] == "failed":
        raise RuntimeError(f"Generation failed: {status_resp}")
    time.sleep(15)

# Step 3: Download
with requests.get(f"{BASE_URL}/videos/{video_id}/content", headers=HEADERS, stream=True) as r:
    r.raise_for_status()
    with open("output.mp4", "wb") as f:
        for chunk in r.iter_content(chunk_size=8192):
            f.write(chunk)
print("Saved: output.mp4")
```

### cURL

```bash theme={null}
{/* Step 1: Multipart upload + submit */}
curl -X POST "https://api.apiyi.com/v1/videos" \
  -H "Authorization: Bearer sk-your-api-key" \
  -F "model=sora-2" \
  -F "prompt=Animate this scene: gentle waves lapping, leaves swaying, cinematic" \
  -F "seconds=8" \
  -F "size=1280x720" \
  -F "input_reference=@./reference.png;type=image/png"

{/* Step 2: Poll */}
curl -X GET "https://api.apiyi.com/v1/videos/video_abc123" \
  -H "Authorization: Bearer sk-your-api-key"

{/* Step 3: Download */}
curl -X GET "https://api.apiyi.com/v1/videos/video_abc123/content" \
  -H "Authorization: Bearer sk-your-api-key" \
  -o output.mp4
```

### Node.js (fetch + FormData)

```javascript theme={null}
import fs from 'node:fs';
import { fileFromPath } from 'formdata-node/file-from-path';
import { FormData } from 'formdata-node';

const API_KEY = 'sk-your-api-key';
const BASE_URL = 'https://api.apiyi.com/v1';

// Step 1: Multipart upload
const form = new FormData();
form.set('model', 'sora-2');
form.set('prompt', 'Animate this scene with cinematic camera push-in, soft lighting');
form.set('seconds', '8');
form.set('size', '1280x720');
form.set('input_reference', await fileFromPath('./reference.png'));

const submitResp = await fetch(`${BASE_URL}/videos`, {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${API_KEY}` },  // Don't manually set Content-Type
    body: form
});
const { id: videoId } = await submitResp.json();
console.log(`Video ID: ${videoId}`);

// Step 2: Poll
let status = 'queued';
while (status !== 'completed' && status !== 'failed') {
    await new Promise(r => setTimeout(r, 15000));
    const data = await (await fetch(`${BASE_URL}/videos/${videoId}`, {
        headers: { 'Authorization': `Bearer ${API_KEY}` }
    })).json();
    status = data.status;
    console.log(`Status: ${status}, progress: ${data.progress ?? 0}%`);
}

if (status === 'failed') throw new Error('Generation failed');

// Step 3: Download
const contentResp = await fetch(`${BASE_URL}/videos/${videoId}/content`, {
    headers: { 'Authorization': `Bearer ${API_KEY}` }
});
fs.writeFileSync('output.mp4', Buffer.from(await contentResp.arrayBuffer()));
console.log('Saved: output.mp4');
```

### 브라우저 JavaScript

```javascript theme={null}
{/* Demo only; route through your backend in production to avoid leaking the API key. */}
const fileInput = document.getElementById('refImage');  // <input type="file" />
const file = fileInput.files[0];

const form = new FormData();
form.append('model', 'sora-2');
form.append('prompt', 'Animate this scene, gentle motion');
form.append('seconds', '4');
form.append('size', '1280x720');
form.append('input_reference', file);

const submitResp = await fetch('https://api.apiyi.com/v1/videos', {
    method: 'POST',
    headers: { 'Authorization': 'Bearer sk-your-api-key' },
    body: form
});
const { id } = await submitResp.json();
console.log('Video ID:', id);

{/* After polling completes, route the video URL through a backend proxy to avoid downloading large files in the browser. */}
```

## 매개변수 빠른 참조

| 매개변수              | 형식  | 필수  | 기본값        | 설명                                                                               |
| ----------------- | --- | --- | ---------- | -------------------------------------------------------------------------------- |
| `model`           | 문자열 | 예   | —          | `sora-2`(720p 전용) 또는 `sora-2-pro`(720p / 1024p / 1080p 등급)                       |
| `prompt`          | 문자열 | 예   | —          | 동영상 설명입니다. **정적인 이미지가 어떻게 애니메이션되어야 하는지**(카메라 움직임, 객체 움직임, 조명 변화)에 집중하십시오         |
| `seconds`         | 문자열 | 아니요 | `"4"`      | 기간이며 **문자열 열거형**입니다: `"4"` / `"8"` / `"12"`                                      |
| `size`            | 문자열 | 아니요 | `720x1280` | 출력 해상도이며, **반드시 `input_reference` 이미지 크기와 정확히 같아야 합니다**                          |
| `input_reference` | 파일  | 예   | —          | 참조 이미지 파일입니다: `image/jpeg` / `image/png` / `image/webp`, **크기는 `size`와 같아야 합니다** |

<Tip>
  자세한 매개변수 제약, 허용 값, 예시는 오른쪽 플레이그라운드에서 확인할 수 있습니다. **`input_reference`는 multipart로 업로드해야 합니다** — URL과 base64는 지원되지 않습니다.
</Tip>

## 참고 이미지 준비

<Steps>
  <Step title="대상 해상도 선택">
    사용 사례에 따라 먼저 `size`를 선택하십시오: 세로형 `720x1280`, 가로형 `1280x720`, Pro 1080p 가로형 `1920x1080` 등입니다.
  </Step>

  <Step title="정확한 픽셀로 로컬에서 자르기">
    Pillow / ffmpeg를 사용해 이미지를 대상 크기로 자르십시오:

    ```python theme={null}
    from PIL import Image
    img = Image.open("source.jpg")
    img = img.resize((1280, 720), Image.LANCZOS)  # Or crop first then resize to preserve aspect ratio
    img.save("reference.png")
    ```

    또는 한 줄 ffmpeg:

    ```bash theme={null}
    ffmpeg -i source.jpg -vf "scale=1280:720" reference.png
    ```
  </Step>

  <Step title="적절한 형식 선택">
    PNG(무손실, 삽화 / 스크린샷에 이상적)를 우선하고, 바이트를 아끼려면 사진에는 JPEG를, 투명도가 필요하면 WebP를 사용하십시오.
  </Step>

  <Step title="Focus the prompt on &#x22;motion&#x22; not &#x22;appearance&#x22;">
    참고 이미지는 이미 시각 요소를 정의합니다. prompt는 **어떻게 움직일지**에 집중해야 합니다: 카메라 푸시/풀, 오브젝트 움직임, 조명 변화, 캐릭터 표정 등입니다. 예: `"Camera slowly pushes in, leaves gently swaying, sunlight flickering through branches"`.
  </Step>
</Steps>

## 응답 형식

응답 형태는 [텍스트-투-비디오](/ko/api-capabilities/sora-2/text-to-video#response-format)와 **동일합니다**: submit은 `id` + `status: "queued"`를 반환하고, 폴링은 진행 상황을 보고하며, 완료 시 `/v1/videos/{id}/content`를 통해 MP4로 다운로드합니다.

```json theme={null}
{
  "id": "video_abc123def456",
  "object": "video",
  "model": "sora-2",
  "status": "queued",
  "progress": 0,
  "created_at": 1712697600,
  "size": "1280x720",
  "seconds": "8",
  "quality": "standard"
}
```

<Warning>
  **⚠️ 일반적인 400 오류**

  * `Inpaint image must match the requested width and height` — 참조 이미지의 치수가 `size`와 일치하지 않습니다. **가장 흔합니다.** 업로드 전에 클라이언트 측에서 치수를 검증하십시오
  * `Invalid file format` — 업로드된 파일이 jpeg / png / webp가 아니거나 손상되었습니다
  * `Missing required parameter: input_reference` — multipart 필드 이름이 잘못되었습니다(반드시 `input_reference`여야 하며, `image`나 `reference`가 아니어야 합니다)
  * `seconds must be one of "4", "8", "12"` — 문자열 `"4"` 대신 정수 `4`를 전달했습니다
</Warning>

<Info>
  Image-to-video와 text-to-video는 **동일한 초당 요금**을 적용합니다(`seconds` 기준으로 청구됨); 참조 이미지를 업로드해도 추가 비용은 없습니다. [요금표](/ko/api-capabilities/sora-2/overview#pricing)를 참조하십시오.
</Info>


## OpenAPI

````yaml api-reference/sora-2-image-to-video-openapi-en.yaml POST /v1/videos
openapi: 3.1.0
info:
  title: Sora 2 Image-to-Video API
  description: >
    OpenAI Sora 2 / Sora 2 Pro image-to-video endpoint (multipart/form-data
    upload of `input_reference`).


    - **Reference image dimensions must exactly match `size`**, otherwise you
    get `Inpaint image must match the requested width and height`

    - Accepted image formats: `image/jpeg` / `image/png` / `image/webp`

    - Same per-second pricing as text-to-video — uploading a reference image
    does not cost extra

    - **Async task endpoint**: this call only submits the task; combine with
    `GET /v1/videos/{id}` polling and `GET /v1/videos/{id}/content` download


    **Authentication**: Add `Authorization: Bearer YOUR_API_KEY` to the request
    header


    **API Key configuration**: In your APIYI console, set the group to **Sora2官转
    (Sora2 Official)** and billing mode to **usage-based**


    **Get API Key**: Visit the [APIYI Console](https://api.apiyi.com/token) to
    create a token
  version: 1.0.0
servers:
  - url: https://api.apiyi.com
    description: Primary endpoint
  - url: https://vip.apiyi.com
    description: Backup endpoint
security:
  - bearerAuth: []
paths:
  /v1/videos:
    post:
      tags:
        - Video Generation
      summary: 'Image-to-video: submit a video generation task from a reference image'
      description: >
        Submits a Sora 2 image-to-video task. The client must use
        multipart/form-data to upload one reference image plus text fields.


        - Required: `model`, `prompt`, `input_reference`

        - Optional: `seconds` (default `"4"`), `size` (default `"720x1280"`)

        - **`input_reference` image dimensions must equal `size`** — pre-crop
        with ffmpeg / Pillow before upload

        - Response shape and polling/download flow are identical to
        text-to-video
      operationId: generateSora2ImageToVideoEn
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/Sora2ImageToVideoRequest'
            example:
              model: sora-2
              prompt: >-
                Animate this scene: gentle waves lapping, leaves swaying,
                cinematic camera push-in
              seconds: '8'
              size: 1280x720
      responses:
        '200':
          description: Task submitted, returns video_id with queued status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Sora2VideoTask'
        '400':
          description: >-
            Invalid parameters (most common: reference image dimensions mismatch
            with size; also unsupported file format, seconds out of range)
        '401':
          description: Unauthorized — invalid API Key
        '403':
          description: Content policy / not on usage-based billing / group is not Sora2官转
        '413':
          description: Uploaded image too large
        '429':
          description: Rate limit exceeded or insufficient balance
        '500':
          description: Upstream OpenAI gateway error — retry 1–2 times
      security:
        - bearerAuth: []
components:
  schemas:
    Sora2ImageToVideoRequest:
      type: object
      required:
        - model
        - prompt
        - input_reference
      properties:
        model:
          type: string
          description: >-
            Model ID. `sora-2` supports 720p only; `sora-2-pro` supports 720p /
            1024p / 1080p
          enum:
            - sora-2
            - sora-2-pro
          default: sora-2
        prompt:
          type: string
          description: >-
            Video generation prompt. **Focus on how the image should animate**:
            camera motion, object motion, lighting changes
          example: >-
            Animate this scene: gentle waves lapping, leaves swaying, cinematic
            camera push-in
        seconds:
          type: string
          description: 'Video duration as **string enum**: `"4"` / `"8"` / `"12"`'
          enum:
            - '4'
            - '8'
            - '12'
          default: '4'
        size:
          type: string
          description: >
            Output resolution. **Must exactly match the `input_reference` image
            dimensions**:


            - `sora-2` (720p only): `720x1280` / `1280x720`

            - `sora-2-pro` additionally: `1024x1792` / `1792x1024` / `1080x1920`
            / `1920x1080`
          enum:
            - 720x1280
            - 1280x720
            - 1024x1792
            - 1792x1024
            - 1080x1920
            - 1920x1080
          default: 720x1280
        input_reference:
          type: string
          format: binary
          description: >
            Reference image file used as the video's starting frame / visual
            anchor.


            - Accepted formats: `image/jpeg` / `image/png` / `image/webp`

            - **Dimensions must equal `size`**, otherwise you get `Inpaint image
            must match the requested width and height`

            - Only one file is supported; field name is fixed as
            `input_reference`
    Sora2VideoTask:
      type: object
      properties:
        id:
          type: string
          description: Task ID for subsequent polling and download
          example: video_abc123def456
        object:
          type: string
          description: Object type, fixed `video`
          example: video
        model:
          type: string
          description: Model ID used for this task
          example: sora-2
        status:
          type: string
          description: |
            Task status:
            - `queued` — submitted, waiting in queue
            - `in_progress` — generating
            - `completed` — done, ready to download (`/v1/videos/{id}/content`)
            - `failed` — failed (not billed), safe to retry
          enum:
            - queued
            - in_progress
            - completed
            - failed
          example: queued
        progress:
          type: integer
          description: Generation progress percentage (0–100), not strictly linear
          example: 0
        created_at:
          type: integer
          description: Task creation Unix timestamp (seconds)
          example: 1712697600
        completed_at:
          type: integer
          description: >-
            Task completion Unix timestamp (seconds), present only on completed
            status
          example: 1712697900
        size:
          type: string
          description: Actual output resolution (matches the requested `size`)
          example: 1280x720
        seconds:
          type: string
          description: Actual duration generated (matches the requested `seconds`)
          example: '8'
        quality:
          type: string
          description: Quality tier (`standard` for sora-2, `high` for sora-2-pro)
          example: standard
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        API Key from the APIYI console (must use Sora2官转 group + usage-based
        billing)

````