HiAPI
ブログ

チュートリアル

hiapi 上で単一の統合APIを使ってマルチモデルAIアプリを構築する

Oct 8, 2026
hiapi 上で単一の統合APIを使ってマルチモデルAIアプリを構築する
モデル
seedance-2.5

構築するもの

ほとんどのチームは、1つのプロダクトのために最終的に3〜4社の異なるAIプロバイダーを呼び出すことになります。サムネイル用のテキスト→画像モデル、SNS向けショート動画用のテキスト→動画モデル、そして場合によってはナレーション用のTTSモデルです。各プロバイダーにはそれぞれのSDK、それぞれの認証方式、それぞれのポーリングロジックがあります。hiapi はこれをすべて1つのエンドポイントパターンに集約します。すなわち POST /v1/tasks でタスクを作成し、GET /v1/tasks/:id でタスクを照会する——プラットフォーム上のすべての生成モデルにこれが当てはまります。

本チュートリアルでは、現在オンラインで利用可能な3つの実在モデルを使ってこの統合パターンを示します——z-image(テキスト→画像)、qwen-image-2.0(テキスト→画像、スキーマが異なる)、そして seedance-2.5(テキスト→動画)です。これにより、APIが完全に同一な部分と、各モデルの input オブジェクトだけが異なる部分が明確になります。

前提条件

  • コンソールから取得した hiapi の APIキー
  • curl、またはJSONを送信できる任意のHTTPクライアント
  • SDKは不要——以下の各例はすべて純粋なHTTP呼び出しです

すべてのリクエストに1つのヘッダーが必要です:

Authorization: Bearer <your-api-key>

キーが無効または欠落している場合、どのモデルにも触れる前に401系のエラーが返ります:

{
  "error": {
    "code": "permission_denied",
    "message": "invalid api key",
    "request_id": "req_...",
    "type": "hiapi_error"
  }
}

キーなし・匿名モードは存在しません——すべての /v1/tasks 呼び出しには有効なベアラートークンが必要です。キーのローテーションとスコープについては認証を参照してください。

最小の動作例

1. タスクの作成

作成呼び出しの構造は常に同じです。1つの model id と、モデル固有の input オブジェクトです。以下は z-image で画像タスクを作成する例です:

curl -s -X POST https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "z-image",
    "input": {
      "prompt": "a minimalist workspace, soft morning light, isometric illustration",
      "aspect_ratio": "16:9"
    }
  }'

レスポンスはすぐに返ります——生成は非同期で行われます:

{
  "code": 200,
  "message": "ok",
  "data": {
    "taskId": "tk-hiapi-xxxxxxxxxxxx",
    "status": "queued"
  }
}

次に qwen-image-2.0 でも同じパターンを使います。同じエンドポイント、同じ外側の構造——異なるのは input だけです(このモデルは aspect_ratio ではなく size 文字列を受け取ります):

curl -s -X POST https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen-image-2.0",
    "input": {
      "prompt": "a minimalist workspace, soft morning light, isometric illustration",
      "size": "2048*2048"
    }
  }'

さらに seedance-2.5/text-to-video で動画タスクを作成します——外側の構造はやはり同じで、今度は duration と、より広い値域の aspect_ratio 列挙が加わります:

curl -s -X POST https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "seedance-2.5/text-to-video",
    "input": {
      "prompt": "a paper airplane gliding through a sunlit office, slow motion",
      "duration": 5,
      "aspect_ratio": "16:9"
    }
  }'

これが核心です:タスクの外側の構造は決して変わらず、変わるのは input だけです。 hiapi 上のすべてのモデルは、それぞれのモデルページで自身の input スキーマを記録しています——新しい統合を構築する前に必ずそのモデルのスキーマを確認してください。なぜなら aspect_ratio、size、duration といったフィールドはモデル間で互換性がないからです。

2. 結果のポーリング

curl -s https://api.hiapi.ai/v1/tasks/tk-hiapi-xxxxxxxxxxxx \
  -H "Authorization: Bearer $HIAPI_API_KEY"

status は queued → handling → archiving と遷移し、最終的に success または fail の終端状態になります。成功時、data.output は配列です——ほとんどの画像/動画モデルは1つのエントリを返しますが、1つのタスクが複数種類の出力を返すこともあります(例えば動画モデルが video と thumbnail を同時に返す場合など):

{
  "code": 200,
  "message": "ok",
  "data": {
    "taskId": "tk-hiapi-xxxxxxxxxxxx",
    "status": "success",
    "output": [
      { "type": "image", "url": "https://...", "expireAt": "2026-10-09T00:00:00Z" }
    ]
  }
}

expireAt は重要です——出力URLはデフォルトで一時的です。タスクが完了したらすぐにバイト内容をダウンロードしてください(または自身のストレージに再アップロードしてください)。ファイルをより長期間プラットフォームに保持させたい場合は、作成時に storage: "persistent" を設定するか、後からアップグレードしてください——ストレージを参照してください。

失敗時には、output の代わりに error オブジェクトが返ります:

{
  "code": 200,
  "message": "ok",
  "data": {
    "taskId": "tk-hiapi-xxxxxxxxxxxx",
    "status": "fail",
    "error": { "code": "...", "message": "..." }
  }
}

以下の最小限のPythonポーリングループは、上記のすべてのモデルに同様に適用できます:

import time
import requests

