> ## Documentation Index
> Fetch the complete documentation index at: https://documentation.kotanipay.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Account Validation

> Check a recipient exists — and that it belongs to who you think — before you send them money.

Paying the wrong account is expensive and usually irreversible. Account
validation lets you check a destination before you commit to it.

This page covers how to read a result and what to do about it. For request and
response fields, use the endpoint references, which are generated from the API
itself and always current:

<CardGroup cols={2}>
  <Card title="Validate mobile money" icon="mobile" href="/v3/api-reference/customers/validate-mobile-money">
    Phone numbers, paybills and tills
  </Card>

  <Card title="Validate bank account" icon="building-columns" href="/v3/api-reference/customers/validate-bank">
    Account number and bank code
  </Card>
</CardGroup>

## Two levels

There are two levels, and which you get depends on your account:

|                             | Available to                     | Tells you                                                                      |
| --------------------------- | -------------------------------- | ------------------------------------------------------------------------------ |
| **Format & corridor check** | Everyone                         | Whether the number is well formed and the corridor is supported                |
| **Name resolution**         | Accounts enabled for name lookup | Who the account actually belongs to, and whether that matches who you expected |

Both come from the same endpoints, with the same request shape. Without the
entitlement you get the format check and `nameLookupStatus: "NOT_ENABLED"`; with
it, the response also carries the resolved name. The request is identical either
way, so you don't need to change your integration when it's switched on.

Name resolution is enabled per account from our side — talk to your account
manager. Paybill and till validation depends on it, because a shortcode is just
digits: whether it exists is exactly what the lookup answers, and there is no
useful format check for one.

## You don't need to send the network

For mobile money, the network is resolved from the phone number's own prefix. The
response tells you which rail the lookup used and which operator the number
belongs to. Send it yourself only if the number has been ported, or if a response
tells you the network couldn't be determined.

## Which field answers which question

Four fields look similar and answer different questions. The reference describes
each one; what matters is how they relate:

| Field               | Question it answers                                    |
| ------------------- | ------------------------------------------------------ |
| `isSupported`       | Do we serve this country and rail at all?              |
| `isValid`           | Is this a usable destination?                          |
| `nameLookupStatus`  | Did we manage to resolve the name, and what came back? |
| `nameMatch.matched` | Is the holder the person you named?                    |

`isValid` starts from the number format, and is **overridden to `false`** when a
lookup proves the destination unusable — either nothing is there, or what's there
can't receive money. That's stronger evidence than any format check. When the
lookup couldn't complete, `isValid` keeps reporting the format result, so read
`nameLookupStatus` to see why.

<Warning>
  A name mismatch does **not** make `isValid` false. A real account that belongs to
  someone other than the name you sent is still a valid account — and usually the
  more urgent problem. Check `nameMatch.matched` as well as `isValid` before paying
  out.
</Warning>

## What to do about each outcome

Every non-matching `nameLookupStatus` comes with a `nameLookupReason` carrying the
network's own wording. Read the reason, not just the status — it's the difference
between "the account is closed" and "the system is busy".

| Status                | Retry? | What to do                                                                                         |
| --------------------- | ------ | -------------------------------------------------------------------------------------------------- |
| `MATCHED`             | —      | Proceed, checking `nameMatch` if you sent a name                                                   |
| `NOT_FOUND`           | No     | Nothing is there — have the customer check the number                                              |
| `ACCOUNT_UNAVAILABLE` | No     | It exists but is shut. **No typo to find** — the holder must resolve it with their bank or network |
| `UNSUPPORTED`         | No     | We can't resolve names on this corridor. Fall back to your own checks                              |
| `TIMEOUT`             | Yes    | Try again shortly                                                                                  |
| `ERROR`               | Yes    | The check didn't complete, so the account may be fine. Try again shortly                           |
| `NOT_ENABLED`         | No     | Name resolution isn't switched on for your account                                                 |

The three that get confused are `NOT_FOUND`, `ACCOUNT_UNAVAILABLE` and `ERROR`.
`NOT_FOUND` is an answer — the account isn't there. `ACCOUNT_UNAVAILABLE` is also
an answer, but a different one: sending your customer back to re-type a number
that was correct all along just wastes their time. `ERROR` is not an answer at
all, so treating it as a bad account turns a momentary outage into a lost payment.

<Note>
  `ACCOUNT_UNAVAILABLE` means we couldn't resolve a name at all. If we *did* resolve
  one and the account is merely restricted, you get `MATCHED` plus an
  `accountStatus` — the same warning, with the name attached.
</Note>

## Confirming the recipient is who you expect

Send the name you're expecting and the response scores it against the real one.

Names are compared token by token rather than as exact strings, because real
account names rarely come back in the format you sent them. `Jane M Doe`,
`Doe Jane Mary` and `Jane Mary Doe` all match `JANE MARY DOE`; `Robert Doe` does
not. The score is compared against a threshold configured for your account.

<Warning>
  When the response marks the name as masked, the provider returned only part of it,
  so the match is based on the visible portion alone. Treat that as "not
  contradicted" rather than confirmed.
</Warning>

## Blocking payouts on a name mismatch

If you'd rather not check every recipient yourself, we can enforce it for you:
your bank payouts get name-checked before dispatch, and any payout whose
recipient name doesn't match is failed instead of sent.

This is off by default and configured per account, including how close the match
has to be. Talk to your account manager if you want it enabled.

## Trying it out

Sandbox has [reserved test values](/v3/testing/sandbox-scenarios) that resolve to
frozen accounts, unresolvable accounts, mismatched names and provider timeouts, so
you can build against the failure cases directly.
