HTTP status codes

The status groups a failure; it does not identify one. Four error codes share 409, three share 503 and two share 429, and what to do differs inside each group.

Branch on error.code, which is stable and named in the contract. The status is what a proxy, a load balancer and a metrics dashboard read.

The statuses

Status Codes Retry?
200 Not an error; meta.status says how far recognition got
400 invalid_request No, until the request changes
401 unauthorized No
402 insufficient_credits After a top-up
403 registration_required, impersonation_read_only No
404 not_found No
409 idempotency_conflict, idempotency_in_progress, idempotency_replay_unavailable, topup_in_progress Depends on the code
413 payload_too_large No, until the image is smaller
415 unsupported_media_type No, until the header changes
422 validation_failed No, until the field is fixed
429 rate_limited, document_repeated After the window
500 internal_error Once
501 payment_driver_unavailable No
503 engine_unavailable, service_unavailable, maintenance, rate_unavailable Yes, with backoff

200 is not always a recognition

A failure to recognize is a 200. meta.status carries one of recognized, no_document_found, unreadable, unsupported_document and rejected, and the body is complete in all five cases.

A consumer that treats every 200 as a recognized document reports a blank holder where the answer was that nothing was in the frame. Read meta.status first.

The three 503s answer three different questions

They share a status because a proxy should treat all three the same way. A caller should not.

Code What is away What to do
engine_unavailable The recognition engine did not answer inside its deadline Retry with backoff. Nothing was charged
service_unavailable A dependency this service needs is unreachable Retry after Retry-After, with backoff. Nothing was charged
maintenance Nothing. An operator closed the service on purpose Wait out the window. It does not clear on its own, so honor Retry-After rather than polling

The difference that matters: the first two clear when something comes back, and a retry loop finds the moment it does. The third clears when a person reopens the service. A fleet polling every second buys nothing there, and arrives all at once when it does.

rate_unavailable is a fourth 503, and it belongs to crypto top-ups rather than to recognition. Fewer than two price sources agreed, so no amount could be quoted.

The four 409s

Code What happened What to do
idempotency_conflict The key was used with a different body Use a new key
idempotency_in_progress The first request under the key is still running Retry the same key and body in a moment
idempotency_replay_unavailable The key's first result was not retained Use a new key
topup_in_progress A crypto top-up for this asset is open and part-paid Finish it, or wait for its window

Three of the four are about an Idempotency-Key. Which one arrives is what says whether to retry the same key or mint a new one. The rules are on idempotency.

The two 429s

Code The limit Keyed by
rate_limited Requests per window The key, or the client address on the public sandbox
document_repeated The same image, too many times on the free sandbox A digest of the image bytes

A caller that meets document_repeated while looping over one test image has met the wrong limit for the wrong reason. The request rate is fine; the picture is the problem. The figures are on limits.

Retry-After

A response carries Retry-After, in seconds, where the service can estimate a wait: the maintenance gate, a store outage, and the sandbox guards. Where it is absent, a few seconds of backoff is the right default.

Nothing in this API sends a Retry-After in the HTTP-date form.

What is never returned

The whole catalogue, grouped by what a caller does with it, is on errors.