HiAPI
블로그

튜토리얼

HappyHorse 1.1 레퍼런스 기반 비디오 생성 API: 바로 실행 가능한 curl + Python 튜토리얼

Jun 26, 2026
HappyHorse 1.1 레퍼런스 기반 비디오 생성 API: 바로 실행 가능한 curl + Python 튜토리얼

HappyHorse 1.1 R2V는 최대 아홉 장의 레퍼런스 이미지와 하나의 텍스트 프롬프트를 짧은 비디오로 변환하며, 여러 샷에 걸쳐 피사체, 의상, 장면 스타일을 일관되게 유지합니다. HiAPI에서 이 모델은 통합 비동기 작업 엔드포인트를 통해 제공되며, 모델 ID는 happyhorse-1.1/reference-to-video입니다. 이 튜토리얼은 먼저 실행 가능한 최소 요청을 보여주고, 이어서 콜백을 사용하는 프로덕션급 버전을 다룹니다.

목표 및 사전 조건

다음 작업을 완료하게 됩니다:

  1. 1-9장의 레퍼런스 이미지를 API가 접근할 수 있는 HTTPS URL로 업로드(또는 이미 호스팅된 상태로 둠)합니다.
  2. 이 URL들을 참조하는 작업을 https://api.hiapi.ai/v1/tasks에 POST합니다.
  3. 작업이 success 상태에 도달하면 data.output[0].url에서 최종 MP4 주소를 읽습니다.

사전 조건:

  • HiAPI 계정과 sk-로 시작하는 API Key. HiAPI 콘솔에서 발급받을 수 있으며, 인증 문서에서 요청 헤더 규칙을 확인하세요. 키는 Authorization: Bearer sk-... 형식으로 전달해야 하며, 절대 클라이언트 코드에 넣어서는 안 됩니다.
  • 이미지 호스팅 요건: 각 레퍼런스 이미지는 JPEG, PNG 또는 WEBP 형식이어야 하고, 최단 변 ≥ 400px, ≤ 20MB여야 합니다. 공개 CDN URL을 사용할 수 있으며, 작업 실행기가 접근할 수 있다면 서명된 S3 URL도 사용할 수 있습니다.

최소 실행 요청(curl)

schema가 허용하는 최소 요청 본문입니다. 기본값인 1080p / 16:9 / 5초 출력을 사용하므로 prompt와 reference_image 두 필드만 설정하면 됩니다.

curl -X POST https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer sk-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "happyhorse-1.1/reference-to-video",
    "input": {
      "prompt": "[Image 1]의 캐릭터가 [Image 2] 스타일의 장면 속을 걷고 있다, 시네마틱 샷",
      "reference_image": [
        "https://static.hiapi.ai/example/ref-1.jpg",
        "https://static.hiapi.ai/example/ref-2.jpg"
      ]
    }
  }'

응답은 작업 봉투(envelope)이며, 실제로 필요한 유일한 필드는 data.taskId입니다:

{
  "code": 200,
  "data": { "taskId": "task_xxx", "status": "queued" }
}

종료 상태가 될 때까지 폴링합니다:

curl https://api.hiapi.ai/v1/tasks/task_xxx \
  -H "Authorization: Bearer sk-YOUR_KEY"

data.status가 success로 바뀌면 비디오 URL은 data.output[0].url에 있습니다. 이 URL에는 expireAt 타임스탬프가 있으므로 즉시 파일을 다운로드하거나 영구 스토리지로 이전하세요. 웹사이트에서 이 주소를 직접 핫링크하지 마세요.

파라미터

R2V 엔드포인트는 입력을 엄격하게 검증하며, 알 수 없는 필드는 400 INVALID_REQUEST를 반환합니다. 전체 schema(POST /v1/tasks 기준 실시간 검증 완료):

필드 타입 필수 허용 값 기본값
model string 예 happyhorse-1.1/reference-to-video(고정값) -
input.prompt string 예 [Image 1], [Image 2]…… 로 레퍼런스 이미지를 참조하며, 순서는 reference_image와 일치해야 함 -
input.reference_image string[] 예 1-9개의 HTTPS URL, JPEG/PNG/WEBP 형식, 최단 변 ≥ 400px, 장당 ≤ 20MB -
input.resolution enum 아니오 720p, 1080p 1080p
input.aspect_ratio enum 아니오 16:9, 9:16, 3:4, 4:3, 4:5, 5:4, 1:1, 9:21, 21:9 16:9
input.duration integer 아니오 3-15(초) 5
callback.url string 아니오 작업 종료 상태 이벤트를 수신할 HTTPS URL -
callback.when enum 아니오 final(현재 유일한 값) final

