HiAPI
블로그

튜토리얼

hailuo 2.3 이미지-투-비디오 API 사용법: curl, Python 및 실행 가능한 요청 예시

Jul 11, 2026
hailuo 2.3 이미지-투-비디오 API 사용법: curl, Python 및 실행 가능한 요청 예시

요약

  • 이것은 무엇인가: hiapi에서 hailuo-2.3/image-to-video API를 사용하는 실행 가능한 레시피입니다—정적 이미지를 짧은 동영상으로 변환합니다: 작업 생성, 작업 폴링, .mp4 다운로드.
  • 엔드포인트: POST https://api.hiapi.ai/v1/tasks, 요청 본문에 "model": "hailuo-2.3/image-to-video"를 작성한 다음 GET /v1/tasks/<taskId>로 폴링하여 작업이 종료 상태에 도달할 때까지 기다립니다.
  • 두 개의 필수 입력 필드: prompt와 image_url—첫 프레임을 가리키는 단일 공개 URL(배열이 아님).
  • duration은 문자열이며, 숫자가 아닙니다: "6" 또는 "10". 정수 6을 전달하면 400으로 거부됩니다.
  • 스키마는 엄격합니다. motion_strength, camera_movement 또는 resolution 매개변수가 없습니다—전달하면 400으로 거부됩니다. 움직임은 전적으로 prompt를 통해 제어됩니다.

무엇을 구축하며, 무엇이 필요한가

정적 이미지—제품 사진, 캐릭터 렌더링, 사진—가 있고, hailuo-2.3이 이를 움직이게 하여 정확히 이 프레임에서 시작하는 동영상 클립을 생성하려 합니다. hailuo-2.3은 물리적으로 그럴듯한 움직임(천, 머리카락, 신체 역학)으로 유명하여 정적 이미지를 생동감 있게 만드는 데 매우 적합합니다.

두 가지가 필요합니다:

  1. hiapi API 키. dashboard에서 생성하세요. 키는 sk-... 형태이며 Authorization: Bearer 헤더에 넣습니다.
  2. 공개적으로 접근 가능한 첫 프레임 이미지 URL. API가 image_url에서 이미지를 가져오므로 인증 없이 접근 가능해야 합니다—S3/R2/CDN 링크는 가능하고, localhost 경로나 만료된 서명 URL은 불가능합니다.

hiapi의 모든 생성 모델과 마찬가지로 이 모델도 비동기 작업 인터페이스를 사용합니다: POST로 작업을 생성하면 즉시 taskId를 받고, 이후 폴링 또는 콜백으로 결과를 수신합니다. 동영상에는 동기 엔드포인트가 없습니다—클립 렌더링에는 시간이 걸리며 일반적인 HTTP 타임아웃을 초과합니다.

요청 스키마

hailuo-2.3/image-to-video의 전체 입력 스키마—실제 API로 검증됨:

필드 유형 필수 설명
prompt string ✅ 장면이 아니라 움직임을 설명—장면은 이미지에서 옵니다.
image_url string ✅ 공개 URL, 즉 첫 프레임. 단수형—image_urls가 아닙니다.
duration string 선택 "6" 또는 "10"(초). 반드시 문자열이어야 합니다.
prompt_optimizer boolean 선택 플랫폼이 생성 전에 prompt를 확장하도록 합니다.

스키마는 엄격합니다: 이 표에 없는 것은 모두 거부됩니다. motion_strength, camera_movement 또는 resolution을 보내면 다음을 그대로 받게 됩니다:

{
  "code": 400,
  "error_code": "INVALID_REQUEST",
  "message": "invalid input: <root>: additional properties 'resolution', 'camera_movement', 'motion_strength' not allowed"
}

기억해야 할 두 가지 결과:

  • 움직임 조정 손잡이가 없습니다. hailuo-2.3의 움직임 물리는 prompt로만 안내됩니다—아래 프롬프트 작성 섹션을 참조하세요.
  • 이 엔드포인트에는 해상도나 종횡비 제어가 없습니다. API가 400을 반환할 매개변수에 대한 UI를 구축하지 마세요.

최소 실행 예시: curl

작업 생성:

curl -X POST https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer sk-<your-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "hailuo-2.3/image-to-video",
    "input": {
      "prompt": "The woman turns toward the camera and smiles; wind gently moves her hair; slow cinematic push-in",
      "image_url": "https://your-cdn.example.com/first-frame.jpg",
      "duration": "6",
      "prompt_optimizer": true
    }
  }'

