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 lock: 5 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 lock: 15 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.* 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_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.

Did this page help you?