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

# Rates and quotes

> Indicative pricing to show a user, executable quotes to hold a rate.

There are two ways to price a payout, and they answer different questions.

|            | Indicative rate             | Executable quote                         |
| ---------- | --------------------------- | ---------------------------------------- |
| Endpoint   | `GET /v1/rates`             | `POST /v1/rates/quotes`                  |
| Commitment | None                        | Rate and fees held for a TTL             |
| Lifetime   | Live at the instant you ask | \~30 seconds                             |
| Use it to  | Show a price on a screen    | Show a price the user is about to accept |

Both need the `rates:read` scope.

## Indicative rates

Live, mid-market-derived, and non-binding. Cheap enough to call on every page
render.

```bash theme={null}
curl "$SPENDIN_BASE_URL/v1/rates?from=USDT&to=GHS" \
  -H "X-API-Key: $SPENDIN_API_KEY"
```

```json theme={null}
{
  "settlement_currency": "USDT",
  "destination_currency": "GHS",
  "rate": 12.2265,
  "mid_market_rate": 12.35,
  "spread_percent": 1.0,
  "rail": "CRYPTO",
  "source": "neona",
  "as_of": "2026-08-16T09:41:00.000Z"
}
```

`rate` is what you would transact at — destination units per one settlement unit,
**after** spread. `mid_market_rate` is the pre-spread reference, shown so the
spread is visible rather than buried.

<Note>
  `rate` does not account for fees. A payout at this rate still carries a platform
  fee and a network fee on top, so `destination_amount / rate` understates what
  you will owe. Quote if you need the real number.
</Note>

## Executable quotes

A quote freezes the rate, the spread, and every fee for a short window, and shows
you the exact settlement amount.

```bash theme={null}
curl -X POST "$SPENDIN_BASE_URL/v1/rates/quotes" \
  -H "X-API-Key: $SPENDIN_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "settlement_currency": "USDT",
    "destination_currency": "GHS",
    "destination_amount": 500
  }'
```

```json theme={null}
{
  "id": "b7e2f1a0-...",
  "rail": "CRYPTO",
  "exchange_rate": 12.2265,
  "spread": 0.1235,
  "destination_amount": 500,
  "destination_currency": "GHS",
  "settlement_amount": 42.32,
  "settlement_currency": "USDT",
  "platform_fee_amount": 0.41,
  "platform_fee_currency": "USDT",
  "network_fee_amount": 1.0,
  "network_fee_currency": "USDT",
  "total_fee_amount": 1.41,
  "total_fee_currency": "USDT",
  "provider": "neona",
  "status": "PENDING",
  "expires_at": "2026-08-16T09:41:30.000Z",
  "created_at": "2026-08-16T09:41:00.000Z"
}
```

You always specify what the **beneficiary receives** (`destination_amount`) and we
derive what you owe (`settlement_amount`). There is no reverse direction — you
cannot ask "what does 50 USDT buy".

### Reading the numbers

```
destination_amount    500.00 GHS   ← the beneficiary receives this
─────────────────────────────────
conversion             40.91 USDT  ← 500 / 12.2265
platform_fee_amount     0.41 USDT
network_fee_amount      1.00 USDT
─────────────────────────────────
settlement_amount      42.32 USDT  ← you send this
```

Read `settlement_amount` and `total_fee_amount` directly. Do not recompute them
from `exchange_rate` and a fee percentage — rounding and fee schedules are ours to
change, and a recomputed figure that disagrees with ours is a reconciliation bug
waiting to happen.

Every amount is paired with its currency. Fees are charged in the settlement
currency, which is not the currency the beneficiary is paid in.

## Quote statuses

| Status     | Meaning                                                 |
| ---------- | ------------------------------------------------------- |
| `PENDING`  | Live and acceptable                                     |
| `ACCEPTED` | Explicitly accepted, still unused                       |
| `USED`     | Locked onto a payment instruction — immutable from here |
| `EXPIRED`  | Past its TTL — no longer usable                         |

```bash theme={null}
curl "$SPENDIN_BASE_URL/v1/rates/quotes/$QUOTE_ID" \
  -H "X-API-Key: $SPENDIN_API_KEY"
```

Expiry is evaluated when you read the quote, so a quote past its `expires_at`
reports `EXPIRED` on the next fetch rather than staying `PENDING` until something
sweeps it.

Accepting is optional:

```bash theme={null}
curl -X POST "$SPENDIN_BASE_URL/v1/rates/quotes/$QUOTE_ID/accept" \
  -H "X-API-Key: $SPENDIN_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
```

An expired quote cannot be accepted:

```json theme={null}
{
  "error": {
    "code": "QUOTE_EXPIRED",
    "message": "This FX quote has expired and can no longer be executed.",
    "details": [
      { "field": "quote_id", "issue": "Quote b7e2f1a0-... expired at 2026-08-16T09:41:30.000Z" }
    ],
    "request_id": "req_01HX..."
  }
}
```

Handle `QUOTE_EXPIRED` by locking a fresh quote and re-presenting the price. Never
retry the same quote ID — it cannot come back.

## How quotes relate to payouts

<Warning>
  **A quote is not an input to `POST /v1/payouts`.** There is no `fx_quote_id`
  field on a payout. Payout creation locks its own rate when the payment
  instruction is generated, at the rate prevailing at that moment.
</Warning>

So a quote is a **display** instrument — it tells your user what a payout will
cost, a second or two before they commit. The payout's own locked rate is what you
are actually charged, and it appears on the payout as `exchange_rate`,
`settlement_amount`, and the fee fields once the instruction exists.

The practical consequence: the price you showed and the price you pay can differ
slightly if the rate moved between quoting and creating. Keep the gap short, and
reconcile against the payout rather than the quote.

Once the instruction is issued, the rate is frozen for the life of that
instruction. If the instruction expires unpaid the payout ends `EXPIRED` and the
locked rate dies with it — see [Payout lifecycle](/guides/payout-lifecycle).

## Errors

| Code                           | Status | Cause                                             |
| ------------------------------ | ------ | ------------------------------------------------- |
| `UNSUPPORTED_CORRIDOR`         | `422`  | No corridor for that currency pair                |
| `SETTLEMENT_BELOW_MINIMUM`     | `422`  | The settlement amount is under the corridor floor |
| `QUOTE_EXPIRED`                | `422`  | The quote passed its TTL                          |
| `QUOTE_NOT_ACCEPTABLE`         | `409`  | Already accepted or used — not `PENDING`          |
| `QUOTE_NOT_FOUND`              | `404`  | No such quote for your account                    |
| `RATE_UNAVAILABLE`             | `503`  | We could not price the corridor right now         |
| `LIQUIDITY_ENGINE_UNAVAILABLE` | `503`  | The upstream pricing engine is unreachable        |

The two `503`s are transient — retry with backoff. See [Errors](/guides/errors).
