---
title: "Indicative rate vs binding quote: how to show a live price before the user commits"
seoTitle: "Indicative rate vs binding quote in a payments API"
description: "An indicative rate previews a price. A binding quote locks it. How to show a live price while the user types, then lock it before they pay."
date: "2026-09-25"
updated: "2026-09-25"
category: "payments"
author: "BlindPay Team"
faq:
  - q: "What is an indicative exchange rate?"
    a: "An indicative exchange rate is a price shown for information. The provider tells you roughly what a conversion would cost right now, but does not commit to it, does not give it an ID, and does not hold it for any window. If you convert later, you get whatever the market says at that moment, not the indicative number."
  - q: "What is a binding quote in a payments API?"
    a: "A binding quote is a price the provider commits to execute at, for a stated window. It comes with a quote ID and an expiry time. If you submit the payment with that ID before it expires, the conversion settles at the quoted rate and fees. At BlindPay, payin and payout quotes stay binding for 5 minutes by default."
  - q: "Can I execute a payment at an indicative rate?"
    a: "No. An indicative rate has no ID, so there is nothing to execute against. At BlindPay, the preview endpoint POST /payin-quotes/fx returns a rate and an amount without creating anything. To move money you create a payin quote, then create the payin with that quote's ID before the quote expires."
  - q: "Why is my quote different from the preview I saw a second ago?"
    a: "Because the preview reserved nothing. The market can move between the two calls, your partner fee is not part of BlindPay's preview, the fee direction you set on the quote changes which side absorbs the fee, and MXN, COP, and ARS amounts round to whole units. Always show the quote's numbers on the confirm screen."
  - q: "How long does a BlindPay quote stay binding?"
    a: "Payin, payout, and transfer quotes expire 5 minutes after creation by default. OTC payin quotes, which are BRL only, expire in 10 seconds, and SEPA payout quotes can be shorter than 5 minutes. Read expires_at from each response, in epoch milliseconds, instead of hardcoding a window into your app."
---

An indicative rate is a price preview: the provider shows what a conversion would cost right now but does not commit to it. A binding quote is a price the provider will execute at, for a stated window, under a quote ID. Good payment UX uses both: the preview while the user types, the quote when they commit.

Most teams get this backwards. Some create a binding quote on every keystroke, which burns quotes and requests for nothing. Others show a cached rate and settle at a different one, which is [slippage](/resources/more/stablecoin-fx-slippage-live-quotes) with extra steps. This guide covers the two-call pattern, what each BlindPay response contains, and the four reasons a preview and a quote disagree.

**Key takeaways**

- An indicative rate is information. A binding quote is a commitment with an ID and an expiry.
- Show the indicative rate while the user types. Create the binding quote when they reach the confirm screen.
- At BlindPay, `POST /payin-quotes/fx` returns an indicative rate and creates nothing. `POST /payin-quotes` returns a quote that holds for 5 minutes.
- A preview can differ from the quote one second later. The confirm screen shows the quote's numbers, never the preview's.
- Locking a price has a cost, and the cost grows with the lock. That's why binding windows are measured in minutes.

## What is the difference between an indicative rate and a binding quote?

An indicative rate tells you where the price is. A binding quote tells you what you will get. The [glossary for this cluster](/resources/more/onramp-offramp-liquidity-glossary) defines both the same way: a firm (binding) quote is a price the provider commits to execute at, and an indicative rate is shown for information only.

