튜토리얼
HappyHorse 1.1 레퍼런스 기반 비디오 생성 API: 바로 실행 가능한 curl + Python 튜토리얼
HappyHorse 1.1 R2V는 최대 아홉 장의 레퍼런스 이미지와 하나의 텍스트 프롬프트를 짧은 비디오로 변환하며, 여러 샷에 걸쳐 피사체, 의상, 장면 스타일을 일관되게 유지합니다. HiAPI에서 이 모델은 통합 비동기 작업 엔드포인트를 통해 제공되며, 모델 ID는 happyhorse-1.1/reference-to-video입니다. 이 튜토리얼은 먼저 실행 가능한 최소 요청을 보여주고, 이어서 콜백을 사용하는 프로덕션급 버전을 다룹니다.
목표 및 사전 조건
다음 작업을 완료하게 됩니다:
- 1-9장의 레퍼런스 이미지를 API가 접근할 수 있는 HTTPS URL로 업로드(또는 이미 호스팅된 상태로 둠)합니다.
- 이 URL들을 참조하는 작업을
https://api.hiapi.ai/v1/tasks에 POST합니다. - 작업이
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",
},
}
수신 엔드포인트에 두 가지를 반드시 연결해야 합니다:
taskId기준 멱등 처리.success와fail은 모두 종료 상태이며, HiAPI의 재시도(또는 자체 재시도 계층에서 발생한 중복)는 동일한taskId를 다시 전달합니다. 어떤 부수 효과(S3 복사, Slack 메시지, 과금 이벤트)를 일으키기 전에 중복을 제거하세요.- 폴백 대조 폴링. 엔드포인트가 다운되면 콜백을 유실합니다. 최악의 생성 시간을 초과해
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이 포함됩니다. 작업 성공 후 즉시 다운로드하거나 영구 스토리지로 이전하세요.
관련 글
모든 아티클 보기 →-
튜토리얼
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 튜토리얼
요청 우선 실전 데모: 프롬프트에서 비디오 클립까지의 호출 구성, 요청 제출, 완료 상태 폴링 및 결과 다운로드