Errors
The pd4castr API uses conventional HTTP status codes to signal the outcome of a request. 2xx responses mean success, 4xx responses mean the request was rejected (most often because of a missing or expired token, an unknown identifier, or a malformed body), and 5xx responses mean the service couldn’t complete the request.
Every failed request returns the same JSON shape, carrying a machine-readable
code that you branch on. Write one error handler around code and it works
across the whole API.
Response shape
A failed request returns a JSON body with a code, a message, and, for some
codes, a data object holding structured detail:
{
"code": "NOT_FOUND",
"message": "Model not found",
"statusCode": 404,
"error": "Not Found"
}| Field | Type | Description |
|---|---|---|
code | string | The stable, machine-readable identifier for the failure. Branch on this. See Error codes. |
message | string | A human-readable description of what went wrong. The wording can change over time, so don’t match on it. |
data | object | Optional. Structured detail for the codes that carry it. Its shape depends on code, so branch before reading it. |
statusCode | number | Deprecated. The HTTP status repeated in the body. See Deprecated fields. |
error | string | Deprecated. The HTTP status reason phrase. See Deprecated fields. |
message is always a single string. Validation failures used to send an array
of strings here, one per rejected constraint. They now send one summary
sentence, with the per-field breakdown under data.issues.
Error codes
Each code maps to exactly one HTTP status, so the status tells you the broad class of failure and the code tells you which specific failure it was. Most codes are generic and derived from the status; a few are domain codes that let you tell apart failures that share a status.
| Code | Status | Meaning |
|---|---|---|
BAD_REQUEST | 400 | The request was rejected for a reason with no more specific code. Read message. |
VALIDATION_FAILED | 400 | A body field, query parameter, or path parameter failed validation. See Validation failures. |
UNAUTHORIZED | 401 | The Authorization header is missing, or the bearer token is invalid or expired. Request a new token and retry once. |
FORBIDDEN | 403 | Your organisation is authenticated but not permitted to perform this request. |
ORGANISATION_DISABLED | 403 | Your organisation has been disabled. message carries the reason. Every authenticated request fails until it’s re-enabled. |
NOT_FOUND | 404 | The model, model group, or run id doesn’t exist, or your organisation doesn’t have access to it. Retrying the same id won’t help. |
REQUEST_TIMEOUT | 408 | The request took too long to complete. Transient, so retry with back-off. |
CONFLICT | 409 | The request conflicts with the current state of the resource, such as triggering a run that’s already in flight. |
MODEL_RUN_OUTPUT_UNAVAILABLE | 409 | The run’s output couldn’t be rebuilt after repeated attempts. Terminal: polling won’t change it. |
RATE_LIMITED | 429 | You’ve exceeded your request allowance. Wait for the number of seconds in the Retry-After response header, then retry. |
INTERNAL_ERROR | 500 | The service failed to handle the request. Retry with back-off; if it persists, contact support. |
BAD_GATEWAY | 502 | An upstream dependency, usually the authorization server, was unreachable. Transient, so retry with back-off. |
New codes are added as the API grows. Treat a code you don’t recognize as the generic failure for its HTTP status, and don’t reject the response for carrying an unknown one.
Validation failures
A VALIDATION_FAILED response describes every rejected input at once. message
summarizes them in one sentence, and data.issues lists one entry per field, so
you can map the failure back to the input that caused it:
{
"code": "VALIDATION_FAILED",
"message": "Request validation failed: limit must not be greater than 100",
"data": {
"issues": [
{
"field": "limit",
"messages": ["limit must not be greater than 100"]
}
]
},
"statusCode": 400,
"error": "Bad Request"
}Each issue has a field and a messages array. field uses dotted paths for
nested body objects, such as window.from. A single field can fail more than
one constraint, which is why messages is an array.
Token endpoint errors
The token endpoint, POST /v1/auth/token, is the one exception to the shape
above. Because it implements the OAuth 2.0 client credentials grant, it returns
OAuth error bodies instead:
{
"error": "invalid_client",
"error_description": "Client credentials were rejected."
}error | Status | Meaning |
|---|---|---|
invalid_request | 400 | A required field is missing or malformed. Fix the request; don’t retry as-is. |
unsupported_grant_type | 400 | grant_type is missing or set to anything other than client_credentials. |
invalid_client | 401 | Your client_id or client_secret is wrong or has been revoked. Stop and re-check your credentials. |
temporarily_unavailable | 502 | The authorization server was unreachable, so no token could be issued. Transient, so retry with back-off. |
Handling errors
Branch on code, and fall back to the HTTP status for anything you don’t
recognize. A pragmatic handler covers five cases:
UNAUTHORIZED: request a fresh token from/v1/auth/token, then retry the original request once. If the retry fails the same way, your credentials are being rejected; treat it asinvalid_clientand stop.VALIDATION_FAILEDandBAD_REQUEST: fix the request and don’t retry.data.issuestells you which inputs were rejected.NOT_FOUND: surface the failure to your caller rather than retrying with the same id.RATE_LIMITED: wait for theRetry-Afterheader’s value, then retry. Lower your request rate if you hit it repeatedly.MODEL_RUN_OUTPUT_UNAVAILABLE: stop polling. The output can’t be rebuilt, so retrying returns the same response.
For everything else, including network timeouts, dropped connections, and unexpected 5xx responses, retry with exponential back-off starting at one second and cap the total attempts.
Deprecated fields
Error responses also carry statusCode and error, which is what the API sent
before code existed. They stay in place during the transition so existing
integrations keep working, and they’ll be removed once consumers have moved
across.
statusCoderepeats the HTTP status from the response’s status line.erroris the HTTP status reason phrase, such asNot Found. It’s derived from the status, so it can’t distinguish two failures that share one.codecan, which is what replaces it.
If your integration switches on error today, move it to code. The mapping is
one-way: every error value corresponds to one or more codes, so Not Found
becomes NOT_FOUND, and Forbidden becomes either FORBIDDEN or
ORGANISATION_DISABLED depending on why the request was rejected.
Next steps
- Authentication is where you exchange your client credentials for a bearer token.
- Quick Start is an end-to-end TypeScript example covering the full request lifecycle.