Stablecoin API quotes explained: expiry, fee direction, and minor units
A stablecoin API quote locks the rate, fees, and amounts for a few minutes. How expiry works, which side pays the fee, and the field that flips meaning.
A stablecoin API quote is a price lock. It fixes the exchange rate, the fees, the amount the sender pays, and the amount the recipient gets, for a short window. You execute the payment against the quote's id before it expires. If it expires, nothing moves, and you ask for a new one.
Simple on paper. In practice, three fields cause most of the bugs: when the quote expires, which side pays the fee, and which currency the amount is in.
What does a quote actually lock?
Everything the user needs to say yes. A good quote response answers four questions with numbers, not estimates:
- What rate am I getting, and what was the market rate?
- What does this cost, fee by fee?
- How much leaves the sender?
- How much arrives at the recipient?
Here's a BlindPay payout quote response, straight from the payout quotes docs:
commercial_quotation is the raw market rate. blindpay_quotation is the rate net of the fee. The gap between them is the spread, visible on every quote. sender_amount and receiver_amount are the two numbers your UI shows. EVM payout quotes also carry a contract object: the ERC-20 approve payload for wallets that sign their own transactions.
If a provider's quote gives you one rate and one total, you can't tell spread from fee. Ask for the itemized version.
How long does a quote last, and why so short?
Because FX moves. A quote that lasted an hour would either carry a fat buffer (you pay for it) or leave the provider exposed. Minutes is the normal range.
At BlindPay the defaults are:
| Quote type | Default expiry | Exception |
|---|---|---|
| Payin quote | 5 minutes | OTC Pix payin quotes: 10 seconds |
| Payout quote | 5 minutes | SEPA payout quotes can be shorter |
| Transfer quote | 5 minutes | None |
Read expires_at from every response. Don't hardcode 5 minutes. And note the unit: expires_at is epoch milliseconds, not seconds. Divide by 1,000 before passing it to a library that expects seconds, or your countdown will say the quote expires in the year 56,000.
Every rail's window is in the cut-off times reference.
What happens when a quote expires?
The execute call fails and no money moves. That's the point: an expired quote protects both sides from a stale price.
Build the UX around it:
- Show the quote with a visible countdown driven by
expires_at. - When it hits zero, disable the confirm button and fetch a new quote.
- Show the new numbers. If they moved, say so.
- Execute against the new id.
On an external wallet flow, there's one extra trap. If the user signs an on-chain approval and the transaction is slow to mine, the quote can expire while they wait. Quote again rather than committing an expired one.
Also: a quote backs one payment. At BlindPay, a quote_id can only back one payout, and a second call with the same quote returns an error. That's a useful second layer against double-sends, but it's not a substitute for proper idempotency on your retries.
Who pays the fee: sender or recipient?
The cover_fees flag decides it, and the choice changes which number stays fixed.
cover_fees | Who pays | What happens to the amounts |
|---|---|---|
false | The recipient | The fee comes off what the recipient gets. The common case. |
true | The sender | The fee is added on top, so the recipient gets the full amount. |
Payroll is the classic true case. A company paying a contractor in Mexico wants them to receive exactly 20,000 MXN, so the company absorbs the fee. A marketplace paying out sellers usually leaves it false and shows the net.
Which currency is request_amount in?
This is the one that bites. currency_type says which side of the payment request_amount refers to, and on BlindPay it means opposite things on payins and payouts:
| Quote | currency_type: "sender" | currency_type: "receiver" |
|---|---|---|
| Payout quote | The stablecoin being sent | The fiat the bank account receives |
| Payin quote | The fiat the payer sends | The stablecoin the wallet receives |
Same field, same values, flipped direction. If you share one "build quote" helper across both flows, test both. A payout helper that assumes sender means fiat will quote the wrong side of every payment.
Why are amounts integers?
Floating point and money don't mix. 0.1 + 0.2 isn't 0.3 in JavaScript, and you don't want to find that out in a reconciliation report. So stablecoin APIs usually take minor units.
At BlindPay, request_amount is an integer and doesn't accept floats. 10000 is 100.00. 500000 is 5,000.00. Two more rules worth knowing:
- Whole pesos. For MXN, COP, and ARS payins quoted on the sender side, the amount must be a multiple of 100.
10050fails withrequest_amount_must_be_a_whole_currency_unit, because those rails settle in whole units. - Minimums. SWIFT payouts need at least 100 USD requested. SEPA needs 50 USDC on the sender side or 40 EUR on the receiver side.
Convert at the edge of your system, once. Store minor units everywhere else.
What should you check in a provider's quote API?
- Is the fee itemized? Market rate, applied rate, flat fee, and partner fee as separate fields.
- Is the expiry machine-readable? A timestamp on every response, not a number in the docs.
- Can you choose who pays? A
cover_feesstyle flag, not a support ticket. - Can you quote either side? "Send exactly X" and "receive exactly Y" are different products.
- Is a quote single-use? It should back one payment and fail loudly on reuse.
The quote is where the price gets set, so it's also where hidden costs hide. Settlement speed matters too: see how long a stablecoin payout takes. For where the quote sits in the full flow, see how a stablecoin payment moves.
Where does BlindPay fit?
BlindPay is a stablecoin API for cross-border payouts and collections: USDC or USDT in, local currency out over Pix, SPEI, ACH, RTP, SEPA, and SWIFT (POBO/COBO) to 100+ countries, with no pre-funding. Every quote itemizes the market rate, the applied rate, and each fee, and virtual USD accounts turn ACH, wire, and SWIFT deposits into stablecoins. Pricing is on the pricing page.
What to do next
Create a payout quote on a free development instance and read the response field by field. Then flip cover_fees and currency_type and watch which numbers move. Ten minutes of that beats an hour of docs. Start with the payout quotes guide, then the payin quotes guide for the flipped direction.
The quote window also has to absorb network time. How fast a quote API should respond sets latency budgets, timeouts, and retries that leave the user time to confirm.
When a payout lands a cent off your own math, minor units, token decimals, and rounding shows where the gap comes from and how to reconcile it.
This article is for general information only and is not legal, tax, or financial advice.
FAQ
More payments articles
See all payments articlesHow AI agents get live FX quotes and pay out through MCP and Agent Skills
How an AI agent creates a live stablecoin quote, shows rate, fees, and expiry, waits for a human to confirm, and pays out through BlindPay's MCP server.
On/off ramp liquidity with live quotes: how it works and why it matters
What on/off ramp liquidity with live quotes means for developers: live quote vs batch rate, the quote ID flow in an API, what happens on expiry, how liquidity depth changes spread by transaction size, rate windows and UX, and a checklist for evaluating a live quote API.
How to off-ramp USDC to a US bank account: ACH, RTP, or wire
Cashing out USDC or USDT to a US bank account: when ACH, Same Day ACH, RTP, and wire pay out, the cut-offs in ET, the fields you need, and what delays it.