Errors
One envelope, the status mapping, and the two refusals that are not what they look like.
Every refusal on this API has the same body:
{
"error": {
"type": "invalid_request_error",
"code": "approve_required",
"message": "…",
"param": "publish.approve",
"request_id": "da06fc13-1f9d-46dc-aa36-cd62607c3386"
}
}Switch on type to decide whether to retry, fix the request, or escalate; code is the specific reason within that
family. param names the offending field when there is one, and is omitted rather than sent empty.
request_id matches the x-string-request-id response header and our log line, so quote it when you ask us about a
request.
Status codes
| Status | Condition | Retriable |
|---|---|---|
400 | Invalid argument, out of range | Not until you change the request |
401 | Bad or missing API key | No |
403 | POST /v1/builds only — the key has no user recorded | Not until you make a new key |
404 | Unknown build | No |
409 | Already exists, failed precondition, aborted | After the build's state changes |
413 | Body over 1 MiB | No |
429 | Rate limited — honour Retry-After | Yes |
499 | The request was already cancelled upstream | n/a |
501 | The route exists but is not served yet | No |
503 | Our key store or core is unreachable | Yes |
504 | Upstream timeout | Yes |
500 | Anything else, with a deliberately generic message | Maybe |
Retry-After on a 429 is always present and never zero.
Two answers that are not what they look like
409, not 400, for a state conflict
Input sent to a finished build, or a gate answered after it stopped being pending, is a 409. Your JSON is fine —
the build moved. Re-read the build's state rather than editing your body.
503, not 401, when our key store is down
Never rotate a key in response to a 503. It is ours, and retriable.
No 403 ever tells you a build exists
A build owned by another organization returns a byte-identical 404 to an unknown id — same status, same code,
same message, built from the id you supplied. Nothing inspects the id, the organization, or the message to choose
between them.
Do not write client code that tries to tell the two apart, because nothing distinguishes them. No route that
takes a build id will ever answer 403.
The one 403: key_not_attributed
POST /v1/builds — which takes no build id, and so reveals nothing about any build — returns 403 with code
key_not_attributed when your key has no user recorded against it.
This is not an authentication failure. The key is genuine, and every read endpoint keeps working with it. We file a feed in our registry under the person who requested it, and a key issued before we recorded that — or one whose creator has since been removed from your organization — has nobody to file under.
Retrying will not help; generate a new key
Every retry fails identically. Generate a replacement under Feeds → Composer API Keys and the builds it starts will register normally.
It is refused at the start of the build rather than at publication, so you are not billed for a dataset that could never have been delivered.