FX markets treat the line between the two as a conduct issue. The [FX Global Code](https://www.globalfxc.org/uploads/fx_global.pdf) (updated December 2024), Principle 9, says market participants handling orders should "make clear whether the prices they are providing are firm or merely indicative." Payment APIs should be just as clear.

| | Indicative rate | Binding quote |
| --- | --- | --- |
| Provider commits to it? | No | Yes, for the stated window |
| Has an ID you can execute against? | No | Yes |
| Has an expiry? | No | Yes (`expires_at`) |
| Stored by the provider? | No | Yes |
| Who carries a market move? | You, at execution | The provider, inside the window |
| Right place in the UI | Amount field, calculators, corridor pages | Confirm screen, approval lists |
| BlindPay payin endpoint | `POST /payin-quotes/fx` | `POST /payin-quotes` |

## Why not create a binding quote on every keystroke?

Because a binding quote is a commitment, and commitments are not free. Each one asks the provider to stand behind a price for minutes, and almost all of them will expire unused if you create one per keystroke.

Three practical problems follow:

1. **Request volume.** A user typing "25000" fires five requests. On BlindPay development instances, requests are capped at roughly 100 per minute per instance, and going over returns a `429` with a `Retry-After` header ([sandbox vs production](/docs/learn/sandbox-vs-production)). A busy amount field gets there fast.
2. **Wasted windows.** The quote's clock starts at creation. A quote created while the user is still typing has already spent part of its 5 minutes before anyone looks at it.
3. **Noisy metrics.** If you track how many quotes expire before confirmation, keystroke quotes bury the real signal. You can't tell an abandoned checkout from a typo.

## How does the two-call pattern work?

Preview while the amount changes, lock when the user commits. Five steps:

1. **Amount screen opens.** Nothing is priced yet.
2. **User types.** Debounce input by 300 to 500 ms, then call the indicative endpoint and show the result as an estimate ("about 1,834 USDC").
3. **User taps Continue.** Create the binding quote. Show the quoted amounts and a countdown built from `expires_at`.
4. **User confirms.** Create the payin (or payout) with the quote ID before it expires.
5. **Quote expires first.** Request a new quote, update the numbers, and highlight the change only if the amount moved.

The [on/off-ramp liquidity guide](/resources/more/on-off-ramp-liquidity-live-quotes) explains why the window length shapes which of these screens you can afford.

## What does BlindPay's indicative endpoint return?

`POST /instances/{instance_id}/payin-quotes/fx` returns an indicative rate and amount without creating a payin quote. Per the [payin quotes docs](/docs/payin-quotes), it doesn't persist anything and doesn't reserve pricing.

```bash
curl https://api.blindpay.com/v1/instances/in_000000000000/payin-quotes/fx \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{
  "from": "BRL",
  "to": "USDC",
  "request_amount": 10000,
  "currency_type": "sender"
}'
```

| Field | Direction | What it means |
| --- | --- | --- |
| `from` | Request | The fiat currency the payer sends, for example `BRL` |
| `to` | Request | The stablecoin the destination receives: `USDC`, `USDT`, or `USDB` on development |
| `request_amount` | Request | Minor units, minimum 500 ($5.00). MXN, COP, and ARS sender amounts must be whole units |
| `currency_type` | Request | `sender` or `receiver`, same meaning as on a payin quote |
| `commercial_quotation` | Response | The raw market rate |
| `blindpay_quotation` | Response | The rate including BlindPay's fee |
| `result_amount` | Response | The other side of the conversion |
| `instance_flat_fee` | Response | The flat-fee component |
| `instance_percentage_fee` | Response | The percentage fee, in basis points |

The preview covers the payin direction: `from` is a fiat currency and `to` is a stablecoin. On the payout side, the [payout quote](/docs/payout-quotes) itself is what you show on the confirm screen.

## What does the binding quote add?

An ID, an expiry, and every fee line. A payin quote response carries `id`, `expires_at` (epoch milliseconds), `sender_amount`, `receiver_amount`, `commercial_quotation`, `blindpay_quotation`, `flat_fee`, `partner_fee_amount`, `billing_fee_amount`, and `is_otc`.

It holds for 5 minutes by default. OTC payin quotes, BRL only and priced through BlindPay's OTC desk, hold for 10 seconds. Payout quotes also default to 5 minutes, and SEPA payout quotes can be shorter because the rail's own deadline can be tighter ([cut-off times](/docs/kb/cut-off-times)). An expired quote is rejected when you try to use it, and nothing moves. The field-by-field tour lives in [stablecoin API quotes explained](/resources/more/stablecoin-api-quotes-explained).

## Why can the preview and the quote disagree?

Four reasons, and only the first one is the market.

- **Time.** The preview reserved nothing. If BRL moves between the two calls, the quote reflects the new price.
- **Partner fees.** The `/fx` endpoint takes no `partner_fee_id`, so `instance_percentage_fee` never includes your own markup. If your platform adds a partner fee, the preview looks better than the quote. Add your markup to the preview yourself.
- **Fee direction.** The binding quote takes `cover_fees`, which decides whether the payer pays the fee on top or the recipient absorbs it. Make sure your preview math assumes the same direction you'll send on the quote.
- **Rounding.** MXN, COP, and ARS payins settle in whole currency units, so small differences show up on peso amounts.

**Illustrative example.** A payer types R$10,000 at 09:00:00. The preview shows a `blindpay_quotation` of 5.45 BRL per USDC:

- Preview: 10,000 / 5.45 = 1,834.86 USDC
- The payer taps Continue at 09:00:40. The quote comes back at 5.46: 10,000 / 5.46 = 1,831.50 USDC
- Difference: 1,834.86 - 1,831.50 = 3.36 USDC
- In basis points: 3.36 / 1,834.86 x 10,000 = about 18 bps

That 18 bps is not slippage. The payer never agreed to 1,834.86. They agree to 1,831.50, and that is what lands. Label the preview as an estimate and nobody feels cheated.

## What does locking a price cost?

Locking a price means someone carries the market risk for the length of the lock, so longer locks cost more. Stripe's [FX Quotes API](https://docs.stripe.com/payments/currencies/localize-prices/fx-quotes-api) makes the trade-off visible: a `lock_duration` of `none` returns an informational rate at no charge, while 5-minute, 1-hour, and 24-hour locks add a duration premium. For BRL, MXN, and COP, which sit in Stripe's second currency group, that premium is 0.12% for 5 minutes, 0.15% for 1 hour, and 0.30% for 24 hours.

**Illustrative example.** On a R$50,000 conversion, a 0.12% lock costs R$60 and a 0.30% lock costs R$150. The 24-hour lock costs 2.5 times the 5-minute lock for the same money. Short windows are cheap because the provider carries little risk, which is why binding quotes across the industry last minutes, not days. If you want the treasury view of longer locks, see [live quote vs forward contract vs fixed rate](/resources/more/live-quote-vs-forward-contract-vs-fixed-rate).

## When is an indicative rate enough on its own?

Whenever nobody confirms a number they expect to pay or receive. That covers:

- Marketing pages and calculators, such as the [USDC to BRL corridor page](/usdc-to-brl).
- Dashboards and internal treasury estimates.
- Reporting, where a daily reference rate is the right tool. The ECB says its [euro reference rates](https://www.ecb.europa.eu/stats/policy_and_exchange_rates/euro_reference_exchange_rates/html/index.en.html) are published for information purposes only and strongly discourages using them for transactions.

Decision rule: if a person confirms an amount, require a binding quote. If a person is only browsing, an indicative rate is fine, as long as it is labeled as an estimate. Why buy and sell prices differ in the first place is covered in [why on-ramp and off-ramp rates differ](/resources/more/why-onramp-and-offramp-rates-differ), and the [on-ramp integration guide](/resources/more/how-to-integrate-a-crypto-on-ramp-api) shows where the payin quote sits in the full flow.

## What to do next

Wire the two calls into your amount screen on a free development instance: debounce `/payin-quotes/fx` for the typing phase, then create the binding quote on Continue. The [payin quotes reference](/docs/payin-quotes) has both requests and every field.
