---
description: >-
  Validate a card without charging it using the Check Card Account API (v2). Returns structured AVS and CVC results for all cards, Visa ANI cardholder name verification for Visa cards, and OCT/AFT push/pull capability indicators.
---

# Check Card Account (ANI)

The Check Card Account API validates a card without processing a payment. It performs up to three verification checks plus card capability profiling and returns the results:

- **AVS** — Address Verification Service (billing address match) — all card networks
- **CVC** — Card Verification Code (security code match) — all card networks
- **AAV Cardholder Name** — Visa [Account Name Inquiry](https://corporate.visa.com/content/dam/VCOM/regional/na/us/support-legal/documents/account-name-inquiry-onesheet-merchant-version.pdf) (cardholder name match against issuer records) — **Visa cards only**

AVS and CVC verification are standard checks performed for any card. The cardholder name check is powered by Visa's **Account Name Inquiry (ANI)** product, which queries the issuing bank's records to verify that the name submitted matches the legal name on the account. ANI is only available for Visa cards — on any other network `verificationResults.aavCardholderName` carries no verdict.

The response also reports the card's **OCT** (push/payout) and **AFT** (funding/pull) capability so you can decide a card's eligibility for a given flow before initiating a transaction.

## What is Visa ANI?

[Account Name Inquiry (ANI)](https://corporate.visa.com/content/dam/VCOM/regional/na/us/support-legal/documents/account-name-inquiry-onesheet-merchant-version.pdf) is a Visa product that enables merchants to verify the cardholder's name directly with the issuing bank before or independently of a financial transaction. When a card is submitted, Visa forwards the provided first, middle, and last name to the issuer, who compares them against the legal name on the account and returns a match result.

Key details about Visa ANI:

- **Visa only** — ANI is a Visa network service. It is not available for Mastercard, Amex, Discover, or other networks.
- **Issuer participation** — ANI became mandatory for U.S. and Canadian Visa issuers in October 2023, meaning most Visa issuers in these markets support it. Some issuers outside these markets may not yet support ANI, in which case the result carries no verdict — `NOT_CHECKED` or `NOT_SUPPORTED`.
- **Name matching** — Visa checks first, middle, and last name components separately against the issuer's records. At minimum, the last name must be provided. The issuer returns the closest match when multiple names are on file (e.g., joint accounts).
- **Independent of payment** — ANI operates independently of the financial transaction. No funds are held or moved.
- **Exclusions** — Business/corporate cards and non-reloadable prepaid cards without a registered name are not supported for ANI.

## Use Cases

- **Onboarding** — Validate a customer's card before storing it for future charges
- **Fraud prevention** — Confirm the person submitting the card is the actual cardholder
- **KYC enhancement** — Cross-reference the name on the card with the name provided during identity verification
- **Payout eligibility** — Check `octStatus` before pushing funds to a card; check `aftStatus` before pulling (Visa cards only)
- **Card-on-file verification** — Check that a stored card is still valid and belongs to the expected person

## How It Works

```mermaid
sequenceDiagram
    participant B as Browser<br/>inyo.js
    participant S as Your server
    participant G as Inyo
    participant I as Issuer

    B->>G: Card data +<br/>cardholder name
    G-->>B: cardTokenId
    B->>S: cardTokenId
    S->>G: POST<br/>/v2/check-card-account
    G->>I: Zero-dollar CHECK:<br/>AVS + CVC
    opt Visa card
        G->>I: ANI cardholder<br/>name inquiry
    end
    I-->>G: Results
    G-->>S: verificationResults +<br/>octStatus / aftStatus
```

> **Note:** This API does **not** charge the card — it runs as a zero-dollar authorization, so no funds are held or moved. No `amount` is required in the request.

---

## Endpoint

```
POST https://{FQDN}/v2/check-card-account
```

**Headers:**

| Header | Value |
|---|---|
| `Authorization` | `Bearer {accessToken}` |
| `Content-Type` | `application/json` |

## Request Body

The payload is simplified compared to standard payment requests — no `amount`, `paymentType` or `capture` fields are needed. Internally the gateway runs the check as a zero-dollar `CHECK` authorization, so the request is validated against the endpoint schema **and** the shared card-payment schema. The tables below reflect the combined result.

### Root Object

| Field | Type | Required | Description |
|---|---|---|---|
| `externalPaymentId` | string | Yes | Your unique identifier for this validation request |
| `ipAddress` | string | Yes | Requester's IP address — must be a valid IPv4 or IPv6 literal |
| `sender` | object | Yes | Cardholder details (see below) |

### `sender` Object

| Field | Type | Required | Description |
|---|---|---|---|
| `firstName` | string | Yes | Cardholder's first name, at least 1 character (compared against issuer records via Visa ANI for Visa cards) |
| `lastName` | string | Yes | Cardholder's last name, at least 1 character (compared against issuer records via Visa ANI for Visa cards) |
| `address` | object | Yes | Billing address — what AVS compares (see below) |
| `paymentMethod` | object | Yes | Tokenized card details (see below) |
| `email` | string | No | Cardholder's email address — stored with the check record |
| `phoneNumber` | string | No | Cardholder's phone number — stored with the check record |

### `sender.address` Object

| Field | Type | Required | Description |
|---|---|---|---|
| `countryCode` | string | Yes | ISO Alpha-3 country code (e.g., `"USA"` — not `"US"`) |
| `stateCode` | string | Yes | State/province abbreviation (e.g., `"MA"`) |
| `city` | string | Yes | City name |
| `line1` | string | Yes | Street address line 1 |
| `zipCode` | string | Yes | Postal/ZIP code |
| `state` | string | No | State/province as kept on the stored address record — send the same value as `stateCode` |
| `line2` | string | No | Street address line 2 — send `""` when there is no second line |

### `sender.paymentMethod` Object

| Field | Type | Required | Description |
|---|---|---|---|
| `type` | string | Yes | `"CARD"` |
| `cardTokenId` | string | Yes | Token UUID from the [tokenizer](tokenizing-cards.md) — must be a well-formed UUID |

## Example Request

```bash
curl -X POST https://{FQDN}/v2/check-card-account \
  -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIs...' \
  -H 'Content-Type: application/json' \
  -d '{
    "externalPaymentId": "ANI-0001",
    "ipAddress": "203.0.113.42",
    "sender": {
      "firstName": "John",
      "lastName": "Smith",
      "email": "john.smith@example.com",
      "address": {
        "countryCode": "USA",
        "stateCode": "NY",
        "state": "NY",
        "city": "New York",
        "line1": "123 Main Street",
        "line2": "",
        "zipCode": "10001"
      },
      "paymentMethod": {
        "type": "CARD",
        "cardTokenId": "ab5fc589-8b48-4531-94c0-68b0629c13fe"
      }
    }
  }'
```

## Response

Three outcomes share HTTP `200` and they do **not** share a body shape. Branch on which fields are present, never on the status code alone.

### Verified (200)

The zero-dollar authorization was approved. `card`, `verificationResults`, `octStatus`, `aftStatus` and `redirectAcsUrl` are always present:

```json
{
  "card": {
    "created": "2026-05-16 16:37:40",
    "issuerName": "MODULR FS, LTD.",
    "issuerCountry": "UNITED KINGDOM",
    "bin": "446203",
    "lastFour": "0000"
  },
  "verificationResults": {
    "avs": "APPROVED",
    "cvc": "APPROVED",
    "aavCardholderName": "APPROVED"
  },
  "octStatus": {
    "capable": false,
    "highPerforming": false,
    "lowPerforming": false
  },
  "aftStatus": {
    "capable": true,
    "highPerforming": true,
    "lowPerforming": false
  },
  "redirectAcsUrl": ""
}
```

> **Repeat calls are served from the stored result.** Once ANI has run for a given `firstName` + `lastName` + card token, an identical call returns the recorded result instead of querying the issuer again. Changing either name queries the issuer afresh.

### 3DS Challenge (200)

The issuing bank requires cardholder authentication before the results are known. The body keeps the verified shape, `redirectAcsUrl` is populated, and every check reads `NOT_SUPPORTED` until the challenge completes:

```json
{
  "card": {
    "created": "2026-05-16 16:37:40",
    "issuerName": "MODULR FS, LTD.",
    "issuerCountry": "UNITED KINGDOM",
    "bin": "446203",
    "lastFour": "0000"
  },
  "verificationResults": {
    "avs": "NOT_SUPPORTED",
    "cvc": "NOT_SUPPORTED",
    "aavCardholderName": "NOT_SUPPORTED"
  },
  "octStatus": {
    "capable": false,
    "highPerforming": false,
    "lowPerforming": false
  },
  "aftStatus": {
    "capable": false,
    "highPerforming": false,
    "lowPerforming": false
  },
  "redirectAcsUrl": "https://{FQDN}/secure-code/start-challenge?token=dce568c6-..."
}
```

When `redirectAcsUrl` is non-empty, redirect the cardholder to it — and do not read the checks from this body, they carry no verdict yet. See [Handling 3D Secure](payment/pulling-funds/cards/authorizing/handling-3d-secure.md) for the complete flow.

### Declined (200)

When the underlying zero-dollar authorization is not approved, the endpoint answers with the **payment response shape** instead. There is no `verificationResults` object, and the check results arrive as flat fields:

```json
{
  "paymentId": "870bf24b-885f-4b1f-a8de-3c3944d5e266",
  "parentPaymentId": "870bf24b-885f-4b1f-a8de-3c3944d5e266",
  "externalPaymentId": "ANI-GMT9433682",
  "created": "2026-01-26 16:25:50",
  "amount": 0.00,
  "approved": false,
  "status": "DECLINED",
  "responseCode": "PAY_022",
  "message": "Rejected by provider",
  "issuerName": "MASTERCARD EUROPE",
  "issuerCountry": "BELGIUM",
  "avsResult": "N/A",
  "cvcResult": "N/A",
  "aavCardholderNameResult": "N/A",
  "redirectAcsUrl": ""
}
```

Test for `verificationResults` before reading it. Note that `N/A` can appear in these flat fields — it never appears inside `verificationResults`.

### Validation Error (400)

Every field that failed validation is reported at once:

```json
{
  "code": "VE_001",
  "message": "Validation error",
  "errors": [
    {
      "field": "sender.address.city",
      "message": "sender.address.city can't be empty"
    },
    {
      "field": "sender.paymentMethod.cardTokenId",
      "message": "sender.paymentMethod.cardTokenId does not match the expected format uuid"
    }
  ]
}
```

## Response Fields

Null fields are omitted from the response, so a card record with no issuer name simply has no `issuerName` key.

### `card`

| Field | Type | Description |
|---|---|---|
| `created` | string | Timestamp the check was created |
| `issuerName` | string | Issuing bank name |
| `issuerCountry` | string | Issuing country |
| `bin` | string | Bank Identification Number (first 6 digits) |
| `lastFour` | string | Last 4 digits of the card |

### `verificationResults`

| Field | Type | Description |
|---|---|---|
| `avs` | string | Address verification result (see below) |
| `cvc` | string | Card verification code result (see below) |
| `aavCardholderName` | string | Cardholder name verification result — Visa ANI, Visa only (see below) |

### `octStatus` / `aftStatus`

Card capability indicators, **populated for Visa cards only**. **OCT** (Original Credit Transaction) reflects the card's ability to *receive* pushes/payouts; **AFT** (Account Funding Transaction) reflects its ability to *fund* pulls.

| Field | Type | Description |
|---|---|---|
| `capable` | boolean | Whether the card supports this transaction type |
| `highPerforming` | boolean | Card is in the high-performing tier for this transaction type |
| `lowPerforming` | boolean | Card is in the low-performing tier for this transaction type |

> **For a non-Visa card the readiness lookup does not run at all**, so all three flags come back `false`. That means "not evaluated" — not "not capable".

### `redirectAcsUrl`

| Field | Type | Description |
|---|---|---|
| `redirectAcsUrl` | string | 3DS challenge URL. Empty (`""`) when no challenge is required; when populated, redirect the cardholder to complete authentication. |

## Understanding the Verification Results

The three values are the issuer/acquirer result descriptions resolved through the gateway's provider-code mapping, with one gateway-level normalization on top: a missing, empty, `NA` or `N/A` result becomes `NOT_SUPPORTED`. So `N/A` never reaches you inside `verificationResults`.

| Value | Meaning | Action |
|---|---|---|
| `APPROVED` | The issuer matched the submitted value | Treat as verified |
| `NOT_CHECKED` | The issuer or acquirer did not run this check | No signal — fall back to the other checks |
| `NOT_SUPPORTED` | No result came back: unsupported for this card, or a 3DS challenge is still pending | No signal |
| any other value | An issuer-specific result description, such as a partial or failed match | Do not treat as verified |

> **Only `APPROVED` is a pass.** The value set depends on the issuer and the acquirer, so gate on equality with `APPROVED` instead of enumerating failure strings.

### AVS Result (`verificationResults.avs`)

Compares the billing address in `sender.address` with the address on file at the card issuer. All five required address fields feed the comparison.

### CVC Result (`verificationResults.cvc`)

Validates the security code captured during tokenization. The Check Card Account request itself carries no security code — it travels inside the card token.

### AAV Cardholder Name Result (`verificationResults.aavCardholderName`)

**Visa cards only.** Verifies whether `sender.firstName` and `sender.lastName` match the legal name registered with the issuing bank, via Visa's [Account Name Inquiry (ANI)](https://corporate.visa.com/content/dam/VCOM/regional/na/us/support-legal/documents/account-name-inquiry-onesheet-merchant-version.pdf) service.

> **Important:** ANI runs for **Visa cards** only. For Mastercard, Amex, Discover and other networks this field carries no verdict — rely on AVS and CVC instead.

> **Name formatting:** the comparison is performed by the Visa issuer, which checks first, middle and last name components separately. Pass the name **exactly as it appears on the card**. Visa supports up to 35 characters per name field. Suffixes (Jr., III) and prefixes (Dr.) are not supported by ANI and should be omitted.

## Integration Example

### Step 1 — Tokenize the Card

Use `inyo.js` to collect and tokenize the card data client-side. The cardholder name entered in the `data-field="cardholder"` input is included in the token.

```javascript
const tokenizer = new InyoTokenizer({
  targetId: '#payment-form',
  publicKey: 'YOUR_PUBLIC_KEY',
  storeLaterUse: false,
  threeDSData: { enable: true, enablePostMessage: true },
  successCallback: (response) => {
    if (response.reasonCode === 'WAITING_TRANSACTION') {
      checkCardAccount(response.additionalData.token);
    }
  },
  errorCallback: (err) => console.error('Tokenization failed:', err)
});
```

### Step 2 — Call Check Card Account

Send the token to your backend, which calls the Check Card Account API:

```javascript
async function checkCardAccount(cardTokenId) {
  const response = await fetch('https://{FQDN}/v2/check-card-account', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${accessToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      externalPaymentId: `ANI-${crypto.randomUUID()}`,
      ipAddress: customerIpAddress,
      sender: {
        firstName: 'John',
        lastName: 'Smith',
        email: 'john.smith@example.com',
        address: {
          countryCode: 'USA',
          stateCode: 'NY',
          state: 'NY',
          city: 'New York',
          line1: '123 Main Street',
          line2: '',
          zipCode: '10001'
        },
        paymentMethod: {
          type: 'CARD',
          cardTokenId: cardTokenId
        }
      }
    })
  });

  const result = await response.json();

  if (result.redirectAcsUrl) {
    // Handle 3DS — redirect or open iframe
    window.open(result.redirectAcsUrl, '_blank');
    return;
  }

  // Evaluate the structured verification signals
  console.log('AVS:', result.verificationResults.avs);
  console.log('CVC:', result.verificationResults.cvc);
  console.log('ANI (Name):', result.verificationResults.aavCardholderName);
  console.log('Push (OCT) capable:', result.octStatus?.capable);
  console.log('Pull (AFT) capable:', result.aftStatus?.capable);
}
```

### Step 3 — Evaluate Results

Branch on the body shape first, then on the checks. Only `APPROVED` is a pass, and `aavCardholderName` carries a verdict for Visa cards only.

```javascript
function evaluateCardCheck(result) {
  // Declined: the payment response shape, with no verificationResults object
  if (!result.verificationResults) {
    return { accept: false, reason: result.message || `Declined (${result.responseCode})` };
  }

  // 3DS challenge pending — the checks carry no verdict yet
  if (result.redirectAcsUrl) {
    return { accept: false, reason: '3DS challenge required' };
  }

  const { avs, cvc, aavCardholderName } = result.verificationResults;
  const passed = (v) => v === 'APPROVED';
  const noSignal = (v) => v === 'NOT_CHECKED' || v === 'NOT_SUPPORTED';

  // Anything that is neither APPROVED nor a no-signal value is a mismatch
  if (!passed(cvc) && !noSignal(cvc)) {
    return { accept: false, reason: `CVC did not match (${cvc})` };
  }
  if (!passed(aavCardholderName) && !noSignal(aavCardholderName)) {
    return { accept: false, reason: `Cardholder name did not match issuer records (${aavCardholderName})` };
  }
  if (!passed(avs) && !noSignal(avs)) {
    return { accept: false, reason: `AVS did not match (${avs})` };
  }

  const verified = [['AVS', avs], ['CVC', cvc], ['ANI', aavCardholderName]]
    .filter(([, value]) => passed(value))
    .map(([name]) => name);

  return verified.length > 0
    ? { accept: true, reason: `Verified by ${verified.join(' + ')}` }
    : { accept: true, reason: 'No check returned a result — apply your own risk logic' };
}
```

## Testing

Use the following test card in sandbox for Check Card Account:

| Card Number | Network | Description |
|---|---|---|
| `4462 0300 0000 0000` | Visa Debit | Standard ANI test card |

Use the cardholder name `AUTHORISED` to simulate an approved result. See [Test Data — Cards](test-data/cards.md) for CVV and AVS simulation values.

## Best Practices

1. **Send the whole billing address** — `countryCode`, `stateCode`, `city`, `line1` and `zipCode` are all required, and they are exactly what AVS compares. Send `state` alongside `stateCode` so the stored address record carries it too.

2. **Match the tokenizer name to the API name** — The `cardholder` field in the tokenizer form should contain the same name you pass as `sender.firstName` and `sender.lastName`. For Visa ANI, a mismatch between the tokenized name and the API name makes the issuer return a no-match result.

3. **Gate on `APPROVED`, not on failure strings** — The result values come from the issuer and the acquirer, so the failure vocabulary is not fixed. Accept only `APPROVED`; treat every other value that is not `NOT_CHECKED` or `NOT_SUPPORTED` as a mismatch.

4. **Handle `NOT_CHECKED` and `NOT_SUPPORTED` gracefully** — Neither is a failure. Both mean the check produced no result, whether because the card is not Visa, the issuer does not run the check, or a 3DS challenge is still pending. Fall back to the checks that did return a result.

5. **`octStatus` / `aftStatus` are Visa-only** — For a non-Visa card the readiness lookup never runs and all three flags read `false`. Do not read that as "this card cannot receive a payout".

6. **Branch on the body shape, not the status code** — A declined check answers HTTP `200` with the payment response shape and no `verificationResults`. Test for that object before reading it.

7. **Send `externalPaymentId` and `ipAddress`** — Both are required, and the IP must be a valid IPv4 or IPv6 literal. A missing or malformed value fails validation with HTTP `400` before the card is ever contacted.

8. **Use before storing cards** — Run a Check Card Account call before saving a card token with `storeLaterUse: true` to confirm the card is valid and belongs to the expected person.

9. **Use the legal name on the card** — Visa ANI compares against the legal name the issuer has on file, not nicknames or preferred names. Omit suffixes (Jr., III) and prefixes (Dr.), which ANI does not support.

## What's Next

- [Tokenizing Cards](tokenizing-cards.md) — Set up client-side card tokenization
- [Handling AVS / CVC](payment/pulling-funds/cards/authorizing/handling-avs-cvc.md) — Deep dive into address and security code verification
- [Handling 3D Secure](payment/pulling-funds/cards/authorizing/handling-3d-secure.md) — Handle CHALLENGE responses
- [Authorizing a Card Payment](payment/pulling-funds/cards/authorizing/) — Create actual payment transactions after validation
