Inyo

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 (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) 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

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:

HeaderValue
AuthorizationBearer {accessToken}
Content-Typeapplication/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

FieldTypeRequiredDescription
externalPaymentIdstringYesYour unique identifier for this validation request
ipAddressstringYesRequester's IP address β€” must be a valid IPv4 or IPv6 literal
senderobjectYesCardholder details (see below)

sender Object

FieldTypeRequiredDescription
firstNamestringYesCardholder's first name, at least 1 character (compared against issuer records via Visa ANI for Visa cards)
lastNamestringYesCardholder's last name, at least 1 character (compared against issuer records via Visa ANI for Visa cards)
addressobjectYesBilling address β€” what AVS compares (see below)
paymentMethodobjectYesTokenized card details (see below)
emailstringNoCardholder's email address β€” stored with the check record
phoneNumberstringNoCardholder's phone number β€” stored with the check record

sender.address Object

FieldTypeRequiredDescription
countryCodestringYesISO Alpha-3 country code (e.g., "USA" β€” not "US")
stateCodestringYesState/province abbreviation (e.g., "MA")
citystringYesCity name
line1stringYesStreet address line 1
zipCodestringYesPostal/ZIP code
statestringNoState/province as kept on the stored address record β€” send the same value as stateCode
line2stringNoStreet address line 2 β€” send "" when there is no second line

sender.paymentMethod Object

FieldTypeRequiredDescription
typestringYes"CARD"
cardTokenIdstringYesToken UUID from the tokenizer β€” must be a well-formed UUID

Example Request

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:

{
  "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:

{
  "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 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:

{
  "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:

{
  "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

FieldTypeDescription
createdstringTimestamp the check was created
issuerNamestringIssuing bank name
issuerCountrystringIssuing country
binstringBank Identification Number (first 6 digits)
lastFourstringLast 4 digits of the card

verificationResults

FieldTypeDescription
avsstringAddress verification result (see below)
cvcstringCard verification code result (see below)
aavCardholderNamestringCardholder 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.

FieldTypeDescription
capablebooleanWhether the card supports this transaction type
highPerformingbooleanCard is in the high-performing tier for this transaction type
lowPerformingbooleanCard 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

FieldTypeDescription
redirectAcsUrlstring3DS 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.

ValueMeaningAction
APPROVEDThe issuer matched the submitted valueTreat as verified
NOT_CHECKEDThe issuer or acquirer did not run this checkNo signal β€” fall back to the other checks
NOT_SUPPORTEDNo result came back: unsupported for this card, or a 3DS challenge is still pendingNo signal
any other valueAn issuer-specific result description, such as a partial or failed matchDo 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) 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.

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:

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.

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 NumberNetworkDescription
4462 0300 0000 0000Visa DebitStandard ANI test card

Use the cardholder name AUTHORISED to simulate an approved result. See Test Data β€” Cards 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