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

> ## Agent Instructions
> Bitnob API requests authenticate with an `X-Auth-Client` API key header against https://api.bitnob.com.
> Always use the sandbox environment for testing. Never put live API keys in sample code.
> Money amounts are integers in the smallest unit of the currency (satoshis for BTC, cents for fiat). Never use floats.
> Every state-changing request takes a client-supplied `reference` and must be treated as idempotent on retry.
> Quotes expire. Re-quote rather than reusing a stale quote id.

# Quote lifecycle and expiry

> Why quotes expire, what the expiry window protects, and how to handle rate movement without stranding a user mid-payment.

A quote is a price held for a short time. It fixes an exchange rate and an amount so that you and your user both know the terms before any value moves.

Every conversion starts with one: a payout or a trade. Card funding isn't quoted: it debits your balance directly. This page explains why the window exists and how to handle it.

## Why quotes expire

Bitnob quotes a rate it can honor. Between the moment you request a quote and the moment you execute it, the market moves.

The expiry window sets who carries that risk. Inside the window, Bitnob carries it, because it committed to the rate. Outside the window, nobody does, so the quote is dead and you request a new one.

A quote with no expiry would mean an open-ended commitment to a stale price. Any party that could wait would exercise it only when the market moved in their favor.

## What a quote contains

A payout quote, as returned in `data.payout` by [Create payout quote](https://www.bitnob.dev/api-reference/payouts/create-quote.md), trimmed to the fields that matter here:

```json theme={null}
{
  "id": "01a15949-999c-79f6-96d6-baa649fed594",
  "quote_id": "QT2_21053530",
  "status": "QUOTE",
  "from_asset": "USDT",
  "to_currency": "NGN",
  "amount": "0.738407",
  "settlement_amount": "1000",
  "fees": "0",
  "total_amount": "0.738407",
  "exchange_rate": {
    "rate": "1356.9806",
    "effective_rate": "1354.26668490412469"
  },
  "reference": "your-unique-ref-123",
  "expires_at": "2026-10-20T12:45:18.111318252Z",
  "created_at": "2026-10-20T12:29:18.115131314Z"
}
```

| Field | Meaning |
| - | - |
| `quote_id` | The identifier you execute against. |
| `amount` | What leaves your balance, before fees. |
| `total_amount` | `amount` plus fees. |
| `settlement_amount` | What the recipient gets. |
| `exchange_rate.rate` | The headline rate. |
| `exchange_rate.effective_rate` | The rate after fees. Show this one to users. |
| `expires_at` | The instant the quote stops being valid. |

> [!WARNING]
>
> Read `expires_at` from the response. Do not hard-code a window length, because it varies by product and by corridor.

## The lifecycle

**1. Create the quote**

Call [Create payout quote](https://www.bitnob.dev/api-reference/payouts/create-quote.md) or [Create trading quote](https://www.bitnob.dev/api-reference/trading/create-quote.md). The response carries `quote_id` and `expires_at`.

**2. Show the terms**

Present `settlement_amount` and `effective_rate` to the user, with the time remaining.

**3. Execute before expiry**

Initialize and finalize the payout against the `quote_id`. See [Initialize payout](https://www.bitnob.dev/api-reference/payouts/initialize-payout.md).

**4. Confirm from the webhook**

Wait for `payouts.withdrawal.success` before you tell the user the money arrived.

A quote you never execute simply lapses. It costs nothing and reserves nothing.

## Handling expiry in your product

### Show the remaining time

Users need to know they are on a clock. Derive the countdown from `expires_at`, not from a timer you start locally, because network latency has already consumed part of the window.

```js theme={null}
const expiresAt = new Date(quote.expires_at);
const secondsLeft = Math.max(0, Math.floor((expiresAt - Date.now()) / 1000));

showCountdown(secondsLeft);
```

### Separate looking from committing

Don't hold a quote open while a person browses. A trading quote lasts about 10 seconds in production, so a quote created when the screen opens is usually dead by the time the user decides.

1. While the user is browsing, show indicative rates. Use [List all prices](https://www.bitnob.dev/api-reference/trading/list-all-prices.md) for crypto pairs and [Get exchange rate](https://www.bitnob.dev/api-reference/exchange-rates/get-single-pair-rate.md) for NGN pairs. They carry no commitment.
2. Create the quote only when the user taps to commit. Show the locked amount with a countdown from `expires_at`, and disable Confirm when it passes.
3. If it expires, fetch a new quote and ask the user to confirm the new rate. Don't execute at a price they haven't seen.
4. Check your stored `expires_at` on your server before you submit the order.

### Refuse to execute an expired quote

Check `expires_at` before you submit. Submitting an expired trading quote returns `422` with code `PRECONDITION_FAILED`, and the round trip wastes the user's time.

When a quote expires, request a new one and show the new terms. Never present old terms with a new quote behind them.

### Re-quote rather than reuse

Prices move. A quote requested a minute ago is not a discount you can apply now, and the platform will not honor it.

If your system converts a fixed amount without a person confirming each rate, for example a scheduled treasury job, you can fetch a quote in advance and refresh it as it approaches expiry:

```javascript theme={null}
let quote = await fetchQuote();

setInterval(async () => {
  if (Date.now() > new Date(quote.expires_at) - 10_000) {
    quote = await fetchQuote();
    updateDisplayedTerms(quote);
  }
}, 1000);
```

Refresh slightly before expiry, so you never submit a dead quote. When a person is confirming, show them the refreshed terms before you execute.

### Store the quote with your record

| reference | quote\_id | rate | expires\_at | status |
| - | - | - | - | - |
| `payout_20261020_a41f9c` | QT2\_21053530 | 1356.9806 | 2026-10-20T12:45:18Z | pending |

Storing the quote lets you answer the question that follows every pricing dispute: what rate did this user actually see, and when.

## When a payment arrives late or short

On flows where a user sends crypto to fund a quote, the payment can arrive after expiry, or for the wrong amount, or in the wrong asset. These are normal in production, especially on mobile.

Handle each case explicitly:

* Detect the condition from the webhook event rather than from your own timer.
* Alert your operations team, because most of these need a human decision.
* Tell the user what happened and what you will do next.

[Payout error handling](https://www.bitnob.dev/docs/payouts/error-handling.md) covers expired quotes, underpayment, and delivery rejection case by case.

## Test the window before you ship

Quote expiry is easy to get wrong and hard to notice, because it only appears when a user hesitates.

In sandbox, create a quote, wait for `expires_at` to pass, then try to execute it. Confirm that your interface blocks the attempt and offers a fresh quote. See [Test in sandbox](https://www.bitnob.dev/docs/getting-started/api-best-practices/simulating-flows-and-testing-in-sandbox.md).

## Next

Continue to [Errors and retries](https://www.bitnob.dev/docs/getting-started/api-best-practices/errors-and-retries.md), which covers the status codes an expired quote returns and the retry rules around them.


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