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

# Beneficiary lookup

> Find the institution code you must route on, and confirm who holds the account.

Two lookups belong before every payout: which institution to route to, and who
actually holds the account. Both need the `identity:enquiry` scope, and neither
writes anything.

## Why the code matters

`destination_provider_name` is a **display name**. It is stored on the payout and
shown back to you, and it is never used to route money.

`destination_provider_code` is the machine code the processor routes on — a bank
code for `BANK_ACCOUNT`, a mobile money code for `MOBILE_MONEY`. It is required for
both, and a payout without it is rejected at validation.

<Warning>
  Do not hardcode codes, and do not derive them from a bank's name. They are set by
  the upstream institution directory, differ in format between countries, and can
  change without a version bump on our side. Read them from `GET /v1/banks` and
  cache them for hours, not months.
</Warning>

## List payable institutions

```bash theme={null}
curl "$SPENDIN_BASE_URL/v1/banks?country=NG&currency=NGN&destination_type=BANK_ACCOUNT" \
  -H "X-API-Key: $SPENDIN_API_KEY"
```

```json theme={null}
[
  {
    "provider_name": "ACCESS BANK",
    "provider_code": "044",
    "destination_type": "BANK_ACCOUNT"
  },
  {
    "provider_name": "GUARANTY TRUST BANK",
    "provider_code": "058",
    "destination_type": "BANK_ACCOUNT"
  }
]
```

| Parameter          | Required | Notes                                           |
| ------------------ | -------- | ----------------------------------------------- |
| `country`          | Yes      | ISO 3166-1 alpha-2, e.g. `NG`, `GH`, `KE`, `ZA` |
| `currency`         | Yes      | ISO 4217, e.g. `NGN`, `GHS`                     |
| `destination_type` | No       | `BANK_ACCOUNT` (default) or `MOBILE_MONEY`      |

Rows come back sorted by name, and every row is guaranteed to carry a usable code —
entries the upstream directory returns without one are dropped rather than handed to
you to fail on at dispatch.

<Note>
  Banks and mobile money providers are **never mixed** in one response, even though
  they come from the same upstream directory. Ask for the type you intend to pay.
  Requesting a directory for `CRYPTO_WALLET` is a `400` — chains have no
  institutions.
</Note>

The list is cached for about six hours and is identical for every merchant. An
empty result is never cached, so an unconfigured corridor or a bad upstream day
does not look like "this country has no banks" for the rest of the day.

## Confirm the account holder

Name enquiry, so you find out about a wrong account number before money moves.

```bash theme={null}
curl -X POST "$SPENDIN_BASE_URL/v1/banks/resolve" \
  -H "X-API-Key: $SPENDIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "destination_type": "BANK_ACCOUNT",
    "destination_currency": "NGN",
    "destination_account_unique": "0123456789",
    "destination_provider_code": "058"
  }'
```

```json theme={null}
{
  "destination_account_name": "ACME LIMITED",
  "destination_account_unique": "0123456789",
  "destination_provider_code": "058",
  "destination_type": "BANK_ACCOUNT"
}
```

The request and response fields are named **exactly** as the payout request expects
them, so the normal flow is: resolve, then spread the result into your payout body
along with the confirmed name.

```typescript theme={null}
const resolved = await resolve({
  destination_type: 'BANK_ACCOUNT',
  destination_currency: 'NGN',
  destination_account_unique: account_number,
  destination_provider_code: bank_code,
});

await createPayout({
  ...resolved,                    // account, code, type all carry over
  destination_country: 'NG',
  destination_amount: 250000,
  destination_provider_name: 'GUARANTY TRUST BANK',
  destination_account_name: resolved.destination_account_name,   // confirmed, not typed
  settlement_currency: 'USDT',
});
```

<Warning>
  Use the **returned** name as `destination_account_name`, not what your user
  typed. It is what the institution holds, and it is what a dispute or a
  reconciliation will be judged against.
</Warning>

### Why this is a POST

It reads rather than writes, but the account number is PII and has no business
sitting in a query string that gets written to every access log between you and us.
For the same reason it carries no `Idempotency-Key` requirement — there is nothing
to make idempotent.

### Caching

Results are cached about five minutes per account. Name enquiry APIs are
rate-limited upstream and the same account often appears across several payouts in
a session, so a tight loop over the same beneficiary costs one upstream call rather
than many.

## Errors

| Code                              | Status | Meaning                                                      |
| --------------------------------- | ------ | ------------------------------------------------------------ |
| `INVALID_DESTINATION_ACCOUNT`     | `422`  | No account exists with those details                         |
| `DESTINATION_TYPE_NOT_ENQUIRABLE` | `400`  | Crypto destinations have no directory and cannot be resolved |
| `LIQUIDITY_ENGINE_UNAVAILABLE`    | `503`  | The upstream directory or enquiry service is unreachable     |

An institution that returns a blank name is treated as
`INVALID_DESTINATION_ACCOUNT` rather than handed back as an empty name — a blank
`destination_account_name` written onto a payout would fail at dispatch for a much
less obvious reason.

A `503` is transient; retry with backoff. Note that a Redis outage does **not**
surface as an error here — the cache is a latency optimisation, and a failed cache
read falls through to a direct upstream call.

<Note>
  Resolving is not yet enforced as a pre-flight on payout creation, so a payout
  with an unverifiable account number is currently accepted and fails later at
  dispatch instead. Calling `/v1/banks/resolve` yourself is what turns that late,
  confusing failure into an immediate, actionable one — treat it as required even
  though we do not yet reject without it.
</Note>

## Crypto destinations

`CRYPTO_WALLET` payouts have neither a directory nor a name to confirm — chains
have no account names. Omit `destination_provider_code` and
`destination_account_name` entirely, and validate the address yourself before
creating the payout. There is no recovery from a wrong address.
