HiAPI
  • Models
  • Pricing
Search

Search HiAPI models, tools, and resources.

LoginGet Started
  • Models
  • Pricing
HiAPI

One API, All AI Models

Generate images, video, and audio with leading models through one production-ready API.

Get a free API key

AI Image API

  • All image models
  • GPT Image 2
  • Nano Banana 2
  • Seedream 5.0 Pro
  • Qwen Image 2.0 Pro
  • FLUX 1.1 Pro

AI Video API

  • All video models
  • Seedance 2.5
  • FLUX.3 Video
  • Seedance 2.0
  • Veo 3.1
  • Kling 3.0

AI Audio API

  • All audio models
  • MiniMax Music 2.6
  • MiniMax Music 1.5
  • ElevenLabs v3
  • Text to music
  • Text to speech

Product

  • Model marketplace
  • Playground
  • Pricing
  • Image API Cost Calculator
  • Free GPT Image 2 Generator
  • Free Nano Banana Image Generator
  • Outfit Preview
  • Product Photo Lab

Developers

  • Documentation
  • API Reference
  • Agent Skills
  • LLM integration index
  • Blog

Company

  • About
  • Contact support
  • Terms of Service
  • Privacy Policy

© 2026 hiapi. All rights reserved.

Open source on GitHubPython SDK on PyPI
  • Common causes
  • Fix steps
  • Minimal verification
  • Related reading
  • FAQ
TutorialJun 28, 2026

hiapi output URL expired: download before it expires

hiapiUpdated Jul 30, 2026hiapitroubleshootingtasks-api

Latest models

Explore models

Contents
  • Common causes
  • Fix steps
  • Minimal verification
  • Related reading
  • FAQ

Generate it with HiAPI

Choose a model, enter your prompt, and see the result.

HiAPI Blog

Related articles

HiAPI

Generate it with HiAPI

When you call the hiapi unified task API (POST /v1/tasks), every successful job returns a result that looks roughly like this:

{
  "id": "task_01H...",
  "status": "succeeded",
  "output": [
    {
      "url": "https://cdn-provider.example/abc.png",
      "expireAt": "2026-06-28T14:05:00Z"
    }
  ]
}

The output[i].url is a short-lived signed URL. After the moment in expireAt passes, the URL stops working and you cannot fetch the asset again from that link — the task is already billed and the original task record stays in your dashboard, but the underlying file is gone from that signed URL. Symptoms you will see in the browser or in curl once it has expired include HTTP 403, an XML body that says AccessDenied / Request has expired, or simply a generic 404 from the CDN.

If you hot-linked the URL into a customer-facing page or stored it in your DB as the canonical asset path, that page or row is now broken. The fix is to download the bytes immediately after status becomes succeeded and host them yourself (R2, S3, your own CDN, a static folder — whatever works).

Common causes

In rough order of how often we see it on support tickets:

  1. Hot-linking the signed URL directly from a webpage, blog, social post or Notion doc. As soon as the signature expires, the image / video 404s for every reader.
  2. Storing the signed URL in your own database (e.g. articles.cover_image, assets.video_url) without downloading the bytes. The row keeps the dead URL forever.
  3. Long-running pipelines where the worker that polls GET /v1/tasks/{id} is not the same process that consumes the asset, and the asset is fetched many minutes — or hours — later from a queue.
  4. Forgetting to upload the asset to your own CDN during a batch generation run. Everything looks fine in the logs, then half the deliverables 404 the next day.
  5. Treating expireAt as "long enough". The exact TTL is set by the upstream provider and varies by model; the only safe assumption is "minutes, not days".

Fix steps

  1. Poll the task to a terminal state. GET /v1/tasks/{id} until status == "succeeded" (or failed / cancelled).
  2. Read output[*].url from the success payload and download the bytes in the same function call, before doing anything else.
  3. Persist the bytes to your own storage (R2, S3, local disk, your CDN). Use the task id as the filename so it is idempotent if you retry.
  4. Store your own URL in your database — never the upstream signed URL.
  5. Stop relying on expireAt as a deadline you can plan against. Treat the signed URL as something you read once and then throw away.
  6. If you have already published a dead URL, re-run the task or fetch from your own copy if you took one; the original signed URL cannot be revived.

Minimal verification

A single end-to-end check that creates an image task, waits for it, and writes the bytes to disk. Replace HIAPI_API_KEY with the key from your dashboard.

API_KEY="$HIAPI_API_KEY"

