A 400 Bad Request from the unified async task API means your request reached HiAPI and was rejected before any model ran, because the JSON or the input object didn't match the model's schema. The response looks like this:
{
"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'"
}
The single most useful field is message: it names the exact offending parameter and what was wrong with it. A 400 is a request problem, not an auth or balance problem — and because no task is created, a rejected 400 is never billed.
What triggers a 400 (most common first)
Every case below returns HTTP 400. The error_code is INVALID_REQUEST for schema problems and MODEL_UNAVAILABLE for an unknown model id. These are the real messages HiAPI returns:
| Trigger | Real message you'll see | error_code |
|---|---|---|
| Invalid enum value | invalid input: aspect_ratio: value must be one of '1:1', '4:3', '3:4', '16:9', '9:16' | INVALID_REQUEST |
| Wrong value type | invalid input: prompt: got number, want string | INVALID_REQUEST |
| Missing required field / wrong field name | invalid input: prompt: missing required field "prompt"; <root>: additional properties 'promptt' not allowed | INVALID_REQUEST |
| Malformed JSON | invalid json body: unexpected end of JSON input | INVALID_REQUEST |
| Unknown / mistyped model id | model not available via /v1/tasks | MODEL_UNAVAILABLE |
Notice what the schema validator catches: enum values (an aspect_ratio or resolution outside the model's allowed set), types (a number where a string is required, or a bare string where an array is required), field names (a typo like promptt, or a renamed/removed field — the validator reports both the missing required field and the unexpected extra one), and the model id itself (a value that isn't a live model on the task endpoint).
How to diagnose it from the response body
- Read the HTTP status.
400= your request. If you instead see401,402,403,429, or504, it's a different class of problem — see Related reading. Don't change parameters to chase a 400 fix when the status is actually 401. - Read
error_code.INVALID_REQUESTmeans the body/inputfailed schema validation.MODEL_UNAVAILABLEmeans themodelvalue doesn't resolve on/v1/tasks. - Read
messageliterally. It is field-level:invalid input: <field>: <reason>. The word afterinvalid input:is the parameter to fix. If it saysvalue must be one of ..., the allowed set is printed right there — copy an allowed value verbatim. - For
additional properties '<x>' not allowed, you sent a field the model's schema doesn't define — usually a typo or a parameter copied from a different model. Remove it or rename it to the correct field.
A field-level message like this is worth calling out: many image and video APIs answer a bad request with a bare 400 Bad Request and no detail. HiAPI validates your input against each model's own schema and tells you which field failed and what it expected, so you can fix the request in one pass instead of guessing.
How to fix it (checklist)
- Fix invalid JSON first. If
messagestarts withinvalid json body, the payload isn't valid JSON — check for an unclosed brace/bracket, a trailing comma, or unescaped quotes. Validate the string withjqorpython -m json.toolbefore sending. - Confirm the model id is a bare, live id. Use a plain id such as
z-imageorgpt-image-2— do not invent route suffixes. Check the id against the model list or the model's page. An unknown id returnsMODEL_UNAVAILABLE. - Match the model's schema. Every model declares its own
inputfields. Put generation parameters insideinput, not at the top level. Remove any field the schema doesn't list (that's what triggersadditional properties ... not allowed). - Use allowed enum values exactly. When
messageprintsvalue must be one of ..., copy one of those values character-for-character (for example"aspect_ratio": "1:1"). - Send the right types.
promptis a string; reference images go in an array field (for exampleimage_urls); numeric fields must be numbers, not quoted strings. - Re-send and re-read. If you still get a 400, the
messagewill now point to the next field. Fix one field at a time until the call returns200with ataskId.
Minimal valid request
This is the smallest well-formed text-to-image call. If it returns 200 with a data.taskId, your key, endpoint, and request shape are all good — any 400 you were seeing was a parameter issue, not a connection issue:
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 red ceramic mug on a wooden table, soft daylight",
"aspect_ratio": "1:1"
}
}'
A successful submission returns a taskId inside data. You then poll GET https://api.hiapi.ai/v1/tasks/<taskId> until data.status is success (or fail), and read the result from data.output. Keep the request minimal while debugging: add optional fields back one at a time so a new 400 points at exactly the field you just added. See the model page for the full field list, and the API docs for the task lifecycle.
Related reading
- Invalid API Key Errors: Causes, Fixes, and a Setup Checklist — when the status is
401, not400. - Fix "Model Not Found" on HiAPI — for
MODEL_UNAVAILABLEand model-id problems. - How to Fix HiAPI Rate Limit (429) Errors — for throttling, not request validation.
- Are Failed HiAPI Requests Charged? — billing for a 400 vs a task that fails after it starts.
- Pricing and your dashboard to check credits and grab a key.
FAQ
What does error_code: INVALID_REQUEST mean?
Your request body or input object failed schema validation. The message field names the parameter that failed. It is not an authentication problem (that's 401) or a balance problem (that's 402).
Why do I get additional properties '<field>' not allowed?
You sent a field the model's schema doesn't define — usually a typo (promptt instead of prompt), a field that belongs to a different model, or a parameter you placed at the top level instead of inside input. Remove or rename it.
Does a 400 Bad Request cost credits? No. A 400 is rejected during validation, before any task is created, so there's nothing to bill. Charges only apply to tasks that are actually accepted and run.
I'm sure my JSON is correct but still get a 400. What now?
Read message word for word. It is field-level, so it tells you the exact parameter and the expected value or type. If it says value must be one of ..., your value isn't in the allowed enum — copy an allowed value exactly. If it names a type (got number, want string), fix the type.
Is this the same error when the model id is wrong?
It's still HTTP 400, but the error_code is MODEL_UNAVAILABLE with the message model not available via /v1/tasks. Use a bare, current model id and don't add made-up route suffixes.
How do I find the exact fields a model accepts?
Each model's page lists its input schema and an example request. Start from the minimal valid request above, then add the fields the model page documents, one at a time.



