HiAPI
블로그

튜토리얼

Happyhorse 1.1 텍스트→비디오 API: 실행 가능한 curl 및 Python 튜토리얼

Jun 26, 2026
Happyhorse 1.1 텍스트→비디오 API: 실행 가능한 curl 및 Python 튜토리얼

이 페이지에서는 happyhorse-1.1/text-to-video로 비디오를 생성하는 최소 요청을 끝까지 완료하고, 각 입력 필드가 실제로 어떤 값을 받는지, 결과를 기다리는 두 가지 방식(폴링과 콜백)을 설명합니다. 아래의 모든 내용은 실제 운영 API에서 테스트되었으며, 요청 구조와 오류 메시지는 원문 그대로 옮겼습니다.

무엇을 만들게 될까요

텍스트 프롬프트를 받아 다운로드 가능한 MP4 URL을 반환하는 함수 하나입니다. 모델은 happyhorse-1.1/text-to-video이며, 통합 /v1/tasks 엔드포인트를 통해 제공됩니다. 작업을 제출하면 즉시 taskId를 받고, 이후 완료될 때까지 폴링하거나 hiapi가 최종 결과를 웹훅으로 POST하도록 할 수 있습니다.

사전 준비 사항:

  • hiapi API key. 신규 계정에는 1달러 상당의 무료 크레딧이 제공됩니다——API Keys 페이지에서 발급받으세요.
  • HTTPS 요청을 보낼 수 있는 수단. SDK 설치가 필요하지 않습니다.

key를 클라이언트 코드에 넣거나 git에 커밋하지 마세요. 환경 변수에서 읽으세요:

export HIAPI_API_KEY="sk-..."

최소 동작 요청

유일한 필수 입력 필드는 prompt입니다. 나머지 모든 필드에는 기본값이 있습니다.

curl

curl -X POST https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "happyhorse-1.1/text-to-video",
    "input": {
      "prompt": "a calm ocean wave at sunset, slow motion, golden hour"
    }
  }'

응답:

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

이 taskId가 해당 작업을 조작하는 자격 증명입니다. 잘 보관하세요.

Python (표준 라이브러리만 사용)

import os
import urllib.request
import json

API_KEY = os.environ["HIAPI_API_KEY"]

def submit_task(prompt: str, **input_overrides) -> str:
    body = {
        "model": "happyhorse-1.1/text-to-video",
        "input": {"prompt": prompt, **input_overrides},
    }
    req = urllib.request.Request(
        "https://api.hiapi.ai/v1/tasks",
        method="POST",
        headers={
            "Authorization": f"Bearer {API_KEY}",
            "Content-Type": "application/json",
        },
        data=json.dumps(body).encode("utf-8"),
    )
    with urllib.request.urlopen(req) as resp:
        payload = json.loads(resp.read())
    if payload.get("code") != 200:
        raise RuntimeError(payload)
    return payload["data"]["taskId"]

task_id = submit_task("a calm ocean wave at sunset, slow motion, golden hour")
print(task_id)  # e.g. tk-hiapi-01KW23VDG2EZ20J7RQH8S67MNC

전체 파라미터 표

파라미터 구조는 작지만 엄격합니다——알 수 없는 필드는 조용히 무시되지 않고 400으로 거부됩니다. 다음은 API가 실제로 input 내에서 받는 필드로, 검증 오류 메시지에서 직접 가져온 것입니다:

필드 타입 필수 여부 허용 값 기본 동작
prompt string 예 장면을 설명하는 비어 있지 않은 텍스트 —
aspect_ratio string 아니오 16:9, 9:16, 3:4, 4:3, 4:5, 5:4, 1:1, 9:21, 21:9 모델 기본값
resolution string 아니오 720p, 1080p 모델 기본값
duration integer 아니오 3에서 15 (초) 모델 기본값

당연해 보이지만 현재 허용되지 않는 필드: size, audio, seed, negative_prompt. 이 중 하나라도 보내면 additional properties '<field>' not allowed가 반환됩니다. 다른 hiapi 모델을 사용해 본 적이 있다면——qwen-image-2.0은 size를, z-image는 aspect_ratio를 사용합니다——happyhorse-1.1/text-to-video는 명시적 크기가 아니라 화면 비율 계열에 속한다는 점에 유의하세요.

지원되는 모든 파라미터를 사용한 요청:

curl -X POST https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "happyhorse-1.1/text-to-video",
    "input": {
      "prompt": "a vintage paper plane gliding above neon Tokyo at night, cinematic, 24fps",
      "aspect_ratio": "16:9",
      "resolution": "1080p",
      "duration": 8
    }
  }'

비용과 길이에 대하여

