Errors
The problem+json error model and stable error codes.
Every error the API returns is an RFC 9457
application/problem+json document with a stable, machine-readable code:
{
"type": "https://docs.viewa.sh/errors#example-code",
"title": "Human-readable summary",
"status": 422,
"code": "example_code",
"detail": "What specifically went wrong with this request."
}Handle the code, not the message
title and detail are for humans and may be reworded at any time. code values are
part of the API contract: stable, additive-only, and safe to branch on.
if (problem.code === 'example_code') {
// handle it
}Job failures are not errors
problem+json is reserved for request-level failures: a bad or missing key, an
unknown id, a rejected upload. When an asynchronous job fails — a photo's
preprocessing, a render — that is a state the resource is in, not a problem with
the request that asked about it. Polling a failed resource answers 200, with a
structured error object on the body:
{ "id": "img_…", "status": "failed", "error": { "code": "preprocessing_failed", "detail": "…" } }Those nested error.code values are just as stable as problem+json codes — see
Photos & preprocessing for the image ones and
Areas for the area-set ones:
occlusion_failed, occlusion_timeout, and area_calculation_failed — the
size and orientation of one or more areas could not be estimated from the photo;
adjust the corners and try again. Your polling loop only ever branches on
status; it never needs an HTTP error handler for a job outcome.
Error catalog
Codes already live on the image endpoints: unsupported_media_type (415),
file_too_large (413), not_found (404), invalid_request (400) — and on
the areas endpoints: invalid_corners (400), image_immutable (400),
unknown_area (400), image_not_ready (409) — plus the auth codes shared by
every endpoint. Render requests add
invalid_selections (400), area_set_not_ready (409), and
idempotency_key_reused (409). The pipeline endpoints also return
visitor_token_required (403) when no X-Viewa-Visitor-Token was sent, and
visitor_token_invalid (403) when the one sent is malformed, expired, or was
issued for a different instance. The first applies only where the token is
mandatory (an instance configured to require it, and the history endpoint
always); the second applies anywhere a token is sent — see Visitor
tokens. The pipeline POSTs add two
more: rate_limited (429) for a transient burst or concurrency refusal, which
carries a Retry-After header, and quota_exceeded (429) when a daily
allowance is used up, which does not — see
Limits. One code is not about your request
at all: internal_error (500) means something went wrong on our side. It is
returned by DELETE /visitors/{id} when a deletion does not complete, and the
request is safe to retry.
Render job failures are returned in the resource's error object rather than as
problem+json. The closed list is capture_failed, capture_timeout,
generation_failed, generation_timeout, dispatch_failed, and internal.