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_CHECKEDorNOT_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
octStatusbefore pushing funds to a card; checkaftStatusbefore 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
amountis 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 β 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
| 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
APPROVEDis a pass. The value set depends on the issuer and the acquirer, so gate on equality withAPPROVEDinstead 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 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 for CVV and AVS simulation values.
Best Practices
Send the whole billing address β
countryCode,stateCode,city,line1andzipCodeare all required, and they are exactly what AVS compares. SendstatealongsidestateCodeso the stored address record carries it too.Match the tokenizer name to the API name β The
cardholderfield in the tokenizer form should contain the same name you pass assender.firstNameandsender.lastName. For Visa ANI, a mismatch between the tokenized name and the API name makes the issuer return a no-match result.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 onlyAPPROVED; treat every other value that is notNOT_CHECKEDorNOT_SUPPORTEDas a mismatch.Handle
NOT_CHECKEDandNOT_SUPPORTEDgracefully β 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.octStatus/aftStatusare Visa-only β For a non-Visa card the readiness lookup never runs and all three flags readfalse. Do not read that as "this card cannot receive a payout".Branch on the body shape, not the status code β A declined check answers HTTP
200with the payment response shape and noverificationResults. Test for that object before reading it.Send
externalPaymentIdandipAddressβ Both are required, and the IP must be a valid IPv4 or IPv6 literal. A missing or malformed value fails validation with HTTP400before the card is ever contacted.Use before storing cards β Run a Check Card Account call before saving a card token with
storeLaterUse: trueto confirm the card is valid and belongs to the expected person.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 β Set up client-side card tokenization
- Handling AVS / CVC β Deep dive into address and security code verification
- Handling 3D Secure β Handle CHALLENGE responses
- Authorizing a Card Payment β Create actual payment transactions after validation
