Skip to main content

Errors and retries

Every non-2xx response uses the same envelope:

{
"error": {
"type": "validation_failed",
"message": "image must be at least 256×256",
"request_id": "req_2f8d4c91d9c14bc2b0f8a4d62a3b7c11",
"param": "image",
"doc_url": "https://docs.drukverify.com/errors/validation_failed"
}
}

type is the machine-readable error code. request_id uniquely identifies the failing request — include it when contacting support.

Common types

TypeHTTP statusMeaning
invalid_credentials401The dv_sk_* is wrong or revoked. Terminal.
token_expired401The dv_tok_* is past its TTL. Refresh and retry.
invalid_token401The dv_tok_* was revoked or never existed. Re-mint.
validation_failed400A request field is malformed. param names the field.
payload_too_large413Image bytes exceed MAX_IMAGE_BYTES. Resize client-side.
rate_limited429Per-key rate limit hit. Sleep retry_after_ms, retry.
payment_required402Tenant is over plan limits or has a billing issue.
internal_error5xxPlatform fault. Safe to retry with backoff.

SDK error handling

Both SDKs wrap the envelope in a typed error class.

Go:

result, err := client.Liveness.Check(ctx, req)
if err != nil {
var ekycErr *ekyc.Error
if errors.As(err, &ekycErr) {
switch ekycErr.Code {
case ekyc.ErrCodeRateLimited:
time.Sleep(ekycErr.RetryAfter)
case ekyc.ErrCodeTokenExpired:
// Don't retry; SDK already refreshed and retried once.
// If you see this, the new token is also expired.
}
log.Printf("ekyc error %s (request_id=%s): %s",
ekycErr.Code, ekycErr.RequestID, ekycErr.Message)
}
}

Flutter:

try {
final r = await sdk.liveness.check(/* ... */);
} on EkycError catch (e) {
if (e.code == EkycErrorCode.rateLimited && e.retryAfter != null) {
await Future.delayed(e.retryAfter!);
}
print('ekyc error ${e.code} (requestId=${e.requestId}): ${e.message}');
}

Retry policy

The SDK retries network errors and 5xx automatically (3 attempts, exponential backoff). It does NOT retry 4xx — those are your bug, not a transient blip. Specifically:

  • 5xx: SDK retries with the same Idempotency-Key, fresh X-Request-Id per attempt.
  • 401 with token_expired / invalid_token (Flutter only): one refresh + one retry. Subsequent failures surface to caller.
  • 401 with invalid_credentials (Go): terminal. Fix your config.
  • 429: NOT auto-retried. Surface to caller with RetryAfter populated.