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:

HTTPcURLRequest body

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
  }
}

How it works

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

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.

Error codes

When a locked card attempts verification, the request returns 400 with a verification.* errorCode: verification.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_locked Permanent lock: verification.attempts_locked_permanent

Unlocking a card

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

HTTPcURLRequest body

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.

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.