# Sell a Stablecoin for Local Currency

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

Record the user's sale of a stablecoin: the user's wallet sends it, and the bank ramp pays local currency to a bank account or a mobile money number.

## Overview

1. Create the sale. The answer's `transfer` says what to send: exactly `transfer.amount` of `asset` on `network`, from `fromAddress` to `transfer.toAddress`. The amount carries a few smallest units of its own, so the transfer is recognised as this sale.
2. Have the user's wallet send it, then call [Say the Stablecoin Was Sent](https://docs.aureahub.com/docs/local-offramp-sent.md) with the transaction hash.
3. Once the transfer arrives, the bank ramp pays `payoutAsset` to `destination` at the rate of that moment, less its fees and your tenant's markup; `withdrawalId` names the payout ([Get a Withdrawal](https://docs.aureahub.com/docs/local-withdrawal-get.md)). `indicativeRate` is a guide only.

- `state`: `awaiting_transfer`, `sent`, `received`, `paying`, `paid`, `failed` or `cancelled`; each change is sent as `ramp.offramp.updated`.
- Send only `asset` on `network`, and at least 1 unit: a sale below 1 answers `422` `BANK_RAMP_DEPOSIT_TOO_SMALL`, a smaller transfer never arrives and is not recovered, and another token is not credited.
- `destination`: a `bank` or `momo` destination ([Withdraw](https://docs.aureahub.com/docs/local-withdrawal-create.md) describes them). Add `travelRule` data where your tenant's rules ask for it.
- **The user's passkey.** Confirmed with the user's passkey step-up for `bank_ramp.payout`, in the `x-aurea-step-up` header ([Passkeys & Step-Up](https://docs.aureahub.com/docs/guide-passkeys.md) › Where It Is Asked): without it `403` `STEP_UP_REQUIRED`, with one that is not for this action `403` `STEP_UP_INVALID`. The same `Idempotency-Key` after an answer needs none.
- A transfer usually arrives in about 3 minutes on `ethereum` and in less than a minute on `solana`; these are usual times, not a guarantee.

Called with the user's access token, in one environment (`isTestnet`). See [Bank Ramp: Local Rails](https://docs.aureahub.com/docs/guide-bank-local.md).

## Endpoint

### `POST /v1/ramp/bank/customer/offramps`

Authentication: bearer token required.

Records the user's sale of a stablecoin for local currency, paid to a bank account or a mobile money number. Send an Idempotency-Key header: the same key with the same body answers with the first answer, with the response header idempotency-replayed: true (see Idempotency).

**Request body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `network` | string | yes | `ethereum`, `solana` or `tron` |
| `asset` | string | yes | The stablecoin: `USDT` or `USDC`, as the network takes it |
| `amount` | string | yes | What the user sells, as decimal text, at least 1 |
| `fromAddress` | string | yes | The user's address that sends it |
| `payoutAsset` | string | yes | The local currency paid: `KES`, `MWK`… |
| `destination` | object | yes | A `bank` or `momo` destination |
| `travelRule` | object | no | The originator and beneficiary data, where your tenant's rules ask for it |
| `isTestnet` | boolean | no | `true` for the sandbox; `false` or omitted for production |

**Responses**

`201` Sale

```json
{
  "id": "3e8d1f62-7a4b-4c95-b0e3-5f2a9c1d7e48",
  "environment": "production",
  "network": "ethereum",
  "asset": "USDT",
  "amount": "250",
  "payoutAsset": "KES",
  "destinationType": "momo",
  "indicativeRate": "129.15",
  "state": "awaiting_transfer",
  "failure": null,
  "withdrawalId": null,
  "transfer": {
    "id": "1c7e9a40-5b2d-4f63-8e1a-9d0b2c4e6f73",
    "network": "ethereum",
    "asset": "USDT",
    "amount": "250.000013",
    "toAddress": "0x4E2a1B7c9D3f6E8a0B5c2D7e9F1a3B6c8D0e2F4a",
    "fromAddress": "0x9b3C2d1E0f4A5b6C7d8E9f0A1b2C3d4E5f6A7b8C",
    "status": "prepared",
    "txHash": null
  },
  "createdAt": "2026-10-23T09:00:00.000Z",
  "updatedAt": "2026-10-23T09:00:00.000Z"
}
```

`403` Not verified

```json
{
  "statusCode": 403,
  "error": "ForbiddenError",
  "message": "The user's verification with the bank ramp is not complete.",
  "details": {
    "code": "BANK_RAMP_KYC_REQUIRED"
  }
}
```

`422` Past the limit

```json
{
  "statusCode": 422,
  "error": "ValidationError",
  "message": "This would take this user KES past its limit of 300000.00 in 24 hours (290000.00 used).",
  "details": {
    "code": "BANK_RAMP_LIMIT_EXCEEDED",
    "asset": "KES"
  }
}
```

`400` Destination

```json
{
  "statusCode": 400,
  "error": "BadRequestError",
  "message": "The destination cannot be paid as given",
  "details": {
    "code": "BANK_RAMP_DESTINATION_INVALID"
  }
}
```

---

Web version: https://docs.aureahub.com/#local-offramp-create
