Skip to main content
Errors surface in two different places, with two different vocabularies:
  • API request errors — the HTTP response to a synchronous API call (authentication, billing, malformed requests, missing resources). See API request errors.
  • Prediction failure codes — a request was accepted and queued, but the prediction itself ended in status: "failed". The failure is described by the error object on the prediction, which you get from the prediction status endpoints, from webhook deliveries, and from the result endpoint. See Prediction failure codes.

Prediction failure codes

A failed prediction carries an error object:
Failed predictions are not charged; any credits reserved for the request are released. A prediction that has not failed carries error: null. See Migration note if you integrated before 17 August 2026.

Where the error reaches you

The same error object is published on every channel, so you can read it wherever you already look:

Reading a failure from the result endpoint

Asking for the output of a prediction that failed returns 400 with the failure that caused it, under the request-level code PREDICTION_FAILED:
Two codes, at two levels, and they are not the same vocabulary:
  • error.code is PREDICTION_FAILED, an API request error meaning “the prediction you asked for the output of did not produce one”;
  • error.details is the prediction’s own error object — identical to what /status reports for the same request, so the two endpoints can never disagree about why it failed. Branch on error.details.reason and error.details.retryable.
error.message repeats the prediction’s message, so a client that only ever reads the top-level message still gets something useful. Two timestamps, two meanings. The top-level timestamp is when this HTTP response was produced; error.details.timestamp is when the prediction failed. For a prediction you fetch days later, they are days apart. Read the inner one whenever you mean the failure. The official client libraries unpack this for you: result() raises an error whose code, reason and retryable are the prediction’s own, identical to what subscribe() raises for the same failure. A prediction that is still queued or running, or one that was cancelled, keeps returning the same generic 400 it always has.

Deciding whether to retry

Read retryable. It answers exactly one question — will submitting this identical request again have a different outcome? — and it is independent of code: the same code can be retryable in one failure and not in another. retryable is optional. When it is absent, fall back to the per-code default in the table below. That is the behaviour Sunra had before this field existed, so falling back is always safe; it is simply less precise.

code

reason

reason names the specific cause behind code. Use it for metrics, routing and support diagnostics; use retryable for the retry decision. Every reason published today has its own section below, and its anchor is the reason itself — #input_fetch_failed, #moderation_blocked and so on — so an error report can link straight to what it means.

input_validation_failed

code: invalid_input · retryable: false The request failed Sunra’s own schema or parameter validation. The message names the offending field. What to do: fix the named field. The same request will keep failing.

provider_rejected_input

code: invalid_input · retryable: false The model provider rejected one or more input parameters. Where the provider tells us which, the message says so; some providers only report that the input was unacceptable, and we say exactly that rather than guessing. What to do: review your parameters against the model’s schema — a value out of range, an unsupported combination, an aspect ratio or duration the model does not accept.

input_fetch_failed

code: invalid_input · retryable: false or true An input file referenced by URL could not be fetched. This is the one reason whose retryable genuinely varies, so read the field rather than assuming:
  • false — the host rejected us: a 4xx, an address we are not permitted to fetch from, a redirect, or a file over the size limit;
  • true — the host was momentarily unreachable: a 5xx, a timeout, or a DNS failure.
The message echoes the URL you submitted so you can tell which input failed — with its query string removed, so credentials carried in a presigned URL are never reflected back to you:
What to do: when retryable is false, check that the URL is publicly reachable and that a presigned link has not expired. When it is true, retry.

moderation_blocked

code: unsafe_content · retryable: false Blocked by the model provider’s content safety system — either the prompt and input media going in, or the generated output coming back. What to do: change the prompt or the media. Resubmitting the same content will be blocked again.

provider_rate_limited

code: service_provider_error · retryable: true The model provider is rate limiting requests. What to do: retry with backoff, or route to another model.

provider_unavailable

code: service_provider_error · retryable: true The model provider failed to serve the request. What to do: retry later, or route to another model.

provider_timeout

code: task_timeout · retryable: true The prediction did not complete within the allowed time. What to do: retry — a lower-load period, or a smaller request (shorter duration, lower resolution), improves the odds.

provider_account_error

code: internal_server_error · retryable: false Our own account with the model provider has a problem — billing, credentials, or a permanent quota. This is our fault, not your input’s. What to do: nothing on your side; retrying will not help, and we are alerted automatically. Route to another model if you need the capacity now.

endpoint_misconfigured

code: internal_server_error · retryable: false This model endpoint is misconfigured on our side. What to do: nothing on your side; retrying will not help, and we are alerted automatically. Contact support with the request_id if it is urgent.

queue_enqueue_failed

code: internal_server_error · retryable: true The prediction could not be placed on the queue. What to do: submit the request again.

reaped_stalled

code: internal_server_error · retryable: true The prediction stalled and was terminated. What to do: submit the request again.

Unknown values

Both code and reason are open sets. New values are added as we classify more failures, without a breaking-change announcement. Your integration must not crash, drop the error, or reject the payload when it sees a value it does not recognise.
  • Unknown reason — treat it as if reason were absent, and decide using retryable (or the per-code default). Keep the raw value in your logs; it is the fastest way for support to identify the failure.
  • Unknown code — treat it as internal_server_error. One sentinel value is worth knowing by name: unknown_error_code, which Sunra emits when a prediction failed but no classification could be recovered for it.

Migration note: error is null when there is no error

Effective 17 August 2026. The error object has one representation on every channel. Three differences between them are gone:
  • error is null when the prediction did not fail. GET /v1/predictions/{prediction_id} used to answer with an empty object — "error": {} — on every succeeded, queued and cancelled prediction. It now answers "error": null, which is what the queue status endpoint already did. If you test for emptiness (Object.keys(response.error).length === 0), replace that with a null check (response.error === null).
  • A failure always carries a non-empty code and message. Where a failure recorded something we cannot classify, you now get the unknown_error_code sentinel and a generic message, rather than a field that is missing, empty or not a string.
  • Failures recorded as plain text reach you as text. A handful of predictions from April 2026 stored their error as a bare string. GET /v1/predictions/{prediction_id} used to drop it, and the queue status endpoint used to replace it with the sentinel message; both now publish it as message under code: "unknown_error_code".
Nothing was removed and no new field appeared: error was already on every one of these responses, and only its value changed. Webhook deliveries are untouched — they were the shape the other channels converged on — and error continues to be absent from a succeeded delivery rather than sent as null. Compatibility window. There is no dual-emission period; the API stopped producing "error": {} on that date. If you replay or reprocess payloads you captured before it, treat null and {} as the same thing until that stored data ages out.

Upcoming change: some failures will get a more specific code

Today a large share of failures are reported as service_provider_error, including many that are neither transient nor the provider’s fault. As we classify providers one at a time, those failures move to the code that actually describes them:
  • a provider deterministically rejecting your input becomes invalid_input (previously service_provider_error);
  • a problem on Sunra’s side, such as a misconfigured endpoint or our own provider account, becomes internal_server_error (previously service_provider_error);
  • an input file we could not fetch becomes invalid_input (previously internal_server_error).
No code is being renamed or removed, and no new code value is being introduced — the five values above remain the complete set of classified codes. What changes is which of them a given failure receives, and it changes gradually, per provider. If you branch on code, this is a good moment to move that logic onto retryable and reason, which describe the failure directly and are not affected by the reclassification.

API request errors