# Wan 3.0 API: Python으로 text-to-video와 image-to-video

> Wan 3.0의 text-to-video와 image-to-video를 Python으로 호출하는 방법, 파라미터, 클립, 오류, Alibaba 가격을 정리합니다.

- Canonical HTML: https://omniakey.com/ko/blog/wan-3-0-api
- Markdown: https://omniakey.com/ko/blog/wan-3-0-api.md
- Published: 2026-10-31 (updated 2026-10-31)
- Author: Kiki, COO at OmniaKey
- Language: ko
- Category: API setup
- Reading time: 8 min
- All posts: https://omniakey.com/blog/llms.txt

---

[Wan 3.0](https://omniakey.com/ko/wan3-0-video)은 Alibaba Cloud Model Studio에서 `wan3.0-video`로, 더 빠른 `wan3.0-video-prime`과 함께 제공됩니다. 모든 호출은 비동기입니다. 작업을 만들고, ID를 폴링하고, MP4를 내려받습니다. Python 스크립트로 텍스트에서 5초 720P 클립을 2 min 42 s에, 정지 이미지에서 3 min 20 s에 만들었습니다. 둘 다 소리가 들어 있습니다.

1. `OMNIAKEY_API_KEY`에 키를 넣습니다.
2. 모델, 작업 종류, 프롬프트, 파라미터, 새 `Idempotency-Key`를 담아 `https://api.omniakey.com/v1/media/tasks`로 POST합니다.
3. 상태가 succeeded가 될 때까지 `GET /v1/media/tasks/{id}`를 폴링하고, 곧바로 `assets[0].url`을 내려받습니다.

## Wan 3.0 공식 API가 있나요? 어떤 모델을 써야 하나요?

있습니다. Alibaba Cloud Model Studio가 공식 API이고 `wan3.0-video`와 `wan3.0-video-prime`을 모두 제공합니다(참조 문서 업데이트 2026년 10월 15일). 생성 호출에는 `X-DashScope-Async: enable` 헤더가 필요하고, Alibaba가 안내하는 Python 경로는 DashScope SDK 2.0.1 이상입니다.

||Alibaba Model Studio|OmniaKey|
|-|-|-|
|모델 ID 2종|제공|제공|
|비동기 헤더|필요|불필요|
|영상 링크 유지|24시간|약 1시간, 갱신 가능|
|과금 단위|초당|초당|
|Python|DashScope SDK|표준 라이브러리|

두 ID 모두 요청 본문 모양은 하나이고, 보낸 미디어를 보고 모델이 모드를 정합니다. 아래 스크립트는 `wan3.0-video`를 실행하고, `MODEL` 줄에서 바꿉니다. [Wan 3.0 Prime](https://omniakey.com/ko/wan3-0-video-prime)은 실행하지 않았습니다. OmniaKey 문서는 Prime을 같은 기능을 약 절반의 시간에 돌려주는 모델로 설명합니다(2026년 10월 31일 확인).

## 첫 호출 전에 무엇이 필요한가요?

세 가지이고, 먼저 키입니다.

1. 셸에서 내보낸 OmniaKey API 키입니다.

   ```bash
   export OMNIAKEY_API_KEY="your-key"
   ```

2. Python 3. 스크립트는 표준 라이브러리만 불러오므로 python3로 그대로 실행됩니다.

3. image to video에는 첫 프레임이 필요합니다. 폭이 약 1024 px인 JPEG이나 공개 HTTPS URL입니다. 스크립트는 700,000바이트를 넘는 파일을 거부합니다. Base64가 3분의 1을 더하고 본문 상한이 1 MiB이기 때문입니다(문서, 2026년 10월 31일 확인).

## 어떤 파라미터가 중요한가요?

일곱 개 필드가 거의 모든 것을 정하고, 모두 `parameters` 안의 값입니다.

|파라미터|하는 일|기본값|
|-|-|-|
|Duration|클립 길이(초)|5|
|Resolution|해상도 단계|1080P|
|Ratio|화면 비율|adaptive|
|Audio|사운드 트랙|on|
|Seed|재현성 힌트|-1|
|Prompt extend|프롬프트를 다시 씀|on|
|Watermark|모서리 표시 추가|off|

스크립트는 duration, resolution, ratio를 지정합니다. 720P를 요청하는데, 정가 기준 초당 요금이 Alibaba의 기본값인 1080P의 절반입니다.

## Python 코드는 어떻게 생겼나요?

아래 스크립트는 2026년 10월 31일에 그대로 실행했습니다. 표준 라이브러리만, 두 가지 모드, 바꾸는 줄은 MODEL 하나입니다.

```python
import base64
import json
import os
import time
import urllib.error
import urllib.request
import uuid

API = "https://api.omniakey.com/v1/media/tasks"
KEY = os.environ["OMNIAKEY_API_KEY"]
MODEL = "wan3.0-video"

def request(method, url, body=None, extra_headers=None):
    headers = {"Authorization": f"Bearer {KEY}", "User-Agent": "omniakey-example/1"}
    data = None
    if body is not None:
        headers["Content-Type"] = "application/json"
        data = json.dumps(body).encode()
    headers.update(extra_headers or {})
    req = urllib.request.Request(url, data=data, headers=headers, method=method)
    with urllib.request.urlopen(req, timeout=120) as resp:
        return json.load(resp)

def poll(url, attempts=5):
    for attempt in range(attempts):
        try:
            return request("GET", url)
        except urllib.error.HTTPError as err:
            if err.code < 500 or attempt == attempts - 1:
                raise
        except urllib.error.URLError:
            if attempt == attempts - 1:
                raise
        time.sleep(5 * (attempt + 1))

def run_task(body, out_path):
    task = request("POST", API, body, {"Idempotency-Key": str(uuid.uuid4())})["id"]
    print("task", task, flush=True)
    for _ in range(180):  # 5-second polls, up to 15 minutes
        time.sleep(5)
        state = poll(f"{API}/{task}")
        if state["status"] not in ("queued", "processing"):
            break
    else:
        raise TimeoutError(f"{task} is still {state['status']}: poll it again later, do not resubmit")
    if state["status"] != "succeeded":
        raise RuntimeError(state.get("error") or state["status"])
    asset_url = state["assets"][0]["url"]  # signed link: short-lived, so download now
    req = urllib.request.Request(asset_url, headers={"User-Agent": "omniakey-example/1"})
    with urllib.request.urlopen(req, timeout=300) as resp, open(out_path, "wb") as f:
        f.write(resp.read())
    return task

def text_to_video(prompt, out_path, duration=5, resolution="720P", ratio="16:9"):
    body = {
        "model": MODEL,
        "type": "video",
        "operation": "text_to_video",
        "input": {"prompt": prompt},
        "parameters": {"duration": duration, "resolution": resolution, "ratio": ratio},
    }
    return run_task(body, out_path)

def image_to_video(prompt, image_path, out_path, duration=5, resolution="720P"):
    with open(image_path, "rb") as f:
        raw = f.read()
    if len(raw) > 700_000:  # base64 adds a third, and the whole body must stay under 1 MiB
        raise ValueError("first frame too large: save it as a JPEG about 1024 px wide")
    frame = "data:image/jpeg;base64," + base64.b64encode(raw).decode()
    body = {
        "model": MODEL,
        "type": "video",
        "operation": "image_to_video",
        "input": {"prompt": prompt, "media": [{"type": "first_frame", "url": frame}]},
        "parameters": {"duration": duration, "resolution": resolution, "ratio": "adaptive"},
    }
    return run_task(body, out_path)

if __name__ == "__main__":
    print("done", text_to_video(
        "A blacksmith hammers a glowing horseshoe on an anvil in a dark forge. Sparks fly at each "
        "strike and light his leather apron. The camera slowly pushes in. Sound of the hammer ringing "
        "on steel and the fire crackling. No dialogue, no music, no text.",
        "forge.mp4",
    ))
    print("done", image_to_video(
        "Image 1 is the first frame. The paper boat drifts slowly to the right across the puddle. "
        "Light rain makes small ripples and the cafe lights shimmer in the water. The camera stays "
        "low and still. Sound of light rain. No dialogue, no music, no text.",
        "boat.jpg",
        "boat-drifts.mp4",
    ))
```

각 부분이 하는 일입니다.

- `run_task`는 작업마다 새 `Idempotency-Key`를 만듭니다. 같은 키로 같은 본문을 다시 보내면 원래 작업이 돌아오고 두 번 과금되지 않습니다(문서, 2026년 10월 31일 확인).
- 5초마다 최대 15분 동안 폴링하고, 그래도 끝나지 않으면 TimeoutError를 냅니다.
- 타임아웃이면 같은 ID를 나중에 다시 폴링합니다. 다시 보내면 새 키로 두 번째 작업이 생기고 과금됩니다.
- 에셋 URL은 서명되어 있고 수명이 짧으므로 첫 URL을 곧바로 내려받습니다.
- `image_to_video`는 700,000바이트를 넘는 프레임을 거부합니다. Base64가 3분의 1을 더하고 본문 상한이 1 MiB입니다.
- 이미지 호출은 ratio를 `adaptive`로 두므로 클립이 정지 이미지의 모양을 따릅니다.

출력은 이렇습니다.

```text
task task_<id>
done task_<id>
task task_<id>
done task_<id>
```

## 두 호출은 무엇을 만들었나요?

두 파일 모두 5.04초 MP4이고, 30 fps로 150프레임, H.264, 44.1 kHz AAC 스테레오 트랙입니다.

### Text to video: 대장간

![Wan 3.0 대장간 클립의 프레임 네 장](https://omniakey.com/_img/blog/wan-3-0-api/wan-forge-grid.4c1b058b.1288.webp)

_Text to video, 720P 5 s: 0.3초, 2.2초, 3.5초, 4.7초 프레임._

클립은 프롬프트를 따릅니다. 어두운 셔츠에 가죽 앞치마를 두른 대장장이가 붉게 빛나는 편자를 모루에 두드리고, 2.2초에 불꽃이 튀며, 카메라가 조금 다가갑니다. 흠은 텍스트를 넣지 말라고 했는데 모루에 글자가 깨진 각인 표시가 생긴 것입니다. 시작부터 파일까지 2 min 42 s, MP4는 1280×720에 6.6 MB입니다.

### Image to video: 종이배

![Wan 3.0 종이배 클립의 첫 프레임과 프레임 세 장](https://omniakey.com/_img/blog/wan-3-0-api/wan-paper-boat-grid.e2ffd154.1288.webp)

_Image to video: 보낸 정지 이미지(왼쪽 위), 이어서 1.6초, 3.5초, 4.7초의 클립._

첫 프레임은 Nano Banana 2.1로 만든 정지 이미지로, 빗물 웅덩이에 뜬 빨간 종이배입니다. 1024×571, 158 KB JPEG로 줄여 data URL로 인라인 전송했습니다. 클립은 그 황혼 빛과 젖은 조약돌을 유지하고, 배는 요청대로 왼쪽에서 오른쪽으로 흐르고, 1.6초에 물결이 생기고, 카메라는 낮게 고정되어 있습니다. adaptive가 정지 이미지의 모양을 따랐기 때문에 1284×716으로 돌아왔고, 3 min 20 s에 7.4 MB였습니다.

### 소리는 켜져 있나요? "no music"은 통하나요?

소리는 기본으로 켜져 있습니다. Gemini 3.8 Flash는 대장간 트랙을 말소리도 음악도 없는 금속성 소리로, 종이배 트랙을 쓸쓸하고 조용한 피아노 선율로 설명했습니다. 종이배 프롬프트는 "no music"으로 끝났는데도 그랬습니다. Alibaba의 [prompt guide](https://www.alibabacloud.com/help/en/model-studio/wan3-video-generation-prompt-guide)(2026년 10월 16일 업데이트)는 "No BGM"이나 "No background music"이라고 쓰라고 권하고, 사용한 프롬프트는 둘 다 아니었습니다.

`"audio": false`로 두면 파일에는 영상 트랙만 들어갑니다. 2초 480P 랜턴 작업에는 사운드 트랙이 없었고, Alibaba의 요금은 어느 쪽이든 같았습니다.

|클립|시간|크기|소리|
|-|-|-|-|
|대장간(텍스트)|2 min 42 s|1280×720|금속음, 음악 없음|
|종이배(이미지)|3 min 20 s|1284×716|"no music"에도 피아노|
|랜턴, 소리 끔|4 min 36 s|854×480|사운드 트랙 없음|

대기 시간은 달라집니다. 2초에 소리를 끈 작업은 4 min 36 s 걸려 5초 720P 클립 둘보다 오래 걸렸습니다. 프롬프트 문장은 [Wan 3.0 프롬프트 가이드](https://omniakey.com/ko/blog/wan-3-0-prompt-guide)에 구조와 예시가 있습니다.

## 영상 파일은 어떻게 받고, 링크는 얼마나 유지되나요?

상태가 succeeded, failed, canceled 중 하나가 될 때까지 `GET /v1/media/tasks/{id}`를 폴링합니다. 실행 중에는 queued나 processing입니다. 끝난 작업은 약 1시간 유효한 서명된 에셋 URL을 돌려줍니다(OmniaKey 문서, 2026년 10월 31일 확인). 곧바로 내려받고, 만료되면 작업을 다시 폴링해 새 URL을 받습니다. 기록은 24시간 남습니다.

파일은 30 fps H.264 영상의 MP4이고, Alibaba 자체 영상 URL은 24시간 유효합니다. Wan 3.0 작업을 취소해도 렌더링은 멈추지 않고, 취소 뒤에 성공한 작업도 과금됩니다. 제출 전에 duration과 resolution을 확인하십시오.

## 어떤 오류가 나나요?

대부분은 작업이 생기기 전에 돌아오고, 각각 요청이나 키를 고치면 해결됩니다.

- **400 `model_price_error`** — duration이 2\~30초 범위를 벗어났습니다. `"duration": 1` 테스트는 0.9 s 만에 "media duration must be between 2 and 30 seconds"로 실패했습니다. 작업은 생기지 않았고 과금도 없었습니다.
- **400 `missing_idempotency_key`** — Idempotency-Key 헤더가 없습니다. 255자까지 넣어 하나 붙입니다.
- **409 `idempotency_key_reused`** — 같은 키에 다른 본문입니다. 프롬프트마다 새 키를 만듭니다.
- **403 `model_not_allowed`** — 그 키로 그 모델을 부를 수 없습니다. `model` 값을 확인하고, 다음으로 키 설정을 봅니다.
- **403과 잔액·상한 메시지** — 잔액이 부족하거나 키별 상한에 걸렸습니다. 충전하거나 상한을 올립니다.
- **404 `task_not_found`** — ID가 틀렸거나 24시간이 지난 작업입니다. 생성 호출이 돌려준 ID를 폴링합니다.
- **409 `task_state_conflict`** — 작업이 이미 끝났습니다. 성공 뒤에 취소가 도착한 경우입니다. 먼저 상태를 읽습니다.
- **429** — 요청이 너무 많습니다. OmniaKey는 고정 레이트 리밋을 공개하지 않으므로 지수 백오프와 지터로 간격을 벌립니다.
- **503 `no_available_channel`** — 그 작업을 처리할 경로가 없습니다. video to video 같은 경우입니다. text to video나 image to video를 씁니다.
- **`TimeoutError`** — 15분이 지나도 실행 중입니다. 같은 ID를 나중에 폴링합니다. 다시 보내면 두 번째 작업이 생기고 과금됩니다.

## Wan 3.0 클립은 얼마인가요?

Alibaba는 영상 초 단위로 과금하고, 실패한 요청은 과금하지 않습니다(가격 페이지 업데이트 2026년 10월 29일). 표는 Alibaba의 싱가포르 정가를 초당으로 적은 것이고, 2026년 10월 31일에 [Alibaba의 Model Studio 가격 페이지](https://www.alibabacloud.com/help/en/model-studio/model-pricing)에서 확인했습니다.

|Model|480P|720P|1080P|
|-|-|-|-|
|Wan 3.0|$0.05|$0.1|$0.2|
|Wan 3.0 Prime|$0.068|$0.14|$0.28|

5초 720P 클립은 Wan 3.0에서 $0.50(5 × $0.1), Prime에서 $0.70(5 × $0.14)입니다. Wan 3.0의 세 가격에는 "Limited-time 30% off" 표시가 있고, 그 숫자가 할인을 포함하는지는 페이지에 없습니다. 소리를 켜든 끄든 요금은 같습니다.

무료 할당량은 싱가포르 `wan3.0-video`에서 입력과 출력을 합쳐 30초 영상입니다. Prime에는 없습니다. Alibaba의 중국(베이징)은 표준 모델의 달러 단가가 더 낮고, 480P, 720P, 1080P에서 초당 $0.041256, $0.082513, $0.165025입니다.

OmniaKey도 초 단위로 과금하고, 현재 요금은 [Wan 3.0](https://omniakey.com/ko/wan3-0-video)과 [Wan 3.0 Prime](https://omniakey.com/ko/wan3-0-video-prime) 모델 페이지에 있습니다.

## 자주 묻는 질문

### 호출 한 번에 얼마나 걸리나요?

Alibaba는 보통 1\~5분 걸리고 15초마다 폴링하라고 권합니다(참조 문서, 2026년 10월 31일 확인). 세 작업은 2 min 42 s, 3 min 20 s, 4 min 36 s였습니다.

### 소리를 끄려면 어떻게 하나요?

`parameters` 안에서 `"audio": false`를 설정하면 MP4에 영상 트랙만 들어옵니다. 랜턴 작업이 그랬습니다. 요금은 같습니다.

### wan3.0-video와 wan3.0-video-prime 중 무엇을 쓰나요?

Alibaba의 낮은 정가와 싱가포르의 30초 무료 할당량을 원하면 `wan3.0-video`, 시간이 더 중요하면 Prime입니다. OmniaKey는 Prime을 같은 기능을 약 절반의 시간에 처리하는 모델로 설명합니다.

### 영상 링크는 얼마나 유지되나요?

OmniaKey에서는 약 1시간입니다. 작업을 다시 폴링하면 새 링크가 나오고, 기록은 24시간 남습니다. Alibaba 자체 영상 URL은 24시간 유효합니다.

## 출처 확인

- [Wan 3.0 영상 생성 API 참조](https://www.alibabacloud.com/help/en/model-studio/wan3-video-generation-api-reference), 2026년 10월 31일 확인
- [Wan 3.0 영상 생성 가이드](https://www.alibabacloud.com/help/en/model-studio/wan3-video-generation-guide), 2026년 10월 31일 확인
- [Wan 3.0 영상 생성 프롬프트 가이드](https://www.alibabacloud.com/help/en/model-studio/wan3-video-generation-prompt-guide), 2026년 10월 31일 확인
- [Alibaba Cloud Model Studio 모델 가격](https://www.alibabacloud.com/help/en/model-studio/model-pricing), 2026년 10월 31일 확인
- [OmniaKey 영상 API 문서](https://docs.omniakey.com/en/media/video), 2026년 10월 31일 확인
- [OmniaKey 미디어 API 개요](https://docs.omniakey.com/en/media/overview), 2026년 10월 31일 확인
- [OmniaKey API 오류 코드](https://docs.omniakey.com/en/api/errors), 2026년 10월 31일 확인
