Skip to main content

HTTP errors

Every error response carries a code and a detail:
Branch on code. Several causes share a status - 409 alone covers nine of them - so the status tells you the kind of failure and the code tells you which one. detail is written for a person reading a log: it names the workflow, verification, step or field at fault, and its wording is not stable. Treat a code you do not recognise as the status it arrived at. Codes are added as new causes become reachable, and an existing one never changes meaning, so a default branch on the status is always a safe fallback. On 422, detail names each field at fault and what was wrong with it, separated by ; when more than one failed:

Every code

Step reason codes

A step that did not pass carries reasons, each a stable code with a message written for the user’s screen. Branch on the code; the message wording can change. A step whose judgement turns on several checks reports one code for each check that did not hold, so read the whole array rather than its first entry. While the verification is open, a user can clear most of these by answering the step again, and you can clear them on a staged verification by resetting and running again. Once it reads completed, a failed step stays failed unless you reset the verification, so the code is what to base your own decision on.

Step gap codes

A step that passed can carry gaps: optional pieces it asked for that never arrived. A gap takes the same shape as a reason, a stable code with a message written for the user’s screen, and says what the record is missing rather than that anything failed. A step reporting one of these passed.

Retrying safely

  • Reads (GET) change nothing, so they are safe to poll and retry.
  • Create is idempotent per subject and workflow, so retrying POST /verifications never creates a duplicate. It answers 201 when the call created the verification and 200 when it resolved to one you already had.
  • Run the checks is idempotent on the same values: repeating the request names the run already in progress and changes nothing. Different values are refused with 409 until you reset.
  • Run it now (POST /verifications/instant) is not idempotent, and it is the one call where a blind retry costs you. Every call is a new verification answered on what that call carried, and a new credit. A call that times out was still run and still charged, so look for its record by reference_user_id before sending the submission again. See Instant.
  • Cancel and purge are idempotent; repeating either on a verification already in that state succeeds.
  • 429 and 503 are transient. Back off and retry the same request.
A 409 is not transient, and it is the status where the code earns its keep: nine codes answer at it, and what to do next differs for each. A 402 is not transient either, and it is the one refusal you cannot clear yourself. The same call is refused until the workflow is topped up, so surface it rather than retrying it. credits-exhausted and resets-exhausted are both topped up by buying, but they are not the same purchase: read the code before asking us for more of the wrong thing. See Credits and limits.