# Bank Ramp: Local Rails

> Coming soon, upon request — this feature is turned on for your tenant by the Aurea team. [Contact us](https://www.aureahub.com/contact)

The bank ramp's local rails let your users pay in and out in local currencies — through local banks and mobile money — and sell or receive stablecoins against them [Coming soon, upon request](https://docs.aureahub.com/docs/feature-availability.md). This page explains the flows; [Desk Orders](https://docs.aureahub.com/docs/guide-bank-desk.md) covers the desk's currencies.

## Overview

- **An Aurea service.** Your apps call Aurea's API with the user's access token, under `/v1/ramp/bank/customer/…`; there is no other account, key or dashboard for your app to manage.
- **Currencies.** The local currencies your tenant offers — for example KES (bank transfer or M-Pesa), MWK, and USD over ACH, Fedwire or SWIFT — and the stablecoins USDT and USDC on `ethereum`, `solana` and `tron`, as set up for your tenant.
- **A balance per user.** Money the user pays in lands on their balance on the local rails ([Balances](https://docs.aureahub.com/docs/local-balances.md)); from there they withdraw it, or have it delivered as a stablecoin.
- **Per environment.** The sandbox (`isTestnet: true`) and production each have their own settings, their own verification for each user, their own balances and their own events ([Environments & Test Data](https://docs.aureahub.com/docs/guide-environments.md)).

## What Aurea Sets Up

Ask your Aurea contact, for the sandbox first and then for production, to:

1. **Connect the local rails** for your tenant. Until then the routes answer `BANK_RAMP_NOT_CONFIGURED` and nothing is sent anywhere.
2. **Switch on the functions** you offer — pay-in, withdrawal, the desk — and the currencies. A function that is off answers `403` `BANK_RAMP_FUNCTION_OFF`; a currency not offered, `BANK_RAMP_PAIR_UNAVAILABLE` with the currencies offered.
3. **Choose whether users must be verified** before moving money (on by default).
4. **Set your limits and your markup**: how much one user, and your users together, may move per currency in 24 hours, and the markup your tenant adds to pay-ins, withdrawals and desk orders.
5. **Choose the reversal window**: how long a pay-in stays on hold before it can be spent, in case the paying bank takes it back.

## Verification

1. Your app registers the user with who they are: [Register for the Local Rails](https://docs.aureahub.com/docs/local-verification-register.md), as an `individual` or a `business`, with the legal name, the country and, for a person, the date of birth. A withdrawal to the user's own wallet names them as its beneficiary.
2. The bank ramp's operations then verify the user. The state moves to `pending`, then `verified` or `rejected`; a verification can later be `expired`. Once it has started, the identity can no longer be changed through the API.
3. Read it with [Your Verification](https://docs.aureahub.com/docs/local-verification.md), or listen to `ramp.kyc.updated`. Its `nextStep` tells your app what to show.

Where your tenant has [identity verification](https://docs.aureahub.com/docs/guide-identity.md) [Coming soon, upon request](https://docs.aureahub.com/docs/feature-availability.md) deciding this verification, the user verifies themselves instead: the registration also answers `verificationId` and `verificationUrl`, your app shows them the verification page, and its decision moves the state — `verified` only when the identity it confirmed matches the one registered; otherwise the user stays `pending` while Aurea reviews it ([Identity Verification](https://docs.aureahub.com/docs/guide-identity.md) › With the Bank Ramp).

While your tenant asks for verification, every flow that moves money answers `403` `BANK_RAMP_KYC_REQUIRED` until the user is `verified`.

## Balances

[Balances](https://docs.aureahub.com/docs/local-balances.md) answers, per asset, `spendable` and `onHold`: money reserved by withdrawals and desk orders in progress, and pay-ins inside your tenant's reversal window, are on hold.

## Pay In

The bank ramp's account in each local currency is shared by all your users, and a bank transfer does not say who sent it. Aurea gives each pay-in its own **exact amount**, so the payment that arrives with that amount is the user's:

1. Your app asks how to pay in: [Ask How to Pay In](https://docs.aureahub.com/docs/local-instruction-create.md), with the currency and the amount.
2. Show the user `payTo` (the account, or for KES the M-Pesa paybill and account number), the exact `amountToPay` — the amount asked for plus a few smallest units — and the deadline `payBy`.
3. The user pays exactly that amount before the deadline. When it lands, the instruction becomes `matched`, `ramp.instruction.updated` is sent, and `expectedCredit` lands on the user's balance — or, if the instruction asked for a stablecoin, is converted and sent to the address ([Stablecoin Deliveries](https://docs.aureahub.com/docs/local-deliveries.md)).

- A payment of another amount, or after the deadline, is never credited by guess: it waits for your tenant's review and is assigned to the right user by hand.
- An open instruction can be withdrawn ([Cancel an Instruction](https://docs.aureahub.com/docs/local-instruction-cancel.md)).

## Sell a Stablecoin

The user sells USDT or USDC from their own wallet for local currency paid to a bank account or a mobile money number: [Sell a Stablecoin](https://docs.aureahub.com/docs/local-offramp-create.md) answers the exact amount to send and the address; the user's wallet sends it; your app gives the transaction hash ([Say It Was Sent](https://docs.aureahub.com/docs/local-offramp-sent.md)); once it arrives the bank ramp pays the local currency at the rate of that moment. Send at least 1 USDT or USDC, and only the asset on the network named: a smaller transfer never arrives, and another token is not credited.

## Withdraw

[Withdraw](https://docs.aureahub.com/docs/local-withdrawal-create.md) pays from the user's balance to a bank account, a mobile money number or a wallet — in the currency held, or in another one with `sourceAsset` and `sourceAmountLimit`. A USD withdrawal pays a USD beneficiary that Aurea registers for your tenant on request. Read the banks and operators with [Banks & Mobile Money](https://docs.aureahub.com/docs/local-institutions.md). A failed withdrawal returns its reserved money to the balance and is never sent again on its own.

## Limits

- **Your tenant's limits**, per currency over the last 24 hours, for one user and for all your users: pay-in instructions given, stablecoin sold, withdrawals and confirmed desk orders count. Past a limit: `422` `BANK_RAMP_LIMIT_EXCEEDED` with the asset. Your tenant can also cap how many of these operations one user starts in 24 hours, every currency together: `422` `BANK_RAMP_VELOCITY_EXCEEDED`.
- **The bank ramp's limits**, a minimum and a maximum per operation by currency and destination: `BANK_RAMP_AMOUNT_BELOW_MINIMUM` and `BANK_RAMP_AMOUNT_ABOVE_MAXIMUM`.
- **Frozen.** When Aurea freezes your tenant or a user, withdrawals and desk confirmations answer `423`.

## Fees

The bank ramp's fee is taken from each pay-in and charged on each withdrawal, sale and desk order; your tenant's markup is added to it. Pay-in instructions show what lands (`expectedCredit`); withdrawals reserve the fees with the amount and show them once known (`fee`, `rate`); desk quotes include them in the rate.

## Reversals

Rarely, a paying bank takes a pay-in back after it was credited. Its amount then leaves the user's balance and `ramp.collection.reversed` is sent. That is why pay-ins stay on hold for your tenant's reversal window before they can be spent. A withdrawal that succeeded can also come back from the bank: its status becomes `reversed`.

## Events

Sent only to the addresses that name them ([Webhooks & Events](https://docs.aureahub.com/docs/guide-events.md)): `ramp.kyc.updated`, `ramp.instruction.updated`, `ramp.offramp.updated`, `ramp.withdrawal.updated`, `ramp.desk_order.updated` and `ramp.collection.reversed`.

## Amounts and Retries

- Every amount is decimal text in the asset's major unit (`"15000.37"`), never a JSON number ([Amount Units](https://docs.aureahub.com/docs/amounts.md)).
- Send an `Idempotency-Key` header on every call that creates something — an instruction, a sale, a withdrawal, a desk quote or a confirmation: a retry with the same key and body answers the first answer and never moves money twice ([Idempotency](https://docs.aureahub.com/docs/idempotency.md)).
- When the bank ramp is paused for maintenance, operations already accepted wait and are sent again by Aurea; an answer that was lost is checked, never sent twice.

## Sandbox

The sandbox takes pay-ins and local withdrawals. It takes stablecoin transfers on `solana` only, and does not run conversions, withdrawals in another currency, withdrawals to a wallet, or reversals: test those with small amounts in production.

## Refusals

Every refusal has a `BANK_RAMP_*` code in `details.code`; each route's page lists its own, and [Errors & Codes](https://docs.aureahub.com/docs/errors.md) lists them all.

---

Web version: https://docs.aureahub.com/#guide-bank-local
