HiAPI
ブログ

チュートリアル

HiAPI タスク出力 URL の期限切れ(expireAt):失効前にダウンロードして転送保存する

終態の output[].url には expireAt が付き、期限切れで 404——どう判断し、どう間に合うようにダウンロードし、期限切れ後にどう復旧するか。

Oct 22, 2026
HiAPI タスク出力 URL の期限切れ(expireAt):失効前にダウンロードして転送保存する

POST /v1/tasks で画像や動画を生成し、タスク完了後に data.output[0].url から temp.hiapi.ai のリンクを取得した——その時は開けたのに、数日後にアクセスすると HTML の「Not Found」ページになってしまう:

HTTP/2 404
content-type: text/html

返ってくるのは画像/動画のバイト列ではなく、Cloudflare の 404 ページだ。原因はただ一つ:タスク出力 URL は有効期限付きの一時リンクであり、終態の expireAt(Unix タイムスタンプ)に達すると temp.hiapi.ai 上のオブジェクトが削除され、ホットリンクが失効する。

まず終態のフィールドを理解する

タスクの終態(GET /v1/tasks/{taskId})が成功したとき、主要なフィールドは次のようになる:

{
  "code": 200,
  "data": {
    "taskId": "tk-hiapi-xxxxxxxx",
    "status": "success",
    "storage": "temp",
    "created": 1791828181,
    "completed": 1791828201,
    "output": [
      {
        "artifactId": "...",
        "type": "image",
        "url": "https://temp.hiapi.ai/....png",
        "expireAt": 1792432981
      }
    ]
  },
  "message": "success"
}
  • data.status == "success" が終態成功(失敗は "fail"、キュー待ち/処理中は queued / handling / archiving)。
  • data.output は配列で、各要素がそれぞれの url と expireAt を持つ。
  • expireAt は Unix 整数タイムスタンプ(ISO 文字列ではない)。期限切れかどうかはこれだけを見る:now >= expireAt で期限切れ。
  • data.storage の値は "temp"——これ自体がヒントで、一時ストレージであり、長期参照用ではない。

有効期間はどれくらいか?実測サンプルでは expireAt - completed = 604800 秒(ちょうど 7 日)。ただし「7 日」をコードにハードコードしないこと——各出力自身の expireAt を基準にすべきで、モデル/ストレージ戦略によって異なる可能性がある。

よくある原因(発生確率順)

  1. ホットリンクをデータベースに長期参照として保存した:保存時に temp.hiapi.ai/... という一時 URL を保存したため、expireAt を過ぎると一斉に 404 になる。これが最もよくある落とし穴。
  2. 終態を取得してもすぐにダウンロードしなかった:タスク完了後、かなり時間が経って(TTL を超えて)からバイト列を取りに行った。
  3. 再ポーリングでリンクが「更新」されると思っていた:同じ taskId に対してもう一度 GET しても、返ってくるのは同じ url と同じ expireAt——メタデータは永続だが、オブジェクト自体は期限切れ後に再生成されず、再 GET しても新しいリンクは得られない。
  4. 404 をタスク失敗と誤解した:タスクは実はとっくに success で、出力アセットが期限切れになっただけであり、これは別の話。

修正手順

  1. 終態になったら即座にバイト列をダウンロードする。 data.status == "success" をポーリングで検知したら、すぐに各 output[].url に対して GET を発行してバイナリを取得する。後回しにしない、遅延させない。
  2. 自分のオブジェクトストレージに転送保存する(R2 / S3 / ローカルディスク)。自分自身の永続 URL に置き換える。
  3. データベースには自分自身の永続 URL だけを保存する。temp.hiapi.ai のホットリンクは絶対に保存しない。
  4. ダウンロード前に期限切れでないことを検証する:now < expireAt のときだけダウンロードし、期限切れなら再実行ロジックに回す。
  5. 期限切れになったらタスクを再実行するしかない。 「taskId で新しいリンクに交換する」などということはない——同じ taskId に再 GET しても、返ってくるのは失効した url のまま。ダウンロード可能な結果を再取得するには、タスクをもう一度送信するしかない(再度課金される)。
  6. バッチタスクではダウンロードのタイミングを制御する:バッチ内で最初に完了したものの expireAt も最も早く切れる。完了順にすぐダウンロードし、バッチ全体が終わるまでまとめてダウンロードしないこと。そうしないと早く完了したものが期限切れ間近になっている可能性がある。

最小検証例

キーが通ること、タスク API が結果を返せること、そして expireAt を実際に確認する。model id は素の id を使う(z-image、/text-to-image などのサフィックスを付けると 400 になる):

# 1. タスクを送信
curl -s https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "z-image",
    "input": { "prompt": "a red apple on a wooden table" }
  }'
# → {"code":200,"data":{"taskId":"tk-hiapi-..."},"message":"success"}

# 2. 前ステップの taskId で終態をポーリングし、url と expireAt を読み取る
curl -s https://api.hiapi.ai/v1/tasks/tk-hiapi-xxxx \
  -H "Authorization: Bearer $HIAPI_API_KEY"
# data.status が "success" になったら、data.output[0].url / .expireAt が得られる

認証に失敗すると HTTP 401 + {"error":{"code":"permission_denied","type":"hiapi_error","request_id":"..."}} が返る——これはキーの問題であり、期限切れの問題ではない。タスクが見つからない場合は {"code":404,"data":null,"message":"task not found"}——通常は taskId の書き間違いか、環境の違うキーを使っている。

関連リンク

FAQ

問:expireAt は具体的にどんな形式? 答:Unix 整数タイムスタンプ(秒)で、例えば 1792432981。ISO 文字列ではない。now >= expireAt で期限切れを判断すればよい。

問:リンクが期限切れになった後、タスクをもう一度 GET してリンクを「延長」できる? 答:できない。同じ taskId に再 GET しても、返ってくるのは元の url と元の expireAt のままで、オブジェクトはすでに削除されている。ダウンロード可能な結果を再取得するには、タスクを再実行するしかない(再度課金される)。

問:期限切れ後のアクセスで返るのは 403 それとも 404? 答:HTTP 404 で、body は text/html の Cloudflare「Not Found」ページ。画像バイト列でもなければ XML の AccessDenied でもない。ダウンロードロジックが 200 しか受け付けない場合は、「非 200 は一律失敗」として扱うように。

問:有効期間は固定の 7 日? 答:実測サンプルでは 7 日(604800 秒)だが、各出力自身の expireAt を基準にすべきで、日数をコードにハードコードしないこと。

問:バッチタスクはどうダウンロードするのが最も安定? 答:完了順に、先に success になったものからバイト列をダウンロードする。最初に完了した出力の expireAt も最も早く切れるため、最後まで引っ張ってまとめてダウンロードすると期限間近に引っかかりやすい。

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

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

APIドキュメントを読む