Verification Attempt Lockout

Introduction

The verification attempt lockout is an opt-in, account-scoped safeguard that throttles a card after repeated hard verification failures. It applies across every network.

📘

This is not the HIGHEST lockout

This page covers the cross-network attempt lockout for the LOW/MEDIUM/HIGH tiers. The
HIGHEST tier has its own, separate per-card lockout (the two-hold amount-confirm) — see
HIGHEST Verification. The two never cross-feed.

Enabling it

The lockout is off by default. Turn it on per subaccount with the failedAttemptLockout flag on verificationPolicy, using the Update Subaccount endpoint:

PATCH /subaccounts/{subaccountId} HTTP/1.1
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json

{
  "verificationPolicy": {
    "failedAttemptLockout": true
  }
}
curl -X PATCH https://api.astrada.co/subaccounts/{subaccountId} \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "verificationPolicy": {
      "failedAttemptLockout": true
    }
  }'
{
  "verificationPolicy": {
    "failedAttemptLockout": true
  }
}
  • Failures are always recorded — the flag only controls enforcement. So you can flip it on
    and it is immediately protective, with no warm-up window.
  • The policy is per subaccount; set it on each subaccount you want protected. Set
    failedAttemptLockout to false (or null) to turn it off.

How it works

The lockout is keyed to the card, account-scoped, and counts countable hard failures across all networks. There are two tiers:

  • Temporary lock5 hard failures inside a 60-minute window block the card for
    60 minutes, then it auto-clears. The window is fixed, not rolling: it starts at the first
    failure and resets only once a failure arrives 60+ minutes after that start — it doesn't
    continuously slide with each new failure.
  • Permanent lock15 cumulative hard failures (over any span of time) block the card with
    no auto-expiry; it stays locked until you clear it.

What counts: hard declines (insufficient funds, stolen / lost / restricted card, contact-issuer), incorrect CVC, and 3DS authentication rejected by the issuer.

What doesn't: transient errors ("try again later"), cardholder-abandoned or canceled attempts, and the HIGHEST second-factor steps.

flowchart LR
  A[Active] -->|5 hard fails in 60 min| B[Temporary lock<br/>60 min]
  B -->|auto-expires| A
  B -->|POST /card-verifications/unlock| A
  A -->|15 cumulative hard fails| C[Permanent lock]
  C -->|POST /card-verifications/unlock| A

Error codes

When a locked card attempts verification, the request returns 400 with a verification.* errorCodeverification.attempts_locked (temporary; carries metadata.lockedUntil) or verification.attempts_locked_permanent (no lockedUntil). Full table (category, retryable, the cardholder-facing screen for each) in Error States & Remediation — that page is the canonical error catalog; this one owns the mechanism.

What the cardholder sees

The SDK renders both lockout screens for you — there's no UI to build. Each lock tier shows its own screen so the cardholder knows whether to wait or to get help.

Temporary lock — verification.attempts_lockedPermanent lock — verification.attempts_locked_permanent
  • Temporary — "Verification temporarily blocked." Tells the cardholder to wait and try again
    later; the lock auto-clears at metadata.lockedUntil.
  • Permanent — "Verification blocked." Directs the cardholder to contact their spend-management
    provider (you); clear it with POST /card-verifications/unlock below.

Unlocking a card

Clear a card's lockout — temporary or permanent — with the Unlock Card Verification endpoint:

POST /card-verifications/unlock HTTP/1.1
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json

{
  "cardId": "00cdba2d-01f0-46bb-b34a-c76d9699e991"
}
curl -X POST https://api.astrada.co/card-verifications/unlock \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "cardId": "00cdba2d-01f0-46bb-b34a-c76d9699e991"
  }'
{
  "cardId": "00cdba2d-01f0-46bb-b34a-c76d9699e991"
}

Returns 200 with { "unlocked": true } (plus vaultCardFingerprint when a lock was actually cleared). It is idempotent — unlocking a card with no active lock succeeds and clears nothing.

📘

Token scope

Unlocking requires subaccounts:write — the same scope used to manage a subaccount's policy —
not card-verifications:write (which creates verifications). A token without
subaccounts:write is rejected with 403.

  • The endpoint clears both the temporary and the permanent lock. (The temporary lock also clears
    on its own once the 60-minute window passes.)
  • It doesn't touch the HIGHEST lockout — see the note at the top of this page.