튜토리얼
Ray 3.2 이미지-투-비디오 API 사용법: curl, Python 그리고 한 번에 성공하는 요청
Ray 3.2 이미지-투-비디오는 정지 이미지 한 프레임에 애니메이션을 입힙니다. 이미지를 주고 움직임을 설명하는 프롬프트를 함께 보내면, 당신의 이미지에서 시작하는 짧은 영상을 반환합니다. hiapi에서 모델 id는 ray-3.2/image-to-video이며, 다른 모든 비디오 모델과 동일한 통합 비동기 작업 API로 호출합니다—POST /v1/tasks를 보낸 뒤 폴링하거나 콜백을 받습니다.
이것은 Ray 3.2 텍스트-투-비디오와 다른 엔드포인트이며, 단지 프롬프트만 다른 것이 아닙니다. 입력 스키마가 더 작고 더 엄격하며, 텍스트 모델에서 익숙했을 두 가지 제어 항목이 사라졌습니다. 이 가이드는 한 번에 통과하는 요청을 제공합니다: 정확한 스키마(2026-10-20 라이브 API 대조 검증 완료), 바로 복사할 수 있는 curl, 완전한 Python 제출/폴링/다운로드 스크립트, 그리고 프로덕션 세부 사항—콜백, 멱등성, 그리고 실제 오류 응답, 그중에는 예상치 못한 위치에 나타나지 않는 실패 모드도 포함됩니다.
무엇을 만들고 무엇을 준비해야 하는가
목표: 첫 프레임 이미지 + 움직임 프롬프트를 제출하고, 작업을 폴링한 뒤, 이미지가 움직이기 시작하는 무음 .mp4를 다운로드합니다.
사전 조건:
- hiapi API 키—대시보드에서 발급받으세요. 키는
sk-...형태이며Authorization: Bearer헤더에 넣습니다. 작업 API가 처음이라면 먼저 빠른 시작을 보세요. - 첫 프레임 이미지의 공개적으로 접근 가능한 이미지 URL(아래 참조). 아무 HTTP 클라이언트—우리는
curl과 Python(requests)을 보여줍니다.
Ray 3.2 이미지-투-비디오는 비디오 클립 단위로 과금됩니다. 클립 길이가 고정되어 있으므로(아래 참조), resolution이 비용을 조절하는 유일한 수단입니다—1080p 클립 비용은 540p의 몇 배입니다. 현재 가격은 가격에서 확인하고, 프롬프트를 반복할 때는 540p로 프로토타이핑하세요.
정확한 입력 스키마(필수 2개, 선택 1개)
모든 것은 input 객체 안에 들어갑니다. 스키마는 엄격합니다—인식할 수 없는 필드는 거부합니다. 필드별로 라이브 API와 대조 검증했습니다:
| 필드 | 타입 | 필수 여부 | 허용 값 | 기본값 |
|---|---|---|---|---|
prompt |
string | 예 | 화면이 어떻게 움직여야 하는지(카메라, 피사체 움직임) | — |
image_url |
string | 예 | 첫 프레임의 공개 URL | — |
resolution |
string | 아니오 | 540p, 720p, 1080p |
540p |
텍스트-투-비디오 모델에서 넘어온 사람이 자주 밟는 세 가지 함정:
duration없음. 클립 길이는 고정(약 5초)입니다.duration을 보내면 잘리는 것이 아니라 바로 거부됩니다. 이는 Ray 3.2 텍스트-투-비디오와 정반대이며, 거기서는duration이 선택 가능한 정수(5 또는 10)입니다.aspect_ratio없음. 출력 화면 비율은 입력 이미지를 따르므로 설정할 것이 없습니다. 텍스트 모델에서는 화면 비율을 선택하지만, 여기서는 이미지가 결정합니다.(아래 실제 실행으로 확인: 정사각형 입력이 정사각형 클립을 생성했습니다.)- 스키마는 닫혀 있음.
seed,negative_prompt,prompt_optimizer및 그 밖의 어떤 필드도 400과 함께additional properties '<field>' not allowed를 반환합니다. 다른 모델에서 요청 본문을 이식하고 여분 키가 무시될 것이라 가정하지 마세요.
검증이 실제로 반환하는 것
다음은 라이브 엔드포인트의 실제 응답입니다—고의로 잘못된 값을 제출했고, 작업은 생성되지 않았으며 과금도 없었습니다:
// POST with no image_url
{"code":400,"data":null,"error_code":"INVALID_REQUEST",
"message":"invalid input: image_url: missing required field \"image_url\""}
// POST with "resolution": "2k"
{"code":400,"data":null,"error_code":"INVALID_REQUEST",
"message":"invalid input: resolution: value must be one of '540p', '720p', '1080p'"}
// POST with a "duration" field
{"code":400,"data":null,"error_code":"INVALID_REQUEST",
"message":"invalid input: <root>: additional properties 'duration' not allowed"}
// POST with an "aspect_ratio" field
{"code":400,"data":null,"error_code":"INVALID_REQUEST",
"message":"invalid input: <root>: additional properties 'aspect_ratio' not allowed"}
message는 구체적인 문제 필드를 지적하므로, 잊어버렸을 때 API 자체가 스키마 참고서가 됩니다.
첫 프레임 준비
image_url은 hiapi worker가 가져올 수 있는 URL이어야 합니다—공개 링크여야 하며, 로컬 경로나 data: base64 blob이 아닙니다. 이미지는 클립의 화면 비율과 오프닝 구도를 결정하므로, 실제로 움직임이 시작되길 원하는 그 프레임에서 출발하세요.
미리 짚고 넘어갈 함정이 하나 있습니다. 놓치기 쉬운데: 잘못된 image_url은 작업 생성 시 잡히지 않습니다. 생성 호출은 요청의 형태만 검증합니다(문자열인가?). URL이 해석 가능한지는 검사하지 않습니다. 도달 불가능하거나 404인 이미지 URL은 완전히 정상적인 200과 taskId를 반환합니다—그러고 몇 초 뒤 작업이 fail로 뒤집힙니다. 정확한 실패 페이로드는 문제 해결 섹션에서 보게 될 것입니다. 핵심은 항상 작업의 종료 상태 status를 기준으로 분기하고, 생성이 200을 반환했다고 성공이라고 가정하지 않는 것입니다.
최소 동작 예제: curl
작업 생성:
curl -s -X POST https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer sk-YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "ray-3.2/image-to-video",
"input": {
"prompt": "slow cinematic push-in, gentle parallax, soft drifting light, the subject breathing almost imperceptibly",
"image_url": "https://your-cdn.example.com/first-frame.jpg",
"resolution": "540p"
}
}'
생성 응답은 작업 id를 담고 있습니다:
{ "code": 200, "data": { "taskId": "tk-hiapi-01M5..." }, "message": "success" }
종료 상태에 도달할 때까지 폴링:
curl -s https://api.hiapi.ai/v1/tasks/tk-hiapi-01M5... \
-H "Authorization: Bearer sk-YOUR_KEY"
작업이 실행되는 동안 data.status는 여러 진행 중 상태를 거칩니다(handling 같은 값을 보게 된 뒤 archiving). 두 가지 종료 상태는 success와 fail입니다. 성공 시:
{
"code": 200,
"data": {
"taskId": "tk-hiapi-01M5...",
"status": "success",
"created": 1792435735,
"completed": 1792435781,
"storage": "temp",
"output": [
{ "type": "video", "artifactId": "278504",
"expireAt": 1793040580,
"url": "https://temp.hiapi.ai/.../clip.mp4" }
]
},
"message": "success"
}
data.output[0].url을 즉시 다운로드하세요. temp.hiapi.ai의 서명된 URL이며 expireAt(Unix 타임스탬프, 약 일주일 뒤)이 붙어 있습니다—핫링크하지 말고 바이트를 자체 스토리지에 저장하세요. Ray 3.2는 무음 MP4를 반환하며, 기다릴 오디오 트랙은 없습니다.
완전한 Python 스크립트(제출 → 폴링 → 다운로드)
import time
import requests
API_BASE = "https://api.hiapi.ai/v1"
API_KEY = "sk-YOUR_KEY"
HEADERS = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
def create_task(image_url: str, prompt: str, resolution: str = "540p") -> str:
resp = requests.post(
f"{API_BASE}/tasks",
headers=HEADERS,
json={
"model": "ray-3.2/image-to-video",
"input": {
"prompt": prompt,
"image_url": image_url, # public URL of the first frame (required)
"resolution": resolution, # "540p" | "720p" | "1080p"
# no "duration" and no "aspect_ratio" — both are rejected
},
},
timeout=30,
)
resp.raise_for_status()
return resp.json()["data"]["taskId"]
def wait_task(task_id: str, timeout_s: int = 900, poll_interval: int = 8) -> dict:
"""Poll until terminal. Tolerates transient network blips by re-polling
the SAME saved task_id instead of aborting the whole job."""
deadline = time.time() + timeout_s
while time.time() < deadline:
try:
resp = requests.get(f"{API_BASE}/tasks/{task_id}", headers=HEADERS, timeout=30)
resp.raise_for_status()
except requests.RequestException:
time.sleep(poll_interval) # a dropped poll is not a failed task
continue
task = resp.json()["data"]
if task["status"] == "success":
return task
if task["status"] == "fail":
err = task.get("error") or {}
raise RuntimeError(f"task failed: {err.get('code')}: {err.get('message')}")
time.sleep(poll_interval) # any non-terminal status = still running
raise TimeoutError(f"task {task_id} not finished after {timeout_s}s")
task_id = create_task(
image_url="https://your-cdn.example.com/first-frame.jpg",
prompt="slow cinematic push-in, gentle parallax, soft drifting light",
)
print("task created:", task_id)
task = wait_task(task_id)
video_url = task["output"][0]["url"]
# Output URLs expire — download right away and store the bytes yourself.
video = requests.get(video_url, timeout=120).content
with open("ray32-i2v.mp4", "wb") as f:
f.write(video)
print(f"saved ray32-i2v.mp4 ({len(video) / 1e6:.1f} MB)")
pip install requests로 실행하고, 키와 실제 image_url로 바꾸면 작업이 끝난 뒤 디스크에 .mp4가 생깁니다.
실제 실행은 어떤 모습인가
숫자를 구체화하기 위해 2026-10-20 라이브 API에 대한 실제 540p 실행을 보여드립니다. 첫 프레임: 1600×1600(정사각형) 이미지; 프롬프트: 느린 영화식 푸시인.
- 작업은
handling → archiving → success를 거쳤고, 서버 측 작업은 약 45초에 완료됐습니다(created생성,completed완료)—8초 폴링 루프를 포함하면 실제 소요는 약 60초였습니다. 비디오 작업은 즉시가 아니라 수십 초에서 몇 분이 걸립니다. 넉넉한 마감 시간을 두세요. - 출력은 720×720 MP4, 약 1.3 MB, 5.04초, 무음이었습니다.
이 실행은 파라미터 표가 말해주지 않는 두 가지를 알려줍니다:
- 출력 화면 비율은 입력과 일치—정사각형 들어가면 정사각형 나옵니다. 이것이 왜
aspect_ratio필드가 없는지입니다. 원하는 클립 구도에 맞춰image_url을 프레이밍하세요. resolution은 품질 등급이지 문자 그대로의 픽셀 높이가 아닙니다. 1600×1600 입력에540p를 요청했더니 720×720이 반환됐습니다—이 등급은 입력 형태에 따라 스케일링되며 고정 치수를 강제하지 않습니다. 정확한 출력 치수가 필요하면 납품 클립의 크기를 직접 조정하세요.
프로덕션 주의 사항: 콜백, 멱등성, 오류
폴링보다 콜백 우선
스크립트가 아닌 모든 상황에서는 폴링 대신 생성 시 webhook을 등록하세요:
{
"model": "ray-3.2/image-to-video",
"input": {
"prompt": "...",
"image_url": "https://your-cdn.example.com/first-frame.jpg",
"resolution": "720p"
},
"callback": { "url": "https://your-app.example.com/hooks/hiapi", "when": "final" }
}
"when": "final"을 사용하면 hiapi가 작업이 종료 상태에 도달했을 때 엔드포인트를 한 번 호출합니다—폴링 트래픽도 없고 간격 사이에 완료를 놓치지도 않습니다. 그런 다음 핸들러가 GET /v1/tasks/<id>를 실행해 output[0].url을 가져옵니다. HTTPS 엔드포인트를 노출할 수 없는 CLI와 노트북에서는 폴링이 여전히 괜찮습니다.
재시도를 멱등하게 만들기
생성 호출이 반환되는 순간 taskId를 영속화하세요. 프로세스가 생성 도중에 죽으면 저장된 id를 폴링해 복구하세요—작업을 다시 생성하지 마세요. 그러면 두 번째 클립에 비용을 지불하게 됩니다. 원래 생성 요청이 작업 id를 반환하기 전에 실패한 경우에만 다시 생성하세요. 위의 내결함성 폴링 루프는 이미 이 규칙을 따릅니다: 네트워크 요청이 끊기면 처음부터가 아니라 같은 id를 다시 폴링합니다.
실제로 보게 될 오류
아래 표의 각 행은 검증된 라이브 응답 형태입니다:
| 증상 | 응답 | 해결 |
|---|---|---|
| 키 누락/유효하지 않음 | 401, 중첩 {"error":{"code":"permission_denied","type":"hiapi_error","request_id":"...","message":"This API key is invalid..."}} |
Authorization: Bearer sk-... 헤더와 키에 접근 권한이 있는지 확인하세요. 지원에 문의할 때 request_id를 제공하세요. |
image_url 누락 |
400 INVALID_REQUEST, message: "image_url: missing required field" |
이 모델은 image_url이 필수입니다—텍스트 전용 모드는 없습니다. |
| 잘못된 열거 값 / 여분 필드 | 400 INVALID_REQUEST, 평탄 {"code":400,"error_code":"INVALID_REQUEST","message":"..."} |
message가 지적하는 필드를 읽으세요. duration/aspect_ratio의 경우 additional properties ... not allowed라고 말합니다—삭제하세요. |
| 폴링 시 잘못된 작업 id | 404, 평탄 {"code":404,"data":null,"message":"task not found"}(참고: 404에는 error_code가 없습니다) |
id가 틀렸거나, 다른 계정의 키로 폴링했습니다. |
image_url 도달 불가 |
생성은 200 + taskId를 반환하고, 작업은 이후 data.status:"fail"과 함께 종료되며 data.error:{"code":"INVALID_REQUEST","message":"Invalid request parameters for this model..."}를 담습니다 |
이것이 함정입니다. 생성 호출은 죽은 링크와 살아 있는 링크를 구분하지 못합니다. 이미지가 공개적으로 접근 가능한지 검증하고 항상 종료 상태 data.status를 읽으세요. |
마지막 행은 명심할 가치가 있습니다. 작업이 실패해도 폴링의 HTTP 외피는 여전히 200입니다—실패는 HTTP 상태 코드가 아니라 data.status와 data.error 안에 있습니다. 실패한 작업은 환불되지만, 코드는 여전히 그것을 알아차려야 합니다. data.status == "fail"을 기준으로 분기하고, 200을 믿지 마세요.
관련 리소스
- Ray 3.2 이미지-투-비디오 모델 페이지—플레이그라운드, 실시간 상태 및 전체 해상도별 가격표
- Ray 3.2 텍스트-투-비디오 모델 페이지—형제 엔드포인트; 이미지가 아닌 텍스트에서 시작(
duration과aspect_ratio가 추가되고image_url이 제거됨) - Hailuo 2.3 이미지-투-비디오 API 사용법—같은 작업 API의 또 다른 이미지-투-비디오 튜토리얼로, 스키마 비교에 유용
- grok-imagine 이미지-투-비디오 API 사용법—비교용 세 번째 i2v 모델
- hiapi 가격—해상도별 클립당 비디오 가격
자주 묻는 질문
모델 id는 무엇이고 어느 엔드포인트를 호출해야 합니까?
ray-3.2/image-to-video, POST https://api.hiapi.ai/v1/tasks에 제출하고 파라미터는 input 아래에 둡니다. 비동기입니다—taskId를 돌려받은 뒤 GET /v1/tasks/<id>를 폴링하거나(또는 콜백을 등록) 합니다.
입력에 무엇이 필요한가?
정확히 두 개의 필수 필드—prompt(움직임)와 image_url(첫 프레임의 공개 URL)—에 선택적 resolution(540p/720p/1080p)이 더해집니다. 그 밖의 모든 것은 거부됩니다.
왜 길이나 화면 비율을 설정할 수 없나?
클립 길이가 고정(약 5초)이라 duration 필드가 없고, 출력 화면 비율은 입력 이미지에서 상속되므로 aspect_ratio 필드가 없습니다. 보내면 둘 다 400을 반환합니다. 선택 가능한 길이가 필요하면 Ray 3.2 텍스트-투-비디오를 사용하세요.
생성 호출은 200을 반환했는데 비디오가 오지 않습니다. 왜?
가장 흔한 원인은 image_url 도달 불가입니다. 생성 호출은 요청 형태만 검증하므로 죽은 링크도 taskId를 반환합니다—그러고 작업이 fail로 끝납니다. 종료 상태까지 폴링하고 data.error를 읽으세요. 이미지가 공개적으로 가져올 수 있는지 확인하세요(인증 불필요, 로컬 경로 아님, base64 아님).
클립에 오디오가 있나? 없습니다—Ray 3.2 이미지-투-비디오는 무음 MP4를 반환합니다. 후처리에서 사운드를 추가하거나 별도 오디오 모델을 사용하세요.
클립 하나에 얼마인가?
클립 단위로 과금되며, 길이가 고정이므로 resolution이 유일한 수단입니다—1080p 클립 비용은 540p의 몇 배입니다. 가격은 변동되므로 숫자를 하드코딩하지 말고 가격 페이지를 확인하고, 먼저 540p로 프로토타이핑하세요.
얼마나 기다린 뒤 포기해야 하나?
수십 초에서 몇 분이 정상입니다. 폴링 루프에서 비종료 data.status를 허용하고 넉넉한 마감 시간(10–15분)을 두거나, "when": "final"이 붙은 callback을 등록해 간격을 추측하지 말고 hiapi가 알려주게 하세요.
관련 글
모든 아티클 보기 →-
튜토리얼
HiAPI API로 hailuo-2.3-fast/image-to-video 호출하는 방법: curl과 Python 실전
빠르고 저렴한 이미지-투-비디오 등급의 첫 실행 요청 — 모델 ID부터 MP4 다운로드까지
-
튜토리얼
HiAPI API로 hailuo-2.3/text-to-video를 호출하는 방법: curl, Python 및 실행 가능한 요청 예시
-
튜토리얼
HiAPI에서 Kandinsky 6.0 Pro 텍스트-투-비디오 사용하기: curl 및 Python 튜토리얼
요청 우선 실전 데모: 프롬프트에서 비디오 클립까지의 호출 구성, 요청 제출, 완료 상태 폴링 및 결과 다운로드