def run_task(model: str, input_payload: dict, api_key: str) -> list[dict]:
    headers = {"Authorization": f"Bearer {api_key}"}
    create = requests.post(
        "https://api.hiapi.ai/v1/tasks",
        headers=headers,
        json={"model": model, "input": input_payload},
    ).json()
    task_id = create["data"]["taskId"]

    time.sleep(3)  # initial wait before first poll
    while True:
        poll = requests.get(
            f"https://api.hiapi.ai/v1/tasks/{task_id}", headers=headers
        ).json()
        status = poll["data"]["status"]
        if status == "success":
            return poll["data"]["output"]
        if status == "fail":
            raise RuntimeError(poll["data"]["error"])
        time.sleep(4)  # 3-5s between polls; use 5-10s for video models

本番環境でのパターン

本番環境ではポーリングの代わりにコールバックを使用する

ポーリングはスクリプトやCLIツールには問題ありませんが、実際のトラフィック規模ではコールバックが推奨されます。タスク作成時に callback オブジェクトを渡すと、hiapi はループ内で GET /v1/tasks/:id を繰り返しリクエストさせる代わりに、終端状態の結果をあなたのエンドポイントにPOSTします。

{
  "model": "z-image",
  "input": { "prompt": "...", "aspect_ratio": "1:1" },
  "callback": { "url": "https://yourapp.com/webhooks/hiapi", "when": "final" }
}

when: "final" は、終端状態(success または fail)のときに1回だけPOSTを受け取ることを意味します——中間の handling/archiving 状態によるノイズはありません。コールバックを設定したのに何も受け取らない場合は、エンドポイントが公開アクセス可能で、素早く2xxを返しているか確認してください。コールバックが静かに失われる最も一般的な原因については、こちらの関連記事を参照してください。

冪等なリトライ

ネットワークの揺らぎは避けられません。タイムアウト後に保護策なしで POST /v1/tasks 呼び出しをリトライすると、重複したタスクを作成(および課金)してしまう可能性があります。Idempotency-Key ヘッダー(255バイト以内の任意の文字列、通常は論理リクエストごとに生成するUUID)を送信すると、同じキーでのリトライは新しいタスクを作成せず元のタスクを返します:

curl -s -X POST https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Idempotency-Key: 8f14e45f-ceea-467e-bd5f-...-9a2c" \
  -H "Content-Type: application/json" \
  -d '{"model": "z-image", "input": {"prompt": "...", "aspect_ratio": "1:1"}}'

モデルごとのバリデーションエラーの処理

各モデルが独自の input スキーマを持っているため、最もよくある失敗パターンは特定のフィールドに対する400です——あるモデルには有効で別のモデルには無効なフィールド、例えば size を期待するモデルに aspect_ratio を送信する場合などです:

{
  "code": 400,
  "data": null,
  "error_code": "INVALID_REQUEST",
  "message": "invalid input: aspect_ratio: value must be one of '1:1', '4:3', '3:4', '16:9', '9:16'"
}

マルチモデルルーターを構築する場合(ユーザーまたは独自のロジックが実行時にモデルを選択する場合)、共有のペイロード構造ではなく、モデル別の入力ビルダーの小さなレジストリを維持してください:

MODEL_INPUT_BUILDERS = {
    "z-image": lambda prompt, **kw: {
        "prompt": prompt, "aspect_ratio": kw.get("aspect_ratio", "1:1")
    },
    "qwen-image-2.0": lambda prompt, **kw: {
        "prompt": prompt, "size": kw.get("size", "2048*2048")
    },
    "seedance-2.5/text-to-video": lambda prompt, **kw: {
        "prompt": prompt,
        "duration": kw.get("duration", 5),
        "aspect_ratio": kw.get("aspect_ratio", "16:9"),
    },
}

def build_task_payload(model: str, prompt: str, **kw) -> dict:
    return {"model": model, "input": MODEL_INPUT_BUILDERS[model](prompt, **kw)}

これにより、各モデルの特殊性が呼び出し箇所ごとに散らばるのではなく、1か所に集約されます。存在しない、または別アカウントの taskId は通常の404を返します:

{ "code": 404, "message": "task not found", "data": null }

また一時的な 503 はユーザーに直接見せるのではなく、指数バックオフでリトライすべきです——レート制限と容量制限についてはレート制限に記載されています。

関連記事

よくある質問

モデルごとに異なるSDKやAPIキーが必要ですか? いいえ。1つのAPIキー、1つのエンドポイントパターン(/v1/tasks)が、プラットフォーム上のすべてのモデルに適用されます。変わるのはリクエストボディ内の input オブジェクトだけです。

タスクを作成した直後のレスポンスに output がないのはなぜですか? 生成は非同期です。作成呼び出しは taskId と status: "queued" のみを返します——status が success または fail に達するまで、GET /v1/tasks/:id をポーリングする(またはコールバックを使用する)必要があります。

素早くテストするために認証なしでAPIを呼び出せますか? いいえ——すべての /v1/tasks 呼び出しには有効な Authorization: Bearer キーが必要です。本番呼び出しにキーなし・デモモードはありません。まずコンソールからキーを生成してください。

出力URLが期限切れになった後にポーリングするとどうなりますか? タスクレコード自体は保持されますが、非永続化タスクの output[].url は expireAt 以降は解決できなくなります。成功時にすぐファイルをダウンロードするか、長期保持が必要な場合は作成時に storage: "persistent" を設定してください。

あるモデルがどのフィールドを受け入れるかはどうやって知ればよいですか? モデル配下のそのモデルのページを確認してください——それぞれが正確な input スキーマを記録しています。aspect_ratio、size、duration がモデル間で共通だと仮定しないでください。上記のように、z-image と qwen-image-2.0 は既に画像の形状の表現方法が一致していません。

永続的なAIアーティファクトで開発を始める

APIキーを取得して、タスクベースの1つのAPIで画像・動画・音声モデルを呼び出せます。

APIドキュメントを読む