이 모델은 생성된 비디오의 초 단위로 과금됩니다——현재 요율은 가격 페이지를 참조하세요. duration이 비용 지렛대이고 resolution은 아닙니다. 5초에서 15초로 늘리면 비용이 세 배가 되지만, 이 글을 작성하는 시점에는 720p에서 1080p로 바꾸는 것은 비용이 들지 않습니다. 개발 중에는 기본적으로 duration: 3과 낮은 해상도를 사용하고, 최종 확정된 프롬프트에서만 1080p로 올리세요.

결과 얻기: 폴링

출력을 가져오는 가장 간단한 방법은 status가 종료 상태에 도달할 때까지 id로 작업을 GET하는 것입니다.

import time
import urllib.request
import json

def get_task(task_id: str) -> dict:
    req = urllib.request.Request(
        f"https://api.hiapi.ai/v1/tasks/{task_id}",
        headers={"Authorization": f"Bearer {API_KEY}"},
    )
    with urllib.request.urlopen(req) as resp:
        return json.loads(resp.read())["data"]

def wait_for_task(task_id: str, timeout_s: int = 600, poll_interval_s: int = 10) -> dict:
    deadline = time.time() + timeout_s
    while time.time() < deadline:
        task = get_task(task_id)
        status = task["status"]
        if status == "success":
            return task
        if status == "fail":
            raise RuntimeError(f"task failed: {task}")
        time.sleep(poll_interval_s)
    raise TimeoutError(f"task {task_id} still {status} after {timeout_s}s")

task = wait_for_task(submit_task("a quiet coffee shop on a rainy afternoon"))
video_url = task["output"][0]["url"]
print(video_url)

몇 가지 중요한 세부 사항:

  • status는 queued → handling → archiving → success(또는 fail) 순으로 진행됩니다. success 또는 fail 이전의 어떤 상태도 종료 상태로 간주해서는 안 됩니다.
  • 생성 시간은 duration과 현재 큐 깊이에 따라 늘어납니다. 5초 720p 작업은 보통 몇 분 내에 완료되며, 1080p 15초는 더 많은 시간을 확보해야 합니다.
  • output[].url은 임시 저장소에 보관됩니다. 바이트를 즉시 다운로드하여 직접 재호스팅하세요. URL만 저장해 최종 사용자에게 제공하지 마세요——만료됩니다.
import urllib.request

def download(video_url: str, out_path: str) -> None:
    with urllib.request.urlopen(video_url) as resp, open(out_path, "wb") as f:
        f.write(resp.read())

download(video_url, "ocean.mp4")

결과 얻기: 콜백 (프로덕션 모드)

폴링은 스크립트와 노트북에 적합하지만, 실제 백엔드에서는 워커 하나가 비디오 하나 때문에 몇 분씩 점유되는 것을 원하지 않을 것입니다. 제출 시 callback 객체를 전달하면 hiapi가 완료된 작업 본문을 정확히 한 번(최선 노력) 여러분의 URL로 POST합니다.

curl -X POST https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "happyhorse-1.1/text-to-video",
    "input": {
      "prompt": "an astronaut planting a flag on a glass beach",
      "aspect_ratio": "16:9",
      "resolution": "1080p",
      "duration": 6
    },
    "callback": {
      "url": "https://example.com/hooks/hiapi",
      "when": "final"
    }
  }'

현재는 when: "final"만 지원됩니다——다른 값을 보내면 다음과 같은 결과가 나옵니다:

{ "code": 400, "error_code": "INVALID_REQUEST",
  "message": "invalid callback.when: only 'final' is supported" }

견고한 수신기 설계

세 가지 규칙이 콜백 경로의 안전을 보장합니다:

  1. 멱등성. taskId별로 영속화하세요. 같은 콜백을 두 번 받게 되면——재시도 때문에 결국 반드시 그렇습니다——중복 처리는 무동작(no-op)이어야 합니다.
  2. 확인과 처리를 분리하세요. payload를 큐에 넣는 즉시 웹훅에서 200 OK를 반환하세요. 실제 다운로드 / 재업로드 / 데이터베이스 쓰기는 백그라운드 작업에서 수행하세요. 느린 웹훅은 타임아웃과 중복 전송을 초래합니다.
  3. 폴링으로 폴백하세요. 제출한 모든 taskId를 created_at과 함께 자체 데이터베이스에 저장하세요. 약 30분이 지나도 아직 종료 상태가 아닌 행에 대해 /v1/tasks/{id}를 폴링하는 정리기(sweeper)를 실행하세요. 콜백은 빠른 경로일 뿐 유일한 경로가 아닙니다.

최소한의 FastAPI 수신기:

from fastapi import FastAPI, Request

app = FastAPI()

@app.post("/hooks/hiapi")
async def hiapi_callback(req: Request):
    body = await req.json()
    task = body.get("data", body)  # callback wraps the same shape as GET
    task_id = task["taskId"]
    if task["status"] == "success":
        url = task["output"][0]["url"]
        # enqueue, don't download here
        await queue.put({"task_id": task_id, "url": url})
    return {"ok": True}

