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

# Lock an FX quote for USDC to BRL

> Request a locked USDC to BRL quote with POST /exchange-rates, then settle the off-ramp at that exact rate with exchangeRateId before the lock expires.

To lock a USDC to BRL price on Lumx, call `POST /exchange-rates` with `type` set to `LOCKED`, `rail` set to `PIX`, `sourceCurrency` set to `USDC` and `targetCurrency` set to `BRL`. The response gives you an `id` and an `expiresAt`. Pass that `id` as `exchangeRateId` to `POST /transactions/off-ramp` before the lock expires, and the conversion settles at the quoted rate.

A locked quote has three lock durations (`30s`, `1m` and `5m`), is consumed by one transaction, and is not a hedge. The sections below cover each part.

## Floating or locked

| **Quote type** | **What you get** | **Use it for** |
| :- | :- | :- |
| `FLOATING` | A reference rate with no price guarantee. The response has no `id`. | Showing an estimate in your UI. |
| `LOCKED` | A guaranteed rate for a fixed window, identified by `id`. | Showing a final price the customer can accept, or fixing the BRL amount for an invoice. |

If you skip the quote and send `sourceAmount` straight to the off-ramp, the transaction uses a floating rate and the final rate can differ slightly from what you displayed. See [Exchange Rates](https://docs.lumx.io/concepts/exchange-rates.md) for the full comparison.

## Before you start

* An approved [customer](https://docs.lumx.io/concepts/customers.md). A customer that is not approved gets `KYC_NOT_APPROVED`.
* USDC or USDT in the customer's [wallet](https://docs.lumx.io/concepts/wallets.md). Off-ramps debit that wallet.
* A BRL [destination](https://docs.lumx.io/concepts/destinations.md) for the recipient. The [Pix payout guide](https://docs.lumx.io/guides/pix-payout-from-stablecoins.md) shows how to register one.

## 1. Request the quote

Send a `POST` request to `/exchange-rates`.

```bash Request theme={null}
curl -X POST https://api-sandbox.lumx.io/exchange-rates \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
    "type": "LOCKED",
    "rail": "PIX",
    "sourceCurrency": "USDC",
    "targetCurrency": "BRL",
    "sourceAmount": "1000.00",
    "timelock": "30s",
    "blockchain": "POLYGON"
  }'
```

```json Response theme={null}
{
  "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
  "id": "123e4567-e89b-12d3-a456-426614174001",
  "type": "LOCKED",
  "rail": "PIX",
  "sourceCurrency": "USDC",
  "sourceAmount": "1000.00",
  "targetCurrency": "BRL",
  "baseTargetAmount": "5925.50",
  "baseExchangeRate": "5.9255",
  "finalTargetAmount": "5890.00",
  "finalExchangeRate": "5.8900",
  "expiresAt": "2026-11-02T15:30:30Z"
}
```

> [!NOTE]
>
> The numbers above are illustrative. The response also carries a `fees` object with `lumx`, `partner` and `total` entries, left out here for brevity.

### Request fields

| **Field** | **Required** | **Description** |
| :- | :- | :- |
| `customerId` | Yes | Customer the quote is for |
| `type` | Yes | `LOCKED` |
| `rail` | Yes | Payment rail of the conversion. Use `PIX` for BRL |
| `sourceCurrency` | Yes | `USDC` or `USDT` for an off-ramp |
| `targetCurrency` | Yes | `BRL` |
| `sourceAmount` | No | Amount of stablecoin to sell. Use it when the customer sets how much to send |
| `targetAmount` | No | Amount of fiat to receive. Use it when the recipient must get a fixed amount |
| `timelock` | No | `30s`, `1m` or `5m`. Defaults to `30s` |
| `blockchain` | No | `ETHEREUM`, `BASE`, `TRON` or `POLYGON`. Falls back to your project's default |
| `partnerFeeId` | No | [Partner fee](https://docs.lumx.io/concepts/partner-fees.md) included in the quote |

The locked off-ramp request has no `blockchain` field, so set the network on the quote. See [Coverage](https://docs.lumx.io/get-started/coverage.md) for which stablecoins and networks convert to BRL.

### Response fields

| **Field** | **Description** |
| :- | :- |
| `id` | The quote identifier. This is the `exchangeRateId` for the transaction |
| `expiresAt` | When the lock ends, in UTC |
| `baseTargetAmount`, `baseExchangeRate` | Amount and rate before fees |
| `fees` | Lumx and partner fees, with a `total` |
| `finalTargetAmount`, `finalExchangeRate` | Amount and rate after fees. This is what the transaction settles at |

## 2. Know how long it lasts

The lock starts when the quote is created and ends at `expiresAt`.

| **`timelock`** | **Fee** |
| :- | :- |
| `30s` (default) | No extra fee |
| `1m` | Includes a fee that covers volatility risk. It varies with market conditions |
| `5m` | Includes a fee that covers volatility risk. It varies with market conditions |

For a flow where your service quotes and executes back to back, `30s` is enough. If a person has to read the price and confirm, use `1m` or `5m` and show a countdown from `expiresAt`.

## 3. Execute the off-ramp with the quote

Send a `POST` request to `/transactions/off-ramp` with the quote's `id` as `exchangeRateId`. Do not send `sourceCurrency` or `sourceAmount`. The quote already fixes them.

```bash Request theme={null}
curl -X POST https://api-sandbox.lumx.io/transactions/off-ramp \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "destinationId": "e80d3137-eddd-4791-b9b8-6e36b289f284",
    "exchangeRateId": "123e4567-e89b-12d3-a456-426614174001",
    "purpose": "TRADE_TRANSACTIONS"
  }'
```

```json Response (202) theme={null}
{
  "id": "123e4567-e89b-12d3-a456-426614174004",
  "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
  "type": "OFF_RAMP",
  "request": {
    "destinationId": "e80d3137-eddd-4791-b9b8-6e36b289f284",
    "exchangeRateId": "123e4567-e89b-12d3-a456-426614174001",
    "purpose": "TRADE_TRANSACTIONS"
  },
  "state": {
    "status": "TRANSFERRING_STABLECOIN"
  },
  "metadata": {},
  "createdAt": "2026-11-02T15:30:10Z",
  "updatedAt": "2026-11-02T15:30:10Z"
}
```

The `purpose` is required and must match what the funds are for. See [Purpose codes](https://docs.lumx.io/additional-information/purpose-codes.md).

## 4. Follow the transaction

The off-ramp moves through `TRANSFERRING_STABLECOIN`, `TRADING` and `TRANSFERRING_FIAT` to `SUCCESS`, or to `FAILED`. Subscribe to the `offramp.*` [webhooks](https://docs.lumx.io/developer/webhooks.md) instead of polling, or read `GET /transactions/{id}`. The `offramp.success` event carries the receipt with the final amounts, rate, fees and `transactionHash`. The [Pix payout guide](https://docs.lumx.io/guides/pix-payout-from-stablecoins.md) walks through the full lifecycle.

## If the quote expires

A transaction that arrives after `expiresAt` is rejected with `422` and `EXCHANGE_RATE_EXPIRED`. Nothing is converted. Request a new quote, show the new price if a person has to approve it, and send the off-ramp again.

> [!WARNING]
>
> Use a new `Idempotency-Key` for the retry. Failed responses are cached for 24 hours per key, so resending the same key returns the same expiry error. See [Idempotency](https://docs.lumx.io/developer/idempotency.md).

Other quote errors you can hit on the transaction call:

| **Status** | **Code** | **Cause** |
| :- | :- | :- |
| `404` | `EXCHANGE_RATE_NOT_FOUND` | The `exchangeRateId` does not exist |
| `422` | `EXCHANGE_RATE_ALREADY_USED` | Another transaction already consumed the quote |
| `422` | `EXCHANGE_RATE_CURRENCY_MISMATCH` | The quote's currencies do not match the transaction type |
| `422` | `MINIMUM_AMOUNT_NOT_MET` | The amount is below the minimum, the equivalent of USD 40 today (it may change) |
| `422` | `TRANSACTION_LIMIT_EXCEEDED` | The amount passes the customer's [transaction limits](https://docs.lumx.io/compliance/transaction-limits.md) |
| `422` | `INSUFFICIENT_BALANCE` | The wallet balance is lower than the quoted amount |

Each quote settles one transaction. To pay several recipients, request one quote per payout.

## What a locked quote does not do

* It locks the rate, not the wallet balance. The balance is checked when you create the off-ramp, which fails with `INSUFFICIENT_BALANCE` if the wallet is short. Fund the wallet and request a new quote.
* It fixes the price for the lock window only. It is not a forward or a hedge, and Lumx does not offer hedging instruments today.
* [Autoconversion rules](https://docs.lumx.io/guides/autoconversion-rules.md) always convert at the market rate. They do not accept `exchangeRateId`.
* It does not change settlement time. Pix settles on its own timeline, covered in [Payment rail cut-off times](https://docs.lumx.io/additional-information/sla-and-cutoffs.md).

## Related resources

> [card] [Pix payout guide](https://docs.lumx.io/guides/pix-payout-from-stablecoins.md)
>
> Register a Pix destination and pay out USDC or USDT in BRL.

> [card] [Exchange Rates](https://docs.lumx.io/concepts/exchange-rates.md)
>
> Floating and locked rates side by side.

> [card] [Transactions](https://docs.lumx.io/concepts/transactions.md)
>
> Off-ramp lifecycle and purpose values.

> [card] [Errors](https://docs.lumx.io/developer/errors.md)
>
> The full error catalog.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.