# 1. Create the task with the documented text-to-image model id.
TASK=$(curl -s -X POST https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-image-2/text-to-image",
    "input": { "prompt": "a red ceramic teapot on a wooden table, soft light" }
  }')

TASK_ID=$(echo "$TASK" | python3 -c 'import json,sys;print(json.load(sys.stdin)["id"])')

# 2. Poll until terminal.
while :; do
  R=$(curl -s -H "Authorization: Bearer $API_KEY" \
        https://api.hiapi.ai/v1/tasks/$TASK_ID)
  STATUS=$(echo "$R" | python3 -c 'import json,sys;print(json.load(sys.stdin)["status"])')
  echo "status=$STATUS"
  [ "$STATUS" = "succeeded" ] && break
  [ "$STATUS" = "failed" ] || [ "$STATUS" = "cancelled" ] && { echo "$R"; exit 1; }
  sleep 3
done

# 3. Download bytes IMMEDIATELY — do not store the URL anywhere.
URL=$(echo "$R" | python3 -c 'import json,sys;print(json.load(sys.stdin)["output"][0]["url"])')
curl -sL "$URL" -o "$TASK_ID.png"
echo "saved $TASK_ID.png"

The Python equivalent (using requests) is the same shape: create → poll → requests.get(output[0]["url"]).content → write to your bucket. The key rule is that the URL is consumed exactly once, in the same code path that produced it.

If step 1 returns HTTP 400, double-check the model id and input fields against the current GPT Image 2 docs. The text-to-image id is gpt-image-2/text-to-image. A 401 means the key is missing or invalid; a 403 means the key does not have permission. Fix authentication before retrying the task.

Related reading

  • Models catalog — every model that goes through /v1/tasks is listed here with its supported inputs.
  • API docs — the full task lifecycle, parameters, and error shapes.
  • Dashboard — get an API key and see all past tasks (the task record is permanent even after the signed URL expires).
  • hiapi blog — more practical troubleshooting notes.

FAQ

How long does the signed URL last? It varies by model and provider. Treat it as "a few minutes" and design for that. If you need a long-lived URL, host the file yourself.

Can I refresh the URL by calling GET /v1/tasks/{id} again? No. The task record remains, but the output[*].url is the same expired signed URL — calling the endpoint again does not mint a new one.

Do I get charged again if I re-run the task to recover a lost asset? Yes, re-running creates a new billable task. That is exactly why downloading once at the end of the polling loop is the cheap path.

Can I pass the URL to a downstream model (e.g. as a reference image)? Only while it is still valid. The safer pattern is to download, upload to your own storage, then pass your own URL to the next call.

Why does hiapi not host the asset for me? The unified task API forwards to upstream providers and returns whatever signed URL they hand back. Re-hosting every output would be an extra storage layer most users do not want; the contract is "you get bytes once, do what you want with them".

My link still works hours later — is the doc wrong? No, some providers issue long-TTL URLs and you happened to get one. Other providers issue 5-minute URLs. Do not let one lucky case set your expectation.

Latest models

View all models
  • GPT Image 2From $0.007/image
  • Nano Banana 2From $0.051/image
  • Seedream 5.0 ProFrom $0.050/image
  • Seedance 2.5From $0.121/s

Explore models

TextImageVideoAudio
Back to blog
GPT Image 2From $0.007/image
Nano Banana 2From $0.051/image
Seedream 5.0 ProFrom $0.050/image
Seedance 2.5From $0.121/s
View all models
TextChat and reasoning
ImageGenerate and edit
VideoText and image to video
AudioSpeech and music
Start generating
View model pricing
View all articles
How to Use the flux-3 API for Text-to-Video, Audio, and Continuation

How to Use the flux-3 API for Text-to-Video, Audio, and Continuation

minimax-music-3 API: curl & Python Guide

minimax-music-3 API: curl & Python Guide

How to use grok-imagine-image-2.0/image-to-image via the hiapi API: curl, Python, and a working request

How to use grok-imagine-image-2.0/image-to-image via the hiapi API: curl, Python, and a working request

How to Use grok-imagine-image-2.0/text-to-image via the hiapi API: curl, Python, and a Working Request

How to Use grok-imagine-image-2.0/text-to-image via the hiapi API: curl, Python, and a Working Request

How to Use the qwen-image-3.0 API: curl, Python, and a Working Request

How to Use the qwen-image-3.0 API: curl, Python, and a Working Request

How to Use qwen-image-3.0-pro via the hiapi API: curl, Python, and a Working Request

How to Use qwen-image-3.0-pro via the hiapi API: curl, Python, and a Working Request

Start generating