실제로 마주치게 될 오류

오류 엔벨로프의 형태는 항상 일관됩니다——{code, error_code, message, data}——따라서 핸들러는 하나만 작성하면 됩니다. 이 모델에 특별히 처리할 만한 몇 가지:

HTTP error_code 발생 조건 해결 방법
400 INVALID_REQUEST 알 수 없는 필드, 잘못된 열거 값, 3–15를 벗어난 duration message를 확인하세요. 필드를 지적합니다——예: aspect_ratio: value must be one of '16:9'...
401 — key 누락 또는 오류, 또는 permission_denied Authorization: Bearer sk-... 헤더를 다시 확인하세요
402 — 예상 비용을 충당할 수 없는 잔액 hiapi.ai/en/pricing에서 충전 후 재시도하세요
429 — 엔드포인트를 과도하게 호출 중 지터를 적용한 백오프로 재시도하세요

실제 검증 실패는 다음과 같습니다——테스트 픽스처로 복사해 사용하세요:

{
  "code": 400,
  "data": null,
  "error_code": "INVALID_REQUEST",
  "message": "invalid input: aspect_ratio: value must be one of '16:9', '9:16', '3:4', '4:3', '4:5', '5:4', '1:1', '9:21', '21:9'; resolution: value must be one of '720p', '1080p'; duration: maximum: got 99, want 15"
}

모든 필드가 하나의 응답에 보고된다는 점에 유의하세요. 가능하면 로컬에서 검증하고, 불가능할 때는 이 응답에 의존하세요.

hiapi의 관련 콘텐츠

자주 묻는 질문

happyhorse-1.1/text-to-video는 오디오를 생성하나요? 작업 API를 통해서는 아닙니다——요청 구조에 현재 audio 필드가 없으며, 이를 보내면 additional properties 'audio' not allowed가 반환됩니다. 출력은 무음 MP4입니다. 다운스트림에서 배경 음악을 입히세요.

세로 / 숏폼 비디오에는 어떤 화면 비율을 사용할 수 있나요? TikTok 및 Reels 스타일 출력에는 9:16이 표준 선택입니다. API는 더 좁은 화면이 필요하다면 4:5(Instagram 피드 세로)와 9:21(초세로)도 허용합니다.

최소 및 최대 길이는 얼마인가요? duration은 3에서 15초(양 끝 포함)의 정수입니다. 0을 보내면 duration: minimum: got 0, want 3이 표시되고, 99를 보내면 duration: maximum: got 99, want 15가 표시됩니다. 필요한 움직임을 담을 수 있는 최소 값을 선택하세요——길이가 곧 지불하는 부분입니다.

재현 가능한 출력을 위해 seed를 전달할 수 있나요? 이 모델은 현재 지원하지 않습니다. {"input": {"prompt": "...", "seed": 42}}를 요청하면 실패하고 additional properties 'seed' not allowed가 반환됩니다. 재현성이 중요하다면 전체 요청 본문을 기록하고 동일한 프롬프트로 재시도하세요. 유사하지만——픽셀 단위로 동일하지는 않은——결과를 얻게 됩니다.

negative_prompt가 지원되나요? 지원하지 않습니다. prompt 자체에 제약을 추가하여 출력을 유도하세요(예: "...clean composition, no text, no watermarks").

출력 URL은 얼마나 오래 유효한가요? 기본 요금제에서 작업의 storage 필드는 temp이며, output[].url은 단기 유효 서명 링크입니다. 작업이 success에 도달하면 즉시 파일을 다운로드하여 재호스팅하세요. 원본 URL을 저장해 나중에 제공하지 마세요.

개발 중에 비용을 어떻게 통제하나요? 두 가지 절약 습관: 프롬프트를 확정하기 전까지 duration: 3을 유지하고, 스토리보드가 확인되기 전까지 resolution: "720p"로 제출하세요. 최종 렌더링에서만 1080p와 더 긴 길이로 올리세요.

폴링과 콜백——어느 것을 사용해야 하나요? 둘 다 사용하되 이 순서로. 먼저 폴링하세요. 움직이는 부품이 하나 적기 때문입니다. wait_for_task에서 워커를 막아둘 수 없는 백엔드를 처음 갖게 되면 콜백으로 전환하세요. 어쨌든 폴링을 폴백 정리기로 유지하세요. 콜백은 최선 노력이며 전달 보장이 아닙니다.

크레딧이 있는데 호출하면 402가 반환되는 이유는 무엇인가요? API는 작업 비용 상한——길이 × 초당 가격——을 추정하고 사전에 예약합니다. 잔액이 그 추정치를 충당할 수 없으면 즉시 402를 반환합니다. 충전하고 재시도하세요. 성공하면 실제 비용만 차감됩니다.

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

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

API 문서 보기