The Symptom
You submit a generation task to POST https://api.hiapi.ai/v1/tasks, poll it with GET /v1/tasks/:id, and the response comes back with "status": "fail" and an error object such as {"code": "TASK_FAILED", "message": "task failed"}. The HTTP status on that polling call is a plain 200 — HiAPI wraps the failure inside the JSON body, not in the status code. Because the task did spend time in queued → handling before failing, it's easy to assume the attempt was billed like a normal API call. In reality, a terminal fail status does not mean you were charged — but the reservation created at submission time can make it look that way if you check your balance before the refund has posted.
Common Causes
- Mistaking the reservation for a charge. HiAPI reserves a task's estimated cost the moment it's created, before any work happens. That reservation shows up in your balance immediately — it is not a charge yet.
- Checking balance mid-task. Between
queued/handlingand the terminal state, the reservation is still outstanding by design. Only the terminal state (successorfail) resolves it one way or the other. - Conflating a provider-side failure with a creation-time rejection. A task that reaches
status: "fail"(for exampleerror.code: "TASK_FAILED") actually ran and failed downstream — HiAPI's docs are explicit that the reserved amount is refunded in full once a task hits that terminal state. A request that never gets ataskIdat all — like a synchronous402 INSUFFICIENT_QUOTAresponse — was rejected before any task existed, so nothing was ever reserved to begin with. Both look like "it failed" from the outside, but only one of them ever touches a reservation. - Reusing an
Idempotency-Keyincorrectly. Replaying the same key with the same request body just returns the originaltaskId— it will not create or charge a second task. But reusing that key with a different body returns a creation-time422 IDEMPOTENCY_KEY_MISMATCH, and reusing it while the first call is still in flight returns409 IDEMPOTENCY_KEY_PROCESSING. Neither of those paths creates a billable task either.
Fix Checklist
- Poll
GET /v1/tasks/:idand readdata.statusdirectly — don't infer billing outcomes from your balance screen alone.queued,handling, andarchivingare all non-terminal; onlysuccessandfailare final. - If
data.statusis"fail", that is a refunded outcome. Checkdata.error.codefor context (e.g.TASK_FAILED), then check your balance again — the refund posts automatically once the task reaches that terminal state. - If the original
POST /v1/taskscall itself returned a non-2xx status (400/402/409/415/422) with notaskIdin the body, there is no task to refund — nothing was ever created or charged. For the specific case of insufficient balance, that shows up aserror_code: "INSUFFICIENT_QUOTA"on a402response. - Cross-check the reservation-and-refund pair in your transaction history at the HiAPI billing dashboard rather than relying on a single point-in-time balance figure.
- When retrying a failed task, send a fresh
Idempotency-Keyfor a genuinely new attempt. Don't reuse the old key with a changed body (triggers422) or fire a duplicate request on the same key while the first is still processing (triggers409). - If you've confirmed the task reached
failand the reservation is still outstanding well after that, that's outside this refund mechanism — contact support instead of assuming it will self-resolve.
Minimal Verification Example
curl -s -X POST https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"model": "gpt-image-2/text-to-image",
"input": { "prompt": "a minimalist desk setup, studio lighting" }
}'
The model id here includes the route suffix /text-to-image — as of this writing, the live model index lists gpt-image-2 only as suffixed entries (/text-to-image, /image-to-image); there is no bare-id entry for it. Always check that index for the exact availability: "online" id you need rather than copying another model's pattern.
Poll the returned taskId:
curl -s https://api.hiapi.ai/v1/tasks/$TASK_ID \
-H "Authorization: Bearer $HIAPI_API_KEY"
A refunded failure looks like this:
{
"code": 200,
"message": "success",
"data": {
"taskId": "tk-hiapi-...",
"model": "gpt-image-2/text-to-image",
"status": "fail",
"created": 1777282033,
"completed": 1777282099,
"error": { "code": "TASK_FAILED", "message": "task failed" }
}
}
FAQ
Does HiAPI charge me if an image or video task fails?
No. If a task reaches the terminal fail status, the amount reserved at creation is refunded in full.
Why did my balance drop right after I submitted a task that later failed?
That drop is the up-front reservation, not a charge. It resolves automatically once the task reaches a terminal state — refunded on fail, consumed on success.
What's the difference between a 402 error and a failed task?
A 402 INSUFFICIENT_QUOTA response means the task was never created in the first place, so there's nothing to reserve or refund. A task that returns a taskId and later reaches status: "fail" did get created, ran, and then failed; HiAPI refunds that reservation automatically.
Will retrying with the same Idempotency-Key get me charged twice?
No. Replaying the same key with the same request body returns the original taskId instead of creating a new task. Changing the body under the same key returns a 422 IDEMPOTENCY_KEY_MISMATCH error instead of creating a duplicate.
Where can I see the reservation and refund in my account? In your billing and transaction history at the HiAPI billing dashboard.









