튜토리얼
HiAPI API로 hailuo-2.3-fast/image-to-video 호출하는 방법: curl과 Python 실전
빠르고 저렴한 이미지-투-비디오 등급의 첫 실행 요청 — 모델 ID부터 MP4 다운로드까지
hailuo-2.3-fast/image-to-video가 해 주는 일
hailuo-2.3-fast/image-to-video는 HiAPI 이미지-투-비디오 제품군에서 속도에 최적화된 등급입니다. 정지 화면 한 장과 원하는 움직임을 설명하는 텍스트 프롬프트를 주면, 그 화면에서 앞으로 움직이는 짧은 영상 클립을 반환합니다. 모델 ID의 "fast"가 핵심입니다. 이 등급은 더 낮은 지연 시간과 더 낮은 단일 렌더링 비용을 얻는 대신, 무거운 영상 모델의 세밀한 충실도 일부를 포기합니다. 그래서 반복 작업과 대량 생산에 매우 적합합니다.
"주요 비주얼" 완성본이 아니라 실행 가능한 첫 버전의 클립이 필요할 때 이 모델을 선택하세요: 스토리보드 미리보기, 소셜 우선 루프 영상, 배경 모션 소재, 또는 파이프라인에서 무거운 모델이 최종 렌더링을 담당하는 초안 단계 등이 그렇습니다. 출력물에 긴 길이의 샷, 복잡한 카메라 연출, 또는 많은 프레임에 걸쳐 엄격한 정체성 일관성이 필요하다면 그런 작업은 더 무거운 등급에 맡기세요. 이때도 요청 구조는 동일하게 유지하면 전환은 설정 한 줄 바꾸기로 끝납니다.
아래 내용은 모두 실제로 실행할 수 있는 하나의 요청을 중심으로 전개됩니다. 목표는 성공 응답 스크린샷이 아니라 MP4 파일이 디스크에 떨어지는 것입니다.
요청 한눈에 보기
영상 렌더링은 몇 초에서 몇 분이 걸리기 때문에 API는 비동기입니다: 작업을 제출하고 작업 식별자를 받은 다음, 완료를 보고하고 출력을 넘겨줄 때까지 해당 작업을 폴링합니다. 즉 모든 통합에는 제출과 폴링이라는 두 단계가 있으며, 모델에 따라 달라지는 것은 제출 단계뿐입니다.
제출 요청에는 직접 제어하는 네 가지 요소가 포함됩니다:
- 엔드포인트와 메서드 — HiAPI 영상 생성 엔드포인트로
POST요청을 보냅니다. - 인증 헤더 —
Authorization: Bearer $HIAPI_API_KEY형식의 키입니다. 키는 환경 변수에 두고, 제출하는 코드에 절대 인라인으로 넣지 마세요. - 모델 ID — 리터럴 문자열
hailuo-2.3-fast/image-to-video. 바로 이것이 등급을 선택합니다. - 입력 — 움직임을 설명하는
prompt와 첫 프레임 이미지입니다. 이미지는 공개적으로 접근 가능한 URL 또는 data URI 형태로 제공되며, 해당 엔드포인트에서 계정이 어떤 형태를 허용하는지에 따릅니다. URL로 시작하는 것이 가장 간단합니다. JSON 요청 본문은Content-Type: application/json으로 전송합니다.
이 네 가지 필드를 코드베이스의 한 곳(딕셔너리, 설정 파일, 환경 변수 기반 빌더)에 두세요. 그러면 나중에 다른 HiAPI 영상 모델로 전환할 때 한 줄만 바꾸면 되고 다시 작성할 필요가 없습니다. 응답은 작은 JSON 봉투이며, 그중 작업 식별자가 유일하게 영속화해야 할 필드이고 나머지는 상태 메타데이터입니다.
실행 가능한 curl 요청
이것이 완전한 제출 단계입니다. 이미지 URL을 계정에서 접근 가능한 주소로 바꾸고, 먼저 키를 내보내세요.
export HIAPI_API_KEY="your-key-here"
curl -sS -X POST "https://api.hiapi.ai/v1/video/generations" \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "hailuo-2.3-fast/image-to-video",
"prompt": "slow push in, gentle breeze moving the fabric, film grain",
"image": "https://example.com/first-frame.jpg"
}'
성공 응답은 작업 식별자를 담은 JSON이며, 보통 {"id": "task_...", "status": "queued"} 형태입니다. 봉투 필드의 구체적인 형태는 바뀔 수 있으므로 방어적으로 파싱하세요: 위치가 아니라 키로 작업 ID를 읽고, 알 수 없는 필드는 정보성 내용으로 취급하세요.
디버깅 중이라면 -i를 붙여 HTTP 상태 줄을 확인하세요. 401은 거의 항상 인증 헤더가 없거나 변수가 빈 문자열로 확장되었다는 뜻입니다. 요청 본문을 탓하기 전에 echo $HIAPI_API_KEY를 실행해 보세요. 400은 보통 요청 본문을 가리킵니다: 도달할 수 없는 이미지 URL, 누락된 model, 또는 엔드포인트와 맞지 않는 필드명 등입니다.
반환된 작업 ID를 셸 변수에 복사해 두면 이후 단계에서 다시 입력할 필요가 없습니다:
TASK_ID=$(curl -sS -X POST "https://api.hiapi.ai/v1/video/generations" \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"hailuo-2.3-fast/image-to-video","prompt":"slow push in","image":"https://example.com/first-frame.jpg"}' \
| python -c 'import sys,json; print(json.load(sys.stdin)["id"])')
echo "$TASK_ID"
같은 요청을 Python으로
Python 버전은 요청 본문을 curl과 바이트 단위로 동일하게 유지하므로, 버그를 추적할 때 curl 결과와 Python 결과를 직접 비교할 수 있습니다.
import os
import requests
API_BASE = "https://api.hiapi.ai/v1"
MODEL = "hailuo-2.3-fast/image-to-video"
def submit_video(prompt: str, image_url: str) -> str:
resp = requests.post(
f"{API_BASE}/video/generations",
headers={"Authorization": f"Bearer {os.environ['HIAPI_API_KEY']}"},
json={
"model": MODEL,
"prompt": prompt,
"image": image_url,
},
timeout=60,
)
resp.raise_for_status()
return resp.json()["id"]
여기서 중요한 습관 세 가지가 있습니다. 변수가 없을 때 os.environ[...](.get이 아님)는 크게 실패하며, 바로 그것이 원하는 실패 방식입니다. 명시적 timeout은 멈춘 소켓이 워커 프로세스를 무한정 점유하는 것을 막습니다. 그리고 raise_for_status()는 4xx 응답 본문을 기록 가능한 예외로 바꿔 주므로, 다음 줄에서 ID 누락으로 인해 조용히 발생하는 KeyError를 피할 수 있습니다.
이 함수는 의도적으로 작업 ID만 반환합니다. 제출과 대기는 별개의 관심사이며, 둘을 섞으면 폴링 루프를 테스트하기 어려워집니다. 모델 ID는 모듈 수준에서 한 번만 나타난다는 점에 주목하세요. 다른 HiAPI 영상 모델로 이전할 때 유일하게 바꿔야 할 곳입니다.
영상이 준비될 때까지 작업 폴링
상태 확인은 작업 리소스로 보내는 GET 요청입니다. 대기 중이거나 실행 중인 작업은 비종료 상태를 반환하고, 완료된 작업은 출력을 포함한 종료 상태를 반환합니다.
import time
def wait_for_video(task_id: str, timeout: float = 600.0) -> dict:
deadline = time.monotonic() + timeout
delay = 2.0
url = f"{API_BASE}/video/generations/{task_id}"
while True:
r = requests.get(
url,
headers={"Authorization": f"Bearer {os.environ['HIAPI_API_KEY']}"},
timeout=30,
)
r.raise_for_status()
task = r.json()
if task.get("status") == "succeeded":
return task
if task.get("status") in {"failed", "cancelled"}:
raise RuntimeError(f"task {task_id} ended: {task}")
if time.monotonic() >= deadline:
raise TimeoutError(f"task {task_id} still {task.get('status')} after {timeout}s")
time.sleep(delay)
delay = min(delay * 1.5, 15.0)
이것은 고정 수면이 아니라 재시도 루프입니다. 백오프는 2초에서 시작해 최대 15초까지 점진적으로 늘어나므로, 짧은 렌더링은 빠르게 끝나고 긴 렌더링도 엔드포인트를 두들기지 않습니다. 데드라인은 수면 전에 확인하므로 총 실제 경과 시간이 한 간격을 초과하지 않고 시간 제한 내에 유지됩니다. time.time() 대신 time.monotonic()을 사용하면 시계 조정이 루프를 연장하지 않습니다.
failed와 cancelled는 "계속 대기"와 다르게 취급하세요. 종료 상태이지만 성공하지 않은 것입니다. 이런 경우 전체 작업 객체를 기록하세요. 그것이 얻을 수 있는 유일한 진단 정보이며, ID 하나만으로 실패를 재현하는 것은 매우 고통스럽습니다. 알 수 없는 상태 문자열은 비종료 상태로 보고 계속 폴링하세요. 그러면 새로운 중간 상태가 워커 프로세스를 중단시키지 않습니다.
출력 다운로드와 처리
완료된 작업은 하나 이상의 출력 URL을 담고 있습니다. 바이트를 가져와 자신이 제어하는 곳에 쓰세요.
def download_video(task: dict, dest: str) -> str:
url = task["output"][0]["url"] if isinstance(task.get("output"), list) else task["output"]["url"]
with requests.get(url, stream=True, timeout=120) as r:
r.raise_for_status()
with open(dest, "wb") as f:
for chunk in r.iter_content(chunk_size=1 << 20):
f.write(chunk)
return dest
.content를 호출하지 말고 스트리밍으로 다운로드하세요. 영상은 여러 작업을 처리하는 워커에게 전체를 메모리에 캐싱하기에는 너무 클 수 있습니다. 출력 URL의 구체적 형태는 모델 등급에 따라 조금씩 다를 수 있으므로 인덱싱하기 전에 리스트인지 먼저 확인하세요. 위 분기가 두 경우를 모두 처리합니다.
중요한 규칙: 반환된 URL은 저장 대상이 아니라 짧은 수명의 핸들로 취급하세요. 작업이 성공하는 즉시 파일을 자신의 버킷이나 로컬 경로로 복사하고 그곳에서 제공하세요. URL을 데이터베이스에 영속화하고 며칠 후에 가져오면 403과 빈 대시보드를 만나게 되는 이유가 바로 여기에 있습니다. 객체 스토리지의 경우 동일한 스트리밍 바이트 루프를 클라이언트 업로드 메서드에 직접 연결할 수 있습니다. SDK가 파일류 객체를 받는다면 중간 임시 파일이 필요 없습니다.
실용 팁과 흔한 함정
- 쿼리 매개변수가 아니라 인증 헤더를 사용하세요. 키는
Authorization: Bearer ...에 넣으세요. URL에 붙여 넣은 키는 액세스 로그에 남는 키입니다. - 이미지 정확성보다 이미지 도달 가능성이 더 중요합니다. 제출 시
400은 프롬프트 문제보다 API가 가져올 수 없는 URL(localhost, 사설 버킷, 핫링크 방지 호스트, 만료되는 링크) 때문인 경우가 훨씬 많습니다. 다른 무엇을 디버깅하기 전에 새 브라우저 세션에서 해당 URL을 먼저 테스트하세요. - 요청 본문의 콘텐츠 유형. JSON을
Content-Type: application/json으로 보내세요. curl로 제출할 때-H를 잊으면 서버가 요청 본문을 아예 파싱하지 않을 수 있습니다. - 지나치게 빈번한 폴링. 수면 없는
while True는 속도 제한에 걸리게 할 뿐 렌더링을 더 빨리 끝내지 않습니다. 백오프와 데드라인이 전부입니다. - 어디에도 없는 타임아웃. 제출, 폴링, 다운로드 호출 모두에 타임아웃을 설정하세요. 타임아웃이 없으면 느린 제공자가 자신의 시스템에서 멈춘 큐가 됩니다.
- 프롬프트는 화면이 아니라 움직임을 설명하세요. 프레임 간에 변하는 것(카메라 움직임, 피사체 동작, 환경 요소)을 설명하고 짧게 유지하세요. 정지 이미지를 다시 설명하는 것은 아무 이득도 없습니다.
- 하나의 설정, 여러 모델. 모델 ID, 프롬프트, 이미지 입력을 같은 페이로드 빌더에 두세요. 이 빠른 등급에서 더 무거운 등급으로 옮길 때 문자열 하나만 바꾸면 됩니다.
설정을 엔드투엔드로 검증하는 가장 빠른 방법: curl 제출을 실행하고, 작업 ID를 폴링 루프에 붙여 넣고, MP4가 디스크에 떨어지는 것을 확인하세요. 이 단계가 작동하면 나머지는 포장 작업일 뿐입니다.
관련 글
모든 아티클 보기 →-
튜토리얼
HiAPI API로 hailuo-2.3/text-to-video를 호출하는 방법: curl, Python 및 실행 가능한 요청 예시
-
튜토리얼
HiAPI에서 Kandinsky 6.0 Pro 텍스트-투-비디오 사용하기: curl 및 Python 튜토리얼
요청 우선 실전 데모: 프롬프트에서 비디오 클립까지의 호출 구성, 요청 제출, 완료 상태 폴링 및 결과 다운로드
-
튜토리얼
HiAPI API로 kling-4.0/text-to-video를 사용하는 방법: curl, Python 및 실행 가능한 요청 예제
개발자를 위한 실전 가이드: 영화적 프롬프트, 동기화 오디오, 비동기 폴링 및 출력 다운로드