응답(발췌)에는 data.taskId 아래에 작업 ID가 포함됩니다:

{ "data": { "taskId": "..." } }

폴링:

curl -s https://api.hiapi.ai/v1/tasks/<taskId> \
  -H "Authorization: Bearer sk-<your-key>"

클립이 렌더링되는 동안 data.status는 비종료 상태를 보고합니다. "success"로 바뀌면 동영상 URL은 data.output[0].url에 있습니다; "fail"이면 data.error.code와 data.error.message가 이유를 설명합니다.

작업이 성공하면 즉시 파일을 다운로드하세요. 출력 URL은 서명되었으며 만료 시간(expireAt)이 있습니다—바이트를 자체 저장소에 저장하고 작업 URL을 절대 핫링크하지 마세요.

최소 실행 예시: Python

동일한 흐름의 전체 스크립트—생성, 폴링, 다운로드:

import time
import requests

API_BASE = "https://api.hiapi.ai/v1/tasks"
API_KEY = "sk-<your-key>"
HEADERS = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}


def create_task() -> str:
    payload = {
        "model": "hailuo-2.3/image-to-video",
        "input": {
            "prompt": (
                "The cat stretches, hops off the windowsill, and walks toward "
                "the camera; soft afternoon light, subtle handheld camera sway"
            ),
            "image_url": "https://your-cdn.example.com/first-frame.jpg",
            "duration": "6",          # string, not int: "6" or "10"
            "prompt_optimizer": True,
        },
    }
    r = requests.post(API_BASE, json=payload, headers=HEADERS, timeout=30)
    task_id = (r.json().get("data") or {}).get("taskId")
    if not task_id:
        raise RuntimeError(f"create failed ({r.status_code}): {r.text[:300]}")
    return task_id


def wait_task(task_id: str, timeout_s: int = 900, poll_s: int = 10) -> dict:
    deadline = time.time() + timeout_s
    while time.time() < deadline:
        r = requests.get(f"{API_BASE}/{task_id}", headers=HEADERS, timeout=30)
        task = r.json().get("data") or {}
        status = task.get("status")
        if status == "success":
            return task
        if status == "fail":
            err = task.get("error") or {}
            raise RuntimeError(f"task failed: {err.get('code')}: {err.get('message')}")
        time.sleep(poll_s)
    raise TimeoutError(f"task {task_id} not finished after {timeout_s}s")


def main() -> None:
    task_id = create_task()
    print("task created:", task_id)
    task = wait_task(task_id)
    url = task["output"][0]["url"]
    # The URL is signed and expires -- download immediately, store the bytes yourself.
    video = requests.get(url, timeout=120).content
    with open("hailuo-output.mp4", "wb") as f:
        f.write(video)
    print("saved hailuo-output.mp4")


if __name__ == "__main__":
    main()

동영상 작업은 이미지 작업보다 눈에 띄게 오래 실행되므로 스크립트는 10초마다 폴링하고 15분의 넉넉한 상한을 설정합니다. ~5초마다보다 더 공격적으로 폴링하지 마세요—렌더링이 더 빨라지지 않습니다.

움직임을 제어하는 prompt 작성

움직임 매개변수가 없으므로 prompt가 유일한 방향타입니다—hailuo-2.3에서는 이것으로 충분합니다. 세 가지 경험 법칙:

  • 외형이 아니라 움직임을 설명하세요. 모델은 이미 이미지를 보았습니다. "a beautiful woman in a red dress"는 prompt를 낭비합니다; "she turns her head slowly to the left as the camera pushes in"가 이를 잘 활용합니다.
  • 카메라 움직임을 평이한 언어로 설명하세요. "slow push-in", "orbit right", "handheld sway", "static camera"—이것들은 (존재하지 않는) camera_movement 필드가 아니라 prompt 텍스트에 작성해야 합니다.
  • 확신이 없으면 prompt_optimizer의 도움을 받으세요. "prompt_optimizer": true로 설정하면 플랫폼이 생성 전에 짧은 prompt를 확장합니다—짧은 입력에 좋은 기본값입니다. 정교하게 작성한 표현을 엄격히 따르고 싶다면 끄세요.

프로덕션 고려사항

폴링보다 콜백을 우선하세요. 작업 생성 시 콜백을 첨부하면 hiapi가 종료 상태 결과를 엔드포인트로 POST하므로 열린 루프를 유지할 필요가 없습니다:

