HiAPI
블로그

튜토리얼

Happyhorse 이미지-투-비디오 API: 사용 가능한 curl 및 Python 예제(hiapi)

Jun 27, 2026
Happyhorse 이미지-투-비디오 API: 사용 가능한 curl 및 Python 예제(hiapi)

자신의 코드에서 정지 이미지를 자연스러운 짧은 동영상으로 변환하고 싶다면, hiapi의 happyhorse-1.1/image-to-video 모델이 바로 그 역할을 합니다: 이미지 URL과 선택적 모션 프롬프트를 전달하면 몇 초 만에 720p 또는 1080p MP4를 반환합니다. 이 가이드는 가장 짧은 실행 경로입니다—실제 요청 본문, 실제 오류 메시지, curl과 Python 모두—필드 이름 때문에 헤매지 않고 오늘 바로 배포할 수 있도록.

시작하기 전 준비 사항

  • hiapi API 키. hiapi 콘솔에서 발급받으세요—sk-... 형태이며 Authorization: Bearer ... 요청 헤더에 넣습니다.
  • 접근 가능한 이미지 URL(공개 HTTPS). 로컬 파일은 안 됩니다; 모델이 해당 URL을 직접 가져갑니다.
  • 텍스트로 표현한 모션 의도(선택이지만 권장). 동사가 명확할수록 모션 효과가 좋습니다.

모델은 동영상 초 단위로 과금되며, 가격은 hiapi 가격 페이지에 나와 있습니다; 나머지 모델 카탈로그는 hiapi models—배치를 늘리기 전에 두 곳을 모두 확인하세요.

최소 실행 요청(curl)

hiapi의 모든 동영상은 비동기 /v1/tasks 엔드포인트를 사용합니다: 먼저 POST로 생성 요청을 보내 taskId를 받고, 그다음 GET /v1/tasks/{taskId}를 폴링하거나 완료 시 콜백을 받습니다.

작업 생성:

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/image-to-video",
    "input": {
      "image_urls": ["https://example.com/your-still.jpg"],
      "prompt": "the cat slowly turns its head toward the camera and blinks",
      "resolution": "1080p",
      "duration": 5
    }
  }'

성공 응답은 이렇게 생겼습니다:

{
  "code": 200,
  "data": { "taskId": "tk-hiapi-01KW3D..." },
  "message": "success"
}

이제 결과를 폴링하세요(5초마다 충분—대부분의 클립은 2분 내에 완료됩니다):

curl "https://api.hiapi.ai/v1/tasks/tk-hiapi-01KW3D..." \
  -H "Authorization: Bearer sk-your-key"

실행 중에는 "status": "handling"이 표시됩니다. 완료되면 다음과 같이 반환됩니다:

{
  "code": 200,
  "data": {
    "status": "success",
    "model": "happyhorse-1.1/image-to-video",
    "output": [
      {
        "type": "video",
        "url": "https://temp.hiapi.ai/.../01KW....mp4",
        "expireAt": 1783131245
      }
    ],
    "taskId": "tk-hiapi-01KW3D..."
  }
}

그 url이 바로 다운로드할 대상입니다—그리고 즉시 다운로드해야 합니다. expireAt은 임시 저장소가 언제 삭제할지 표시하는 Unix 타임스탬프이기 때문입니다.

Python: 성공할 때까지 폴링

완전히 실행 가능한 스크립트입니다—키와 이미지 URL만 채워 넣으면 됩니다.

import os
import time
import requests

API = "https://api.hiapi.ai/v1/tasks"
TOKEN = os.environ["HIAPI_KEY"]  # sk-...

def create_video(image_url: str, prompt: str, duration: int = 5,
                 resolution: str = "1080p") -> str:
    """Submit an image-to-video task. Returns the taskId."""
    r = requests.post(
        API,
        headers={"Authorization": f"Bearer {TOKEN}",
                 "Content-Type": "application/json"},
        json={
            "model": "happyhorse-1.1/image-to-video",
            "input": {
                "image_urls": [image_url],          # exactly one URL
                "prompt": prompt,                   # optional
                "resolution": resolution,           # "720p" or "1080p"
                "duration": duration,               # int, 3..15
            },
        },
        timeout=60,
    )
    body = r.json()
    if body.get("code") != 200:
        raise RuntimeError(f"create failed: {body}")
    return body["data"]["taskId"]

