
You generated an image in ChatGPT, copied the link behind it — something like https://chatgpt.com/backend-api/estuary/content?id=file-...&ts=...&p=... or a files.oaiusercontent.com/file-... URL — and pasted it into an <img> tag, a doc, or a script. It rendered for a while (or only in your own browser), then broke. Depending on which piece is missing, the request fails like this:
HTTP 422
{"detail":[{"type":"missing","loc":["query","p"],"msg":"Field required","input":null},
{"type":"missing","loc":["query","ts"],"msg":"Field required","input":null}]}
HTTP 404
<Error><Code>ResourceNotFound</Code>
<Message>The specified resource does not exist.</Message></Error>
And if what you saved was a gen_id from the generation metadata rather than a link, there is nothing to fetch at all — a gen_id is an identifier, not a URL.
Ordered by how often each is the culprit:
ts, p, and friends) carry a timestamp and signature. If they get stripped — many editors and chat apps truncate query strings when you paste — the endpoint answers the 422 above, listing the missing fields. If they age past their expiry window, the request stops authenticating. These links exist to serve the ChatGPT web app, not to be re-used elsewhere.gen_id is internal metadata. It identifies a generation inside your conversation so that follow-up edits can reference it. There is no public endpoint that resolves a gen_id to an image file.404 ResourceNotFound even for a well-formed link.The root cause is the same in every case: ChatGPT is a consumer app. Its image URLs are private, temporary pickup links — not hosting.
No image service hands out permanent URLs; signed links that expire are the norm (hiapi's included). The reliable pattern is: generate → download the bytes immediately → upload to storage you control → serve your own URL.
gpt-image-2 is available through the unified task endpoint POST /v1/tasks — see the gpt-image-2 model page. Per the live pricing page it starts at $0.03 per image at 1K resolution.output[0].url is itself a signed link with an explicit expireAt — same rule as above, it's a pickup token. Full walkthrough: hiapi output URL expired: download before it expires.Create a task with the bare model id (gpt-image-2 — don't append pipeline suffixes to the id you post):
curl -X POST https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer $HIAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-image-2",
"input": {"prompt": "a lighthouse at dawn, soft fog", "aspect_ratio": "1:1", "resolution": "1K"}
}'
A succeeding call returns {"code":200,"data":{"taskId":"..."}}. If instead you get HTTP 401 with:
{"error":{"code":"permission_denied","type":"hiapi_error","request_id":"..."}}
your key is missing, wrong, or not permitted for the model — check the authentication docs and your key in the dashboard.
Then poll until the task reaches a terminal status and fetch the output immediately:
curl -s https://api.hiapi.ai/v1/tasks/$TASK_ID -H "Authorization: Bearer $HIAPI_KEY"
# when "status":"success": grab output[0].url and download before expireAt
curl -s -o lighthouse.png "<output[0].url>"
Field-by-field request/response reference: Create Task.
Can I convert a gen_id into a working image URL?
No. A gen_id only has meaning inside the conversation that produced it (for example, when asking ChatGPT to edit a previous generation). There is no public endpoint that turns it into a file.
How long do ChatGPT image links stay valid? The expiry isn't documented and can change. The links are signed with a timestamp, so treat every one of them as temporary — if you need the image, download it now.
Do hiapi's image URLs expire too?
Yes. output[0].url from /v1/tasks ships with an expireAt field. The workflow is deliberately the same everywhere: download the bytes promptly and re-host them. See the download-before-expiry guide.
So what exactly is a "stable image URL"? One served from storage you control. APIs give you a temporary fetch URL; stability comes from uploading the bytes to your own bucket/CDN and saving your URL in your database.
Why does the same link work in my browser but 4xx in my code?
Your browser carries your ChatGPT session and (while fresh) the signed query parameters. Your server has neither — so the endpoint rejects the request with a 422 (missing signature fields) or the storage layer answers 404.