Skip to main content

Pending and polling

Most checks answer in the same response. When one cannot, the API does not make you wait on an open connection: it accepts the check and answers status: "pending" with a check_id. The body has exactly the same shape as a completed check, so the code that reads the response does not change; you only branch on data.status.

Why a check is pending​

A check is handed to a queue, and answers pending, when it genuinely needs more time than a request should hold:

  • a third-party data source is busy and the lookups have to wait their turn,
  • a cold traversal of the chain takes longer than the request budget, or
  • the check reached the end of the time it was given to answer inline.

While a check waits for required evidence there is no verdict, no charge and no completion webhook. Everything is settled once, when the check finishes.

The waiting fields​

FieldWhile pendingOnce terminal
statuspendingcompleted or failed
pending_reasonWhy the check is waiting for evidence, for example provider_pending (lookups at a third-party data source are in flight) or provider_capacity (waiting for lookup capacity). Informational: do not branch on values you do not know. null when the check is simply queued.null
deadline_atThe configured time by which the check will have finished. It is a limit, not an estimate: most checks finish far sooner. For native and batch checks it defaults to 24 hours after acceptance.Kept as recorded. null for a check that never had to wait.
next_attempt_atThe earliest time the next retry or readiness inspection is scheduled. Not a promise of a result at that time.null

When a data source is known to be down, a check does not wait for its deadline: it finishes early with a partial verdict that says why (see partial_reasons and degraded_sources in Address checks) and answers once.

Poll a check​

GET /v1/checks/{check_id} with the checks:read scope.

  • A pending id never answers 404. It resolves immediately with status: "pending". A 404 means the id is not yours, belongs to the other environment, or never existed.
  • Poll no earlier than next_attempt_at. Nothing changes between scheduled inspections. When it is null, wait a few seconds. Polling counts against the rate limit of the GET route, 30 requests per minute from one source IP address; see Rate limits and quotas.
  • Stop at completed or failed. Both are terminal, and no field changes afterwards. Two reads of a terminal check return identical bodies, so it is safe to read it again whenever you need the verdict.
  • Stop at deadline_at. The check is finalised by then. If the last answer is still pending a short time after it, keep the check_id and read it again later instead of looping.

Prefer a webhook when you can: you register an endpoint once and receive one signed check.completed delivery per finished check, so there is nothing to poll. Keep polling as the fallback for a delivery that never arrives.

curl -s https://api.leeri.ai/v1/checks/$CHECK_ID \
-H "Authorization: Bearer $LEERI_API_KEY"

When a check fails​

A check that ends without a verdict has status: "failed", with score, band and the other verdict fields null. A failed check is never charged. If you submit a check and it fails during the request itself, the answer is 422 CHECK_FAILED; status: "failed" is what you read when you fetch a check later. To try again, submit the check as a new request with a new Idempotency-Key; see Idempotency.

Testing the pending path​

A sandbox key submitting the test address 1PENDNGCHECKADDRESSFRSANDBXTEST99 as a BTC address check always answers pending, so you can build and test your polling and webhook handling without waiting for a real slow lookup. See Sandbox and live.