def wait_for_video(task_id: str, timeout_s: int = 600,
                   poll_s: int = 5) -> str:
    """Block until the task succeeds, fails, or times out. Returns the MP4 URL."""
    deadline = time.time() + timeout_s
    while time.time() < deadline:
        r = requests.get(
            f"{API}/{task_id}",
            headers={"Authorization": f"Bearer {TOKEN}"},
            timeout=30,
        )
        task = r.json().get("data") or {}
        status = task.get("status")
        if status == "success":
            return task["output"][0]["url"]
        if status == "fail":
            raise RuntimeError(f"task failed: {task.get('error')}")
        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=120) 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 = create_video(
        image_url="https://example.com/your-still.jpg",
        prompt="the dancer spins once and ends in a pose",
        duration=5,
        resolution="1080p",
    )
    print("submitted:", task_id)
    mp4_url = wait_for_video(task_id)
    download(mp4_url, "out.mp4")
    print("saved out.mp4")

전체가 약 50줄이며, 이것이 프로덕션에서 사용할 수 있는 전부입니다—SDK도, 백그라운드 큐도, 스트리밍 프로토콜도 없습니다. HIAPI_KEY=sk-... python video.py로 실행하세요.

프로덕션 환경: 폴링 대신 콜백 사용

폴링은 일회성 작업과 테스트에는 문제없습니다. 서버 부하가 있는 환경에서는 플랫폼이 클립이 준비되면 콜백하도록 하는 것이 좋습니다—루프가 사라지고 추가 작업 없이 확장되며, 요청 스트림이 즉시 반환됩니다.

요청 본문의 루트 레벨(input 내부가 아님)에 callback 객체를 추가하세요:

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/image-to-video",
    "input": {
      "image_urls": ["https://example.com/still.jpg"],
      "prompt": "slow zoom out from the subject",
      "resolution": "1080p",
      "duration": 6
    },
    "callback": {
      "url": "https://your-app.example.com/webhooks/hiapi",
      "when": "final"
    }
  }'

작업이 종료 상태에 도달하면 hiapi는 폴링으로 얻었을 작업 객체(status, output[].url, error 등)를 당신의 URL로 POST합니다. when: "final"은 성공 또는 실패 콜백만 받는다는 뜻입니다—진행 상황 노이즈는 없습니다.

실제 디버깅 시간을 절약해 주는 몇 가지 규칙:

  • webhook을 멱등하게 만드세요. 동일한 taskId가 두 번 도착할 수 있습니다(네트워크 재시도는 실제로 발생합니다). 처리 전에 데이터베이스에서 해당 작업을 먼저 확인하세요.
  • 처리 함수 내에서 MP4를 재호스팅하세요. hiapi가 주는 URL은 expireAt 필드가 있는 임시 저장소를 가리킵니다; 처음 받았을 때 자신의 버킷으로 스트리밍해 저장하지 않으면 잃게 됩니다.
  • 빠르게 확인 응답하세요. 빠르게 2xx를 반환한 후 무거운 작업은 비동기로 처리하세요. 느린 webhook은 재시도 위험이 있습니다.
  • 폴링을 폴백으로 사용하세요. webhook이 유실될 경우를 대비해 taskId를 지연 작업에 넣어 10분 후 폴링하세요. 이 문제가 계속 발생하면 hiapi 작업 콜백이 발생하지 않을 수 있는 원인에서 일반적인 원인을 확인하세요.

만나게 될 오류 메시지

다음은 API가 반환하는 정확한 문자열입니다; 코드에서 이를 매칭하는 것이 추측보다 안정적입니다.

