# Initiate EUR Bank Deposit

Assign the user a virtual IBAN: EUR bank transfers to it are converted by the bank ramp into crypto and sent to an on-chain address.

## Overview

This endpoint creates a bank ramp *bank deposit → on-chain address* route for the user and returns the bank details to show them. There is no amount and no payment reference: the user can send any number of SEPA transfers to the IBAN. Each transfer appears in [Get Deposits](https://docs.aureahub.com/docs/payin-deposits.md) once the bank ramp reports it, and the converted crypto is sent to `destinationAddress`.

- **Pay-in must be switched on** for your tenant in the pair's environment ([Tenant Settings](https://docs.aureahub.com/docs/bank-settings.md)), otherwise `403` with `details.code` `NOAH_FUNCTION_OFF`, before anything is read or sent. It is checked before an `Idempotency-Key` is replayed, so a key sent again after the switch-off is refused too.
- **The pair must be offered.** `cryptoCurrency` on `network` must be a pay-in pair of [Currencies & Networks](https://docs.aureahub.com/docs/bank-currencies.md), which lists the pairs your tenant offers. Aurea enables today, in production, `EURC` on `Base` and `Solana`; `USDC` on `Base`, `Celo`, `Ethereum`, `PolygonPos` and `Solana`; `USDT` on `Celo` and `Ethereum`; and `PYUSD`, `USDG` and `USDPT` on `Solana`; in the sandbox, `EURC_TEST`, `PYUSD_TEST`, `USDC_TEST` or `USDG_TEST` on `SolanaDevnet`, `USDC_TEST` on `PolygonTestAmoy` or `CeloTestSepolia`, and `PYUSD_TEST` on `FlowEvmTest`. Any other pair answers `400` with `details.code` `NOAH_PAIR_UNAVAILABLE` and the `available` pairs your tenant offers, before anything is sent to the bank ramp.
- **Sandbox or production is taken from `network`.** The bank ramp's test networks (`SolanaDevnet`, `PolygonTestAmoy`, `CeloTestSepolia`, `FlowEvmTest`) use the user's sandbox profile and the bank ramp's sandbox; mainnets (`Solana`, `PolygonPos`) use the production profile and credentials. If you also send `isTestnet`, it must agree with the network, otherwise `400` `NOAH_ENVIRONMENT_MISMATCH`. The defaults are the sandbox route `EURC_TEST` on `SolanaDevnet`, so always set both fields in production.
- **Names.** Use the bank ramp's names, as Currencies & Networks lists them. `Polygon` and `Sepolia` are still read as `PolygonPos` and `EthereumTestSepolia`, and on a sandbox network an asset code is read as its sandbox code (`EURC` on `SolanaDevnet` is `EURC_TEST`). The response and the stored payment method carry the bank ramp's names.
- The user must have completed the bank ramp's onboarding, with KYC approved, in that environment ([Onboarding Status](https://docs.aureahub.com/docs/payin-status.md) with the matching `isTestnet`). Without a profile there the call answers `404`; with onboarding or KYC incomplete, `422`.
- **Destination.** If you leave out `destinationAddress`, a Solana route uses the user's Solana wallet of the same environment — the Devnet wallet for `SolanaDevnet`, the mainnet wallet for `Solana` — and answers `400` when the user has none. An EVM route (such as `PolygonTestAmoy`) has no default: without `destinationAddress` it answers `400` `destinationAddress is required for network …`. The default is an Aurea wallet, so with the standalone mode send the proven address.
- **The destination must be an address your tenant delivers to** (`modes` in [Tenant Settings](https://docs.aureahub.com/docs/bank-settings.md)):

  - with the *Aurea wallets* mode, one of the user's Aurea wallets: a wallet Aurea created for the user, holds a key share of, or the user registered from their device. A watch-only import is not one, because it proves nothing about who controls the address;
  - with the *standalone* mode, an address the user proved they own in the network's environment and has not revoked ([Standalone Pay-In](https://docs.aureahub.com/docs/guide-standalone-payin.md));
  - with both, either.

  An EVM address matches on any EVM chain and in any letter case. Any other address answers `400` with `details.code` `NOAH_DESTINATION_NOT_ALLOWED`, and nothing is sent to the bank ramp.
- EVM routes also need a primary EVM wallet in Aurea, unless your tenant has the standalone mode in that environment; Solana routes never do. For your app to show what arrives on an EVM network, your tenant needs that network enabled and its tokens added from the catalog; Aurea sets both up for your tenant.
- Aurea treats the assignment as valid for about a day (`expiresAt`): call this endpoint again to refresh it before showing the IBAN again.
- **Business fee.** When your tenant has a pay-in fee in that environment (`fees` in [Tenant Settings](https://docs.aureahub.com/docs/bank-settings.md)), Aurea sends it to the bank ramp with the request, for every bank payment method type — the type's own fee, or else the fee for every other type — and the bank ramp takes it from each deposit. Without a fee nothing is added. A change to the fee is sent with the next call.
- **What the bank ramp answered.** `virtualAccountId` is the bank ramp's id of the virtual account; `paymentMethodType` its primary rail (`BankSepa` for EUR); `fee` the bank ramp's fee for each deposit on it (`pct`, `base`, `min` as exact decimal text, in `currency`); `reference` the bank ramp's reference, as sent — the bank ramp doesn't document its use; `relatedPaymentMethods` the account's other rails, each with its own `accountNumber`, `bankCode` (ABA routing number for Fedwire and ACH, BIC for SWIFT) and fee, empty when there are none. A value the bank ramp didn't send in its documented form is `null`, or the rail is left out. When the bank ramp returns the same payment method again, its newest answer is kept, and a value the bank ramp no longer sends stays.
- **The account's currency.** `fiatCurrency` is the currency the account was asked in, and the one Aurea keeps for it — a USD account is listed as USD by [Payment Methods](https://docs.aureahub.com/docs/payin-methods.md) and paid in dollars by [Simulate Deposit](https://docs.aureahub.com/docs/sandbox-deposit.md). Asking again refreshes an account stored before with another currency.
- An answer from the bank ramp without its payment method id or account number answers `502` with `details.code` `NOAH_UNEXPECTED_RESPONSE`, and nothing is stored.
- `POST /v1/ramp/bank/payin/initiate-deposit` is an alias with the same body and response.
- **Retries.** Send an `Idempotency-Key` header to make a retry safe: the same key with the same body answers with the first answer, and the response header `idempotency-replayed` is `true`. See [Idempotency](https://docs.aureahub.com/docs/idempotency.md). The alias and this endpoint share the same keys.

## Endpoint

### `POST /v1/ramp/bank/payin/initiate`

Authentication: bearer token required.

Creates (or refreshes) the user's virtual IBAN for a crypto route and returns the bank details.

**Headers**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `Idempotency-Key` | string | no | Optional. Up to 255 letters, digits, - or _. The same key with the same body answers with the first answer instead of doing it again; see [Idempotency](https://docs.aureahub.com/docs/idempotency.md) |

**Request body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `cryptoCurrency` | string | no | the bank ramp currency code of an enabled pay-in pair, e.g. EURC or EURC_TEST (default EURC_TEST). On a sandbox network an asset code such as EURC is read as EURC_TEST |
| `network` | string | no | the bank ramp network name of that pair, e.g. Solana or SolanaDevnet (default SolanaDevnet). Also selects sandbox or production |
| `destinationAddress` | string | no | Address that receives the crypto (26-255 characters), in the network's address format. Required in practice outside the sandbox |
| `isTestnet` | boolean | no | Optional check: when sent, must match the network's environment, otherwise 400 NOAH_ENVIRONMENT_MISMATCH |
| `fiatCurrency` | string | no | EUR, USD or GBP — the account's currency, which decides the banking rail the bank ramp uses: **EUR is SEPA, USD is ACH/Fedwire, GBP the local rail**. Default EUR. A user whose country is not eligible for that rail is refused by the bank ramp with 403 and `details.code` `NOAH_FORBIDDEN`, carrying the bank ramp's `denyReasons` — the bank ramp's country-by-rail policy decides it, not Aurea |

**Responses**

`201` Created

```json
{
  "paymentMethodId": "7a1d3c5e-9b2f-4e6a-8c0d-1f3b5d7e9a2c",
  "noahPaymentMethodId": "<bank payment method ID>",
  "paymentMethodType": "BankSepa",
  "virtualAccountId": "<the bank ramp virtual account ID>",
  "accountHolderName": "<account holder>",
  "iban": "<virtual IBAN>",
  "bic": "<BIC>",
  "bankName": "<bank name>",
  "bankAddress": { "street": "<street>", "street2": null, "city": "<city>", "postalCode": "<postal code>", "state": "<state>", "country": "<country>" },
  "reference": null,
  "fee": { "currency": "EUR", "pct": "0", "base": "0", "min": "0" },
  "relatedPaymentMethods": [],
  "fiatCurrency": "EUR",
  "cryptoCurrency": "EURC",
  "network": "Solana",
  "destinationAddress": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
  "status": "active",
  "expiresAt": "2026-09-12T08:30:00.000Z",
  "message": "Virtual IBAN assigned successfully. Please note: IBAN expires in 24 hours and must be refreshed."
}
```

`422` Not onboarded

```json
{
  "statusCode": 422,
  "error": "ValidationError",
  "message": "Customer onboarding not completed or KYC not approved",
  "details": { "onboardingStatus": "pending", "kycStatus": "pending" }
}
```

`400` No EVM wallet

```json
{
  "statusCode": 400,
  "error": "BadRequestError",
  "message": "A primary EVM wallet is required to initiate a bank deposit. Please create or import an EVM wallet in the app first."
}
```

`403` Pay-in switched off

```json
{
  "statusCode": 403,
  "error": "ForbiddenError",
  "message": "The bank ramp's pay-in is not switched on for this tenant in sandbox.",
  "details": { "code": "NOAH_FUNCTION_OFF", "function": "payin", "environment": "sandbox" }
}
```

`400` Not an Aurea wallet

```json
{
  "statusCode": 400,
  "error": "BadRequestError",
  "message": "A pay-in is delivered to one of your Aurea wallets, and this address is not one of them.",
  "details": { "code": "NOAH_DESTINATION_NOT_ALLOWED", "environment": "sandbox" }
}
```

`400` Not a proven address

```json
{
  "statusCode": 400,
  "error": "BadRequestError",
  "message": "A pay-in is delivered to an address you proved you own, and this address is not one of them: prove it first with POST /v1/ramp/bank/addresses/challenge.",
  "details": { "code": "NOAH_DESTINATION_NOT_ALLOWED", "environment": "production" }
}
```

`404` No customer

```json
{ "statusCode": 404, "error": "NotFoundError", "message": "Customer not found. Please onboard first." }
```

`502` the bank ramp's answer incomplete

```json
{
  "statusCode": 502,
  "error": "NoahApiError",
  "message": "The bank ramp answered with an unexpected response (HTTP 200)",
  "details": { "code": "NOAH_UNEXPECTED_RESPONSE", "noahStatus": 200 }
}
```

Other `400` messages: `Invalid destination address format` when the address doesn't match the network, and `No Solana Devnet wallet found…` or `No Solana mainnet wallet found…` when `destinationAddress` is missing on a Solana network and the user has no Solana wallet of that environment.

## Implementation

```javascript
// Production: EUR bank transfers -> EURC on Solana, sent to the user's Solana wallet
// idempotencyKey: made once for this request (e.g. crypto.randomUUID()) and sent again on every retry
async function getVirtualIban(token, solanaAddress, idempotencyKey) {
  const res = await fetch('https://api.aureahub.com/v1/ramp/bank/payin/initiate', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${token}`,
      'Idempotency-Key': idempotencyKey
    },
    body: JSON.stringify({
      cryptoCurrency: 'EURC',
      network: 'Solana',                // a production network -> the production environment
      destinationAddress: solanaAddress // always set it outside the sandbox
    })
  });

  const data = await res.json();
  if (!res.ok) throw new Error(`${res.status}: ${data.message}`);

  return {
    iban: data.iban,
    bic: data.bic,
    accountHolder: data.accountHolderName,
    bankName: data.bankName,
    refreshAfter: data.expiresAt
  };
}
```

---

Web version: https://docs.aureahub.com/#payin-initiate