다소 직관적이지 않은 몇 가지 검증 규칙은 문제가 되기 전에 알아두는 것이 좋습니다:

  • 레퍼런스 이미지 URL이 한 장뿐이어도 reference_image는 배열로 전달해야 합니다. 문자열을 직접 전달하면 reference_image: got string, want array가 반환됩니다.
  • duration은 3에서 15(포함) 사이의 정수여야 합니다. 999와 1 모두 명확한 경계 오류 메시지를 반환합니다.
  • 최상위에 seed, negative_prompt, fps, cfg_scale, steps는 존재하지 않습니다. API는 알 수 없는 속성을 거부합니다.

해상도와 길이 조합별 전체 가격은 HiAPI 가격 페이지를 참고하세요. 비용은 resolution과 duration 두 파라미터에 따라 동시에 달라집니다.

Python: 엔드투엔드 + 폴링

아래 코드 블록은 작업을 제출하고, 종료 상태까지 폴링하고, 비디오를 다운로드하는 자체 완결형 생성기입니다. 어느 서비스에든 복사해 붙일 수 있도록 의도적으로 의존성을 최소화했습니다(requests만 사용). 보다 완전한 요청 생명주기 설명은 퀵 스타트를 참고하세요.

import os
import time
import requests

API = "https://api.hiapi.ai/v1/tasks"
HEADERS = {"Authorization": f"Bearer {os.environ['HIAPI_API_KEY']}"}


def submit_r2v(prompt: str, reference_images: list[str], duration: int = 5,
               resolution: str = "1080p", aspect_ratio: str = "16:9") -> str:
    if not 1 <= len(reference_images) <= 9:
        raise ValueError("reference_image must contain 1-9 URLs")
    payload = {
        "model": "happyhorse-1.1/reference-to-video",
        "input": {
            "prompt": prompt,
            "reference_image": reference_images,
            "resolution": resolution,
            "aspect_ratio": aspect_ratio,
            "duration": duration,
        },
    }
    r = requests.post(API, json=payload, headers=HEADERS, timeout=60)
    r.raise_for_status()
    data = r.json()
    if data.get("code") != 200:
        raise RuntimeError(f"create failed: {data}")
    return data["data"]["taskId"]


def wait_for_video(task_id: str, timeout_s: int = 600, poll_s: int = 5) -> str:
    deadline = time.time() + timeout_s
    while time.time() < deadline:
        r = requests.get(f"{API}/{task_id}", headers=HEADERS, timeout=30)
        r.raise_for_status()
        task = r.json()["data"]
        status = task["status"]
        if status == "success":
            return task["output"][0]["url"]
        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} did not finish in {timeout_s}s")


def download(url: str, path: str) -> None:
    with requests.get(url, stream=True, timeout=300) as r:
        r.raise_for_status()
        with open(path, "wb") as f:
            for chunk in r.iter_content(1 << 16):
                f.write(chunk)


if __name__ == "__main__":
    task_id = submit_r2v(
        prompt="[Image 1]의 피사체 디자인을 유지하며 [Image 2]의 분위기 속을 걷고 있다",
        reference_images=[
            "https://static.hiapi.ai/example/char.jpg",
            "https://static.hiapi.ai/example/mood.jpg",
        ],
        duration=5,
        resolution="720p",
        aspect_ratio="9:16",
    )
    print("submitted", task_id)
    video_url = wait_for_video(task_id)
    print("ready", video_url)
    download(video_url, "out.mp4")

이 코드는 의도적으로 다음과 같은 일을 합니다:

  • HIAPI_API_KEY 환경변수에서 키를 읽어 git이나 컨테이너 로그에 절대 남지 않게 합니다.
  • r.raise_for_status()를 사용해 JSON 파싱 전에 전송 오류를 잡습니다.
  • success와 fail만 종료 상태로 간주합니다. 그 외 모든 상태(queued, processing 등)는 루프를 계속 돌립니다.
  • 출력 URL을 즉시 다운로드합니다. 출력 URL은 서명된 주소이며 만료됩니다. 나중에 파일이 필요하면 영구 스토리지로 이전하거나 자체 버킷에 복사하세요.

프로덕션 환경: 폴링에서 webhook으로 전환

5초마다 폴링하는 방식은 노트북이나 저트래픽 worker에는 문제없습니다. 실제 서비스에서는 작업 생성 시 콜백을 등록해 HiAPI가 종료 상태 이벤트를 엔드포인트로 푸시하게 하세요. 그러면 worker가 유휴 상태를 유지할 수 있고 빈 GET 요청 비용도 지불할 필요가 없습니다. 전체 요청 schema는 작업 생성을, 폴백 폴링은 작업 상세 조회를 참고하세요.

