Skip to main content

Errors

Every non-2xx response on /v1 uses one envelope. Branch on errors[0].code, never on the message text: messages are for people and can change, codes are stable.

The error envelope​

{
"status": "error",
"data": null,
"errors": [
{
"code": "IDEMPOTENCY_KEY_CONFLICT",
"message": "Idempotency-Key was already used with a different request body",
"parameter": "idempotency-key"
}
]
}
  • status is always "error" and data is always null.
  • errors has at least one entry. code is one of the values in the table below; parameter names the offending field or header when the error can be pinned to one, otherwise it is null.

Request ids​

Every response carries an x-request-id header. If you send a well-formed x-request-id with the request, it is echoed back; otherwise one is generated. Log it with each call and quote it when you contact support at hello@leeri.ai.

How to react​

SituationWhat to do
429 with RATE_LIMIT_EXCEEDED or PROVIDER_CAPACITY_EXCEEDEDWait for the number of seconds in the Retry-After header, then retry the same request. See Rate limits and quotas.
503 with REPORT_RENDERINGRetry after the Retry-After interval; the report link stays valid.
503 with RULESET_UNAVAILABLE, RATE_LIMIT_STORE_UNAVAILABLE, SERVICE_DRAINING or UPSTREAM_UNAVAILABLENo Retry-After is sent. Retry with exponential backoff and jitter. Nothing is charged for a request that was refused.
500 with INTERNAL_ERRORRetry with backoff and the same Idempotency-Key; if it keeps failing, contact support with the x-request-id.
409 with IDEMPOTENCY_REQUEST_IN_FLIGHTThe first request with this key is still running. Retry the same request after a short pause. See Idempotency.
409 with IDEMPOTENCY_LEASE_LOSTAnother retry took over the key while this request was running. Send the same request again to read the recorded result.
422 with PROVIDER_CAPACITY_REQUEST_TOO_LARGENever retry unchanged: the request is larger than the capacity can ever hold. Split it into smaller requests.
402 with INSUFFICIENT_BALANCEQuota and prepaid balance are both used up. Retrying does not help until the balance is topped up.
Any other 4xxThe request itself is wrong (VALIDATION_ERROR, ADDRESS_REQUIRED, UNSUPPORTED_ASSET, NOT_FOUND, SCOPE_ERROR and so on). Fix it; retrying unchanged returns the same error.

A request that fails with a 4xx or 5xx status is not recorded under its Idempotency-Key, so a retry runs again.

Error codes​

The table is generated from the API contract this documentation is pinned to, so it always lists exactly the codes the API can return. Each code is a link target: for example, ./errors.mdx#ADDRESS_REQUIRED points at its row.

CodeHTTP statusCondition
VALIDATION_ERROR400The request body, params or a header failed Zod validation — an unknown field, a value outside its declared bounds, or a shape the endpoint cannot parse.
AUTH_ERROR401The Authorization header is missing, malformed, or does not resolve to an active, unexpired API key.
SCOPE_ERROR403The API key authenticated but does not carry a scope this route requires.
TENANT_BOUNDARY403A data-layer read or write detected a cross-tenant reference — the platform-level RLS guard's own rejection, not a route-level check.
NOT_FOUND404No resource with this id exists for the authenticated tenant and key environment (live/sandbox) — including a cross-tenant or cross-environment id, which deliberately answers identically to an unrelated, nonexistent one.
IDEMPOTENCY_KEY_CONFLICT422The same Idempotency-Key was already used against this endpoint with a different request body.
INSUFFICIENT_BALANCE402The tenant has exhausted both its remaining quota and its prepaid balance for the current billing period.
PROVIDER_CAPACITY_EXCEEDED429The global or tenant provider-work capacity is full. No check or billing hold is accepted; retry after the interval named in Retry-After. With the durable provider flow each live check holds up to 10 provider slots until its lookups are frozen; the default tenant capacity is 1000 slots, i.e. 100 live checks awaiting provider work at once, which always admits one full 100-item batch.
PROVIDER_CAPACITY_REQUEST_TOO_LARGE422This single request needs more provider-work slots than the tenant or global capacity holds in total, so it can never be admitted: retrying does not help and no Retry-After is sent. No check or billing hold is accepted; split the request into smaller ones.
RULESET_UNAVAILABLE503No published scoring ruleset was reachable when the check ran — the check is never charged when this fires.
RATE_LIMIT_STORE_UNAVAILABLE503The rate-limit store is unreachable. The request is refused rather than served with no limit at all (fail-closed).
RATE_LIMIT_EXCEEDED429A rate limit — IP, caller, failed-authentication, the per-tenant rederive or address-report limiter, or the dedicated documentation limiter — was exceeded. Retry after the window named in Retry-After.
SERVICE_DRAINING503The instance is mid-shutdown and refusing new work; retry against another instance.
UPSTREAM_UNAVAILABLE503A dependency this request calls synchronously (DNS resolution for a webhook URL, a provider lookup) failed or timed out.
REPORT_RENDERING503The evidence report PDF is being generated and was not ready within the wait budget. Retry after the interval named in Retry-After; the link stays valid.
REPORT_RENDER_FAILED503The evidence report PDF could not be generated. The message carries the short report ID to quote to support; opening the link again after a short cooldown retries the render.
UNSUPPORTED_ASSET422The asset symbol is not in the screening engine's supported registry.
ADDRESS_REQUIRED422A transaction check left address out for an asset that cannot derive it (anything but TRX, ETH, BNB and BASE), or a sandbox key left it out of any transaction check. Send the customer's address in the transaction.
TRANSACTION_UNRESOLVED422A transaction check left address out and the transaction could not be read from its chain (no such transaction on that network, an undecodable recipient for a deposit, or the chain data source did not answer in time). Nothing is accepted or charged; check the hash and network, retry, or send the address.
PAYLOAD_TOO_LARGE413The request body exceeds the configured body-size limit.
IDEMPOTENCY_REQUEST_IN_FLIGHT409A request carrying this Idempotency-Key is already being processed by another request; retry shortly.
IDEMPOTENCY_LEASE_LOST409This request's idempotency lease was taken over by a concurrent retry before it finished; retry to read the recorded, published result.
CHECK_FAILED422The screening engine ran to completion but the check itself terminated in failure (e.g. an unrecoverable downstream provider error).
INTERNAL_ERROR500An unclassified server fault. Every 5xx this taxonomy does not otherwise name (and every internal-only CustomError code) collapses to this one on the wire — the raw message never leaves the server log.

The AMLBot-compatible endpoint​

The AMLBot-compatible endpoint keeps the convention of the API it reproduces: it answers HTTP 200 in every case and reports failures in its own result: false envelope; see Responses in the migration guide. The envelope and codes above apply to /v1.