상황 HTTP 응답 본문 형태
잘못되었거나 폐기된 키 401 {"error":{"code":"permission_denied","message":"This API key cannot use the selected model...","type":"hiapi_error"}}
필수 필드 누락 400 {"code":400,"error_code":"INVALID_REQUEST","message":"invalid input: image_urls: missing required field \"image_urls\""}
resolution 유효하지 않음 400 ... resolution: value must be one of '720p', '1080p'
길이가 범위를 벗어남 400 ... duration: minimum: got 2, want 3 / duration: maximum: got 30, want 15
image_urls 길이 오류 400 ... image_urls: maxItems: got 2, want 1(또는 minItems: got 0, want 1)
루트 레벨 또는 input 내 알 수 없는 필드 400 ... additional properties 'identity_preserving' not allowed
제출 후 작업 자체 실패 200(작업 GET) 및 data.status == "fail" 그리고 data.error.{code,message}

인증 및 검증 오류는 재시도하지 말아야 함을 의미합니다; 그 외 모든 경우(플랫폼의 5xx, 폴링 중 네트워크 타임아웃, 재시도 가능한 코드가 있는 일시적 task fail)는 약간의 백오프 재시도가 적합합니다.

출력 튜닝

실제로 영향을 주는 손잡이는 네 개입니다—그 외(seed, aspect_ratio, fps, cfg_scale, negative_prompt)는 현재 알 수 없는 필드로 거부됩니다. 이 집합 안에 머무르세요:

  • image_urls—하나의 HTTPS URL. 출력의 종횡비와 구도는 이 이미지를 따릅니다.
  • prompt—간결한 모션 설명("she smiles and tilts her head"). 영화적이고 동사 중심의 프롬프트가 형용사 나열보다 좋습니다.
  • resolution—"720p"는 프로토타이핑과 썸네일용, "1080p"는 최종 납품용.
  • duration—정수 초, 3에서 15까지. 더 긴 클립은 더 많은 시간과 비용이 듭니다; 스토리를 전달하는 가장 짧은 길이를 선택하세요.

시작 프레임과 종료 프레임이 필요한 프로젝트는 happyhorse-1.1 reference-to-video를 참고하세요. 소스 이미지가 필요 없는 순수 텍스트-투-비디오는 happyhorse-1.1 text-to-video가 자매편입니다. 나머지 내용은 hiapi models catalogue 또는 블로그의 레시피 섹션에서 둘러보세요.

FAQ

두 개의 이미지를 전달해 첫 프레임과 마지막 프레임을 제어할 수 있나요? happyhorse-1.1/image-to-video에서는 안 됩니다—image_urls는 최대 한 개 요소(maxItems: 1)입니다. 이것이 필요하면 happyhorse-1.1/reference-to-video를 사용하거나 happyhorse-1.1 reference-to-video로 구축하세요.

실제로 받는 MP4의 해상도는 무엇인가요? 출력은 입력 이미지의 종횡비와 설정한 resolution 플래그를 따릅니다. "1080p"는 둘 중 더 높은 것을 제공합니다; "720p"는 테스트에 더 빠르고 저렴합니다.

다른 모델에서는 키가 작동하는데 401 permission_denied가 나는 이유는 무엇인가요? 일부 모델은 키별로 제한됩니다. hiapi 콘솔에서 모델 페이지를 확인하세요—happyhorse-1.1/image-to-video가 허용 목록에 없다면 새 키를 생성하거나 오류의 request_id를 가지고 지원팀에 문의하세요.

스트리밍/SSE 응답이 있나요? 없습니다. 모든 동영상 모델은 이 문서의 비동기 작업 패턴을 사용합니다. 서버를 제어하는지 여부에 따라 폴링 또는 콜백을 선택하세요.

클립당 실제 비용은 어디서 확인하나요? hiapi 가격 페이지에서. 비용은 동영상 초 단위로 계산되므로 전달하는 duration이 곧 비용을 결정합니다—개발/테스트 클립은 duration: 3으로 유지해 예산을 아끼세요.

출력 URL이 하루 후에 작동하지 않습니다. 예상된 동작입니다—해당 URL은 임시 저장소이며 응답에 expireAt Unix 타임스탬프가 포함됩니다. 항상 작업이 성공하는 순간 MP4를 다운로드하세요(또는 자신의 CDN을 통해 스트리밍하세요).

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

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

API 문서 보기