payload = {
    "model": "happyhorse-1.1/reference-to-video",
    "input": {
        "prompt": "[Image 1]의 캐릭터가 [Image 2]의 도시 거리를 지나간다",
        "reference_image": [
            "https://cdn.example.com/char.jpg",
            "https://cdn.example.com/street.jpg",
        ],
        "resolution": "1080p",
        "aspect_ratio": "16:9",
        "duration": 8,
    },
    "callback": {
        "url": "https://your-domain.com/hiapi/callback",
        "when": "final",
    },
}

수신 엔드포인트에 두 가지를 반드시 연결해야 합니다:

  1. taskId 기준 멱등 처리. success와 fail은 모두 종료 상태이며, HiAPI의 재시도(또는 자체 재시도 계층에서 발생한 중복)는 동일한 taskId를 다시 전달합니다. 어떤 부수 효과(S3 복사, Slack 메시지, 과금 이벤트)를 일으키기 전에 중복을 제거하세요.
  2. 폴백 대조 폴링. 엔드포인트가 다운되면 콜백을 유실합니다. 최악의 생성 시간을 초과해 processing 상태에 머문 모든 작업에 대해 GET /v1/tasks/<id>를 호출하는 가벼운 대조 작업을 유지하세요.

실제로 마주치게 될 오류

아래 오류들은 명시적으로 처리할 가치가 있습니다. 나머지는 일시적인 5xx이며 SDK나 HTTP 클라이언트가 백오프로 재시도해야 합니다.

  • 401 permission_denied — Authorization 헤더가 없거나 형식이 잘못되었거나 키가 폐기된 경우입니다. 요청 헤더를 고치려 하지 말고 콘솔에서 키를 재발급하세요.
  • 400 INVALID_REQUEST와 필드 경로 — 오류 메시지가 어디서 실패했는지 정확히 알려줍니다. 예를 들어 reference_image: maxItems: got 10, want 9 또는 aspect_ratio: value must be one of ...입니다. worker 제출 전에 입력을 검증해 로컬에서 빠르게 실패하게 하세요.
  • task failed(종료 상태) — 폴링/콜백이 status: "fail"과 함께 error.code, error.message를 반환합니다. 흔한 원인: worker가 가져올 수 없는 레퍼런스 이미지 URL, NSFW 검열 트리거, 또는 이미지 최단 변이 400px 임계값 미만.

자주 묻는 질문

9장을 초과하는 이미지를 참조할 수 있나요? 아닙니다. schema는 reference_image를 최대 9장으로 제한합니다. 더 많은 변화가 필요하면 플랫폼 외부에서 더 고밀도의 레퍼런스 이미지(그리드 이미지 또는 스토리보드 이미지)를 합성해 단일 입력으로 사용하세요.

프롬프트에 [Image N] 마커를 반드시 사용해야 하나요? 이 마커는 특정 레퍼런스 이미지를 가리키는 방법입니다. 프롬프트에서 이미지를 전혀 지정하지 않아도 유효하지만, 모델이 모든 레퍼런스 이미지를 자유롭게 섞게 됩니다. 다중 샷 일관성이 필요한 장면에서는 보통 원하는 결과가 아닙니다.

왜 503 nsfw_moderation_unavailable을 받나요? 검열 서비스로 인한 플랫폼의 일시적 오류이며 요청과는 무관합니다. 잠시 백오프 후 재시도하세요.

720p와 1080p 중 무엇을 써야 하나요? 1080p가 기본값이며 화질이 더 좋지만 비용이 높고 렌더링이 느립니다. 플레이어가 어차피 다운샘플링하는 세로형 소셜 미디어 클립에는 720p와 aspect_ratio: 9:16 조합이 보통 더 적절한 절충안입니다.

출력을 재현 가능하게 하려고 seed를 전달할 수 있나요? 현재는 불가능합니다. API는 seed를 알 수 없는 속성으로 간주해 거부합니다. 재현성이 필요하면 prompt, reference_image, aspect_ratio, resolution, duration을 정확히 고정하고 실행 간 차이는 어느 정도 감수하세요.

출력 URL은 어디에 저장되며 유효 기간은 얼마인가요? 짧은 TTL을 가진 서명 URL이며 응답에 expireAt이 포함됩니다. 작업 성공 후 즉시 다운로드하거나 영구 스토리지로 이전하세요.

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

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

API 문서 보기