{
  "model": "hailuo-2.3/image-to-video",
  "input": { "...": "..." },
  "callback": { "url": "https://your-app.example.com/hooks/hiapi", "when": "final" }
}

"when": "final"은 종료 상태에서 한 번만 트리거됩니다. 폴링은 스크립트와 노트북에 적합합니다; 콜백이 서버의 올바른 형태입니다—동영상 렌더링이 충분히 길어 폴링에 블로킹된 worker는 실질적인 비용입니다.

다음 오류를 명시적으로 처리하세요:

  • 401 및 "code": "permission_denied"—키에 문제가 있거나 해당 키에 이 모델을 사용할 권한이 없습니다. 메시지에 지원팀에 보낼 수 있는 request_id가 포함됩니다.
  • 400 INVALID_REQUEST—스키마 위반. 메시지는 정확합니다, 예: duration: got number, want string 또는 image_url: missing required field "image_url". 그대로 기록하세요.
  • 작업 생성 시 402—잔액 부족. 동영상은 작업당 이미지보다 높게 과금되므로 이미지 작업이 여전히 정상적으로 실행되는 키가 동영상 생성에서 먼저 실패할 수 있습니다. 충전하거나 dashboard에서 사용량을 확인하세요.
  • 작업이 "fail"로 전환됨—생성 측 실패; data.error를 확인하세요. 특히 이미지-투-비디오의 흔한 원인: image_url을 가져올 수 없음(비공개 버킷, 만료된 서명 URL).

재시도에 대해 멱등성을 확보하세요. 프로세스가 생성과 다운로드 사이에 죽으면 순진한 재시작은 두 번째 렌더링을 생성(및 과금)합니다. create_task가 반환된 직후 taskId를 영속화하고, 재시작 시 다시 생성하지 않고 폴링을 계속하세요.

관련 읽기

자주 묻는 질문

hailuo-2.3 이미지-투-비디오는 어떤 길이를 지원하나요? 두 가지: "6" 및 "10"초, 문자열로 전달. 다른 값—또는 동일 값을 숫자로 작성—은 400 INVALID_REQUEST를 반환합니다.

왜 "duration": 6은 실패하고 "duration": "6"은 작동하나요? 스키마는 duration을 문자열 열거형으로 정의합니다. API는 명확히 알려줍니다: duration: got number, want string. 이는 이 엔드포인트에서 가장 흔한 400입니다.

출력 해상도나 종횡비를 설정할 수 있나요? 아니요. 엔드포인트는 resolution 또는 aspect_ratio 필드를 노출하지 않으며, 둘 중 하나를 보내면 400입니다—스키마는 알 수 없는 속성을 무시하지 않고 거부합니다.

카메라 움직임이나 움직임 강도를 어떻게 제어하나요? prompt를 통해. camera_movement나 motion_strength 매개변수가 없습니다—카메라와 움직임 지시를 평이한 언어로 작성하세요("slow push-in", "she walks toward the camera").

여러 참조 이미지를 전달할 수 있나요? 아니요—image_url은 단일 문자열, 즉 첫 프레임입니다. 이는 image_urls 배열을 받는 hiapi의 다른 일부 모델과 다릅니다; 스키마는 모델별로 정의되므로 반드시 모델 페이지를 확인하세요.

첫 프레임에 어떤 이미지 형식이 지원되나요? 표준 웹 형식(JPEG/PNG), 공개적으로 접근 가능한 URL에서 제공. 플랫폼이 해당 URL을 가져올 수 없으면 작업은 생성 후 실패합니다—따라서 제출 전에 링크가 공개적으로 접근 가능한지 확인하세요.

hailuo-2.3/text-to-video와 무엇이 다른가요? 텍스트-투-비디오는 prompt만으로 장면을 발명합니다; 이미지-투-비디오는 첫 프레임을 이미지에 고정하고 그로부터 애니메이션합니다. 작업 수명 주기는 완전히 동일합니다—입력 스키마만 다릅니다(여기서는 image_url이 필수이고, 텍스트-투-비디오에는 이미지 필드가 전혀 없습니다).

비용은 얼마인가요? 동영상은 출력 초 단위로 과금되며 요금은 변동합니다—현재 수치는 hailuo-2.3/image-to-video 모델 페이지와 가격 페이지를 확인하세요.

영구 AI 아티팩트로 빌드 시작하기

API 키를 받아 태스크 기반의 하나의 API로 이미지, 비디오, 오디오 모델을 호출하세요.

API 문서 보기