String API
Composer API

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

StatusConditionRetriable
400Invalid argument, out of rangeNot until you change the request
401Bad or missing API keyNo
403POST /v1/builds only — the key has no user recordedNot until you make a new key
404Unknown buildNo
409Already exists, failed precondition, abortedAfter the build's state changes
413Body over 1 MiBNo
429Rate limited — honour Retry-AfterYes
499The request was already cancelled upstreamn/a
501The route exists but is not served yetNo
503Our key store or core is unreachableYes
504Upstream timeoutYes
500Anything else, with a deliberately generic messageMaybe

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.