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

# Quotes

> Pricing a transfer — the four shapes, what fixes the rate, the limits that reject it, and the ten minutes you have to use it

A quote is Endl's priced answer to "if I send this, what arrives?". It fixes an FX
rate and a fee breakdown, stamps an expiry on them, and hands you an id. That id is
the only way to start a transfer — **nothing moves money without one**.

<Warning>
  **A quote is time-boxed and single-use.** It is good for **ten minutes**, and it
  can back **exactly one transaction, ever**.
</Warning>

<Info>
  This is the conceptual guide. For the endpoints themselves — parameters, schemas
  and worked bodies — see [Generate quote](/api-reference/quotes/generate-quote)
  and [Get quote](/api-reference/quotes/get-quote).
</Info>

## Where a quote sits

<Steps>
  <Step title="Price it">
    [Generate quote](/api-reference/quotes/generate-quote). You get a `qut_…` id,
    the destination amount, the rate and every fee line.
  </Step>

  <Step title="Commit to it">
    [Create pre-transaction](/api-reference/transactions/create-pre-transaction)
    takes the `qut_` id and a recipient, and turns the price into an intent.
  </Step>

  <Step title="Send it">
    [Submit transaction](/api-reference/transactions/submit-transaction) releases
    the money.
  </Step>
</Steps>

Everything on this page is about step one. **The clock starts the moment the quote
is generated**, and it runs through steps two and three.

## The four shapes

What a quote *is* follows entirely from the two currencies. Endl treats `USDC`,
`USDT` and `RLUSD` as stablecoins and everything else as fiat.

| Source → Destination | Shape | Chain needed on | KYC depth |
| - | - | - | - |
| Fiat → Fiat | A cross-border payment | Neither leg | Bridge |
| Fiat → Stablecoin | An on-ramp | `destination.chain` | Bridge |
| Stablecoin → Fiat | An off-ramp | `source.chain` | Bridge |
| Stablecoin → Stablecoin | A crypto transfer | Both legs, when supplied | Sumsub only |

<Note>
  **The shape decides which KYC the customer needs**, and the two are not
  interchangeable. A stablecoin-to-stablecoin transfer never touches a fiat
  account, so it needs only Sumsub verification. Anything that touches fiat through
  Bridge needs the full Bridge check. The one exception is **Paywho-routed** quotes,
  which also need only Sumsub.

  A customer who can quote `USDC → USDC` may therefore be refused on `USDC → EUR`.
  That is the gate working, not a bug.
</Note>

## What every quote must carry

Five things are mandatory on every request, whatever the shape.

| Field | Notes |
| - | - |
| `depositRail` | How the money arrives. Coupled to `source.currency` — see below. |
| `payoutRail` | How it leaves. `ACH`, `SEPA`, `SWIFT`, `PIX`, `SPEI`, `UPI`, `BANK_TRANSFER`, `CRYPTO`, … |
| `accountType` | `INDIVIDUAL` or `BUSINESS`. **This is what the fee slab is priced from.** |
| `source` | At minimum `currency`. |
| `destination` | At minimum `currency`. |

Plus **exactly one amount**, on one leg or the other.

<Warning>
  **`depositRail` is not free.** A `USDC` or `USDT` source must use `CRYPTO`; any
  other source currency must **not**.

  Get it wrong and the message you receive names `CRYPTO_WALLET` or
  `CRYPTO_MANUAL_WALLET` — rails that do not exist in the v0 vocabulary. **The
  message is stale**; the value the validator actually wants is `CRYPTO`. Ignore
  the wording and send `CRYPTO`.
</Warning>

<Note>
  **`RLUSD` is a special case on the source leg.** It counts as a stablecoin when
  the shape is classified, and an `RLUSD` source is rewritten internally to `USDC`
  on `arbitrum` for routing, with an RLUSD conversion fee applied to the result.
  The deposit-rail rule above, though, only names `USDC` and `USDT`. Confirm
  RLUSD-as-source with Endl before building on it; `RLUSD` as a *destination* is
  unambiguous.
</Note>

### Forward and reverse quotes

Send `source.amount` to price forwards — *"I am sending 1,000, what lands?"*. Send
`destination.amount` to price backwards — *"the recipient must receive exactly
50,000, what do I send?"*. **Send one, never both, never neither.**

| What you sent | Result |
| - | - |
| Neither | `400 Source or Destination amount is required` |
| A zero or negative amount | `400 Source amount must be greater than 0` (or `Destination …`) — a different error from the one above, deliberately |
| Both | Accepted, but the **source amount wins** — pricing runs forwards and the destination amount you sent is replaced by the computed one |

A reverse quote derives the source amount by working backwards through fees and FX,
so **the number you get back is not the simple inverse of the forward rate**.

### Rounding

Amounts are normalised to **two decimal places before anything is priced**
(half-up), and rounded again on the way out — deliberately in your favour, never
Endl's:

* `source.amount` is rounded **up**, because it is money coming in.
* `destination.amount` is rounded **down**, because it is money going out.

So a reverse quote for exactly 50,000 can return a source amount a cent above the
true figure. **Do not assert exact equality when reconciling**; compare to the cent.

## Chains, and why they are spelled differently here

Whenever a leg is a stablecoin, the chain matters. On a one-sided shape the chain on
the crypto leg is **required**; on stablecoin → stablecoin it is optional per leg,
but validated whenever supplied.

<Warning>
  **Quotes use a different chain vocabulary from [wallets](/guides/wallets).** A
  wallet lives on `ArbitrumOne`; a quote names that network `arbitrum`. They are not
  interchangeable, and passing a wallet's network name to a quote is one of the most
  common first-integration failures.
</Warning>

| Quote chain | The wallet network it corresponds to |
| - | - |
| `ethereum` | `Ethereum` |
| `base` | `Base` |
| `arbitrum` | `ArbitrumOne` |
| `optimism` | `Optimism` |
| `polygon` | `Polygon` |
| `tron` | `Tron` |
| `xrp` | `XrpLedger` |
| `solana` | *(quotes only — no wallet network)* |

Quote chains are matched **case-insensitively**, so `Base` and `base` both work
here. There is no quote chain for `AvalancheC`.

<Note>
  **Testnet chain names are accepted only where the platform runs on testnets.** In
  sandbox and QA you may name `basesepolia`, `ethereumsepolia`, `arbitrumsepolia`,
  `optimismsepolia`, `polygonamoy`, `tronnile` or `xrpledgertestnet`, and each is
  judged as the mainnet it mirrors. Production rejects all of them.
</Note>

### Not every currency is on every chain

Two rules, applied to each leg independently:

* **USDT is only issued on `ethereum` and `tron`.** USDT on Base prices nowhere.
* **TRON carries only USDT.** USDC on `tron` is refused.

A violation is `400 USDT is not supported on the base network (source.chain)` — the
message names the leg, so you know which half to fix.

## Amount limits

Four separate ceilings can reject the same amount. They are checked in this order,
and **the first one to fire is the one you are told about** — the order is
deliberate, so you get the limit you can actually act on rather than an internal one.

<Steps>
  <Step title="Account-type limits">
    | Account type | Minimum | Maximum |
    | - | - | - |
    | `INDIVIDUAL` | 100 USD | 100,000 USD |
    | `BUSINESS` | 100 USD | 500,000 USD |

    On a reverse quote whose derived source amount lands under the floor, a business
    is allowed down to **50 USD**; an individual is not.
  </Step>

  <Step title="SWIFT's own floor">
    Any quote with SWIFT on either leg has a minimum of **15,200 USD**, checked
    *before* the account-type floor.
  </Step>

  <Step title="Structural ceilings">
    The quote record itself cannot hold a source amount above `999,999.9999` or a
    destination amount above `99,999,999.99`. These are not commercial limits and
    nothing is exempt from them.
  </Step>

  <Step title="The payout rail's published maximum">
    Each rail carries its own cap — the same number
    [Get recipient required fields](/api-reference/reference/get-recipient-required-fields)
    reports. Where several providers serve the same rail, **the widest cap applies**,
    so only an amount *no* provider could serve is rejected here.
  </Step>
</Steps>

Two routes sit outside the account-type band:

* **Stablecoin → stablecoin lifts the minimum but keeps the maximum.** The 100 USD
  floor is a fiat-settlement rule and a crypto transfer can legitimately be smaller;
  the account ceiling still applies.
* **AED is priced from its own fee configuration** rather than the shared USD band,
  on either leg.

## Routing you do not control

You choose rails; you do not choose providers. Endl picks the provider from the
currencies and the rail, and **the choice is invisible in the response** — but it
changes which KYC the customer needs.

A quote routes to **Paywho** when either leg is a Paywho-supported currency on a
Paywho payout rail, or when either leg is `INR` on `BANK_TRANSFER`, `IMPS`, `UPI` or
`CRYPTO`. Everything else routes to **Bridge**.

Paywho-routed and stablecoin-to-stablecoin quotes need **Sumsub** verification;
Bridge-routed quotes need the full **Bridge** check.

### Pairs that are refused outright

| Pair | Why |
| - | - |
| `USDT` → `EUR` | Not supported. Use `USDC` as the source for EUR payouts. |
| `USDT` → `GBP` | Not supported. Use `USDC` as the source. |
| Anything → `USDC` / `USDT` over `SWIFT` | SWIFT cannot pay out crypto. Use the `CRYPTO` payout rail. |
| `USDC` / `USDT` over SWIFT from a chain other than Ethereum or TRON | SWIFT deposits only settle from those two. |
| SWIFT on an individual account | SWIFT is **business-only** — the user record **and** `accountType` must both say `BUSINESS`. |

SWIFT additionally has to be **enabled on the customer**. Being a business is
necessary, not sufficient.

## Reading the quote back

| Field | What it is |
| - | - |
| `quoteId` | The `qut_…` reference. The only input Create pre-transaction needs from here. |
| `expiresOn` | **Ten minutes** after generation. |
| `destination.amount` | What the recipient receives, after fees. |
| `fxRate` | The cumulative rate from Endl's provider, **without markup applied**. |
| `feeList` | Every fee line. |
| `feeTotal` / `feeCurrency` | The sum, always denominated in the **source** currency. |
| `gasFeeBreakdown` | Estimated vs charged on-chain gas. `null` when there is no on-chain leg. |
| `coupon` | Only present when a coupon applied. `null` otherwise. |

<Warning>
  **`fxRate` is not the rate you are charged at.** It is the raw partner rate
  *before* markup; the markup is charged as a fee line instead. Reconciling by
  multiplying the source amount by `fxRate` **will not** reproduce
  `destination.amount`. Use the amounts, and use `feeList` for the breakdown.
</Warning>

Fee lines carry a `feeCategoryTypeEnum` — `EXCHANGE_FEE`, `EXCHANGE_MARKUP_FEE`,
`OUTGOING_PROCESSING_FEE`, `GAS_FEE`, `PLATFORM_FEE`, `PARTNER_FEE`, `TDS_FEE` and
others. **Treat the set as open**: display what you get, and do not switch
exhaustively on it.

<Note>
  `source.amountToConvert` and `amountToConvertActual` are **display-only** fields
  describing what will actually be converted. Do not use them as inputs to anything.
</Note>

### Fields that are quietly ignored

Three things you might send have no effect, and **none of them is an error**:

* **`partnerId`** — overwritten with whoever the API key authenticated as. You
  cannot file a quote under another partner.
* **`overrideGasFee`** — a merchant-portal feature, dropped on the v0 surface. You
  get the quote you asked for, priced with Endl's own gas fee.
* **Any unknown property** — ignored rather than rejected.

<Warning>
  **That last one has teeth.** Misspelling a field name produces a "missing field"
  error, not an "unknown field" one. If `destination.currency` is required and you
  sent `destination.curency`, what you will read is
  `destination.currency field is required`. **Check your spelling before you check
  your logic.**
</Warning>

## The ten minutes

`expiresOn` is set **600 seconds** after generation, and expiry is enforced when you
**use** the quote, not when you read it.
[Get quote](/api-reference/quotes/get-quote) reports `expired` as a boolean and will
happily return an expired quote — it is a record, not a reservation. Create
pre-transaction is where an expired quote is refused.

<Warning>
  **A quote backs exactly one transaction.** Once a transaction exists against a
  `qut_` id, reusing it is refused with *"Transaction already exists … get a fresh
  quote"* — **including after a failed or cancelled transaction**.

  Retrying a failed submit means generating a **new** quote, not resending the old
  one. Build that into your retry path from the start.
</Warning>

Do not generate quotes speculatively to warm a cache. Price when the customer is
ready to confirm, and re-price if they hesitate past the window.

## Error handling

Quote failures are `400` with the specific reason in `errors[0]`, and the messages
are written to be shown to a user. The one quote-domain code is `ERRQUO_1000`
(`404`), for a quote that does not exist **or** belongs to another customer — the
same answer either way, so quote ids cannot be probed. Everything else uses the
shared envelope and the `ERRCORE_` codes in
[Errors and the response envelope](/api-reference/errors).

**Branch on `errors[0].code`, not on the message text.**

| You get | Do this |
| - | - |
| `400` | Fix the input. **Never retry unchanged** — pricing is deterministic. |
| `403` | The customer lacks the KYC depth this route needs, or the key lacks `quotes`. |
| `404` | The customer or quote does not exist under this partner. |
| `429` | Back off. The window is one minute. |
| `502` / `503` | A pricing provider is unavailable. **Safe to retry** — no quote was created. |

## Common pitfalls

<AccordionGroup>
  <Accordion title="400 'For USDC/USDT source currency, depositType must be CRYPTO_WALLET or CRYPTO_MANUAL_WALLET'">
    The message is stale. Send `depositRail: "CRYPTO"`. Those two rail names do not
    exist in the v0 vocabulary.
  </Accordion>

  <Accordion title="I passed my wallet's network and the quote rejected the chain">
    Quotes and wallets use different chain vocabularies. A wallet on `ArbitrumOne`
    is `arbitrum` to a quote; `XrpLedger` is `xrp`. See the mapping table above.
  </Accordion>

  <Accordion title="400 'source.currency field is required' and I definitely sent it">
    Unknown properties are ignored rather than rejected, so a misspelled field name
    reads as a missing one. Check the spelling of the field you think you sent.
  </Accordion>

  <Accordion title="source.amount × fxRate does not equal destination.amount">
    Expected. `fxRate` is the pre-markup partner rate; the markup is a fee line, not
    a rate adjustment. Reconcile with the amounts and `feeList`.
  </Accordion>

  <Accordion title="My retry failed with 'Transaction already exists'">
    Quotes are single-use, and the first attempt consumed this one — even if it
    failed. Generate a fresh quote for every attempt.
  </Accordion>

  <Accordion title="Get quote returned 200 for a quote I know has expired">
    Get quote returns the record and reports `expired: true`. Expiry is enforced at
    Create pre-transaction, not on read.
  </Accordion>

  <Accordion title="A customer can quote USDC to USDC but gets 403 on USDC to EUR">
    Different KYC depth. Stablecoin-to-stablecoin needs only Sumsub; a fiat leg
    through Bridge needs the full Bridge check.
  </Accordion>

  <Accordion title="SWIFT is refused on an account that definitely has it enabled">
    SWIFT is business-only. Both the customer record and the `accountType` in the
    body must say `BUSINESS`; having the rail enabled does not make an individual
    eligible. It also has a 15,200 USD floor.
  </Accordion>

  <Accordion title="A small stablecoin transfer was rejected for being under 100">
    Check **both** legs are stablecoins. The floor is lifted only for stablecoin →
    stablecoin; a stablecoin → fiat transfer keeps it.
  </Accordion>

  <Accordion title="I sent both source.amount and destination.amount">
    The source amount wins silently. Send exactly one, so the quote prices the
    direction you meant.
  </Accordion>
</AccordionGroup>

## Glossary

| Term | Meaning |
| - | - |
| Quote | A priced, time-boxed, single-use answer to "what arrives if I send this?". |
| Forward quote | Priced from `source.amount` — you fix what you send. |
| Reverse quote | Priced from `destination.amount` — you fix what the recipient receives. |
| Deposit rail | How money reaches Endl. `CRYPTO` for stablecoin sources, a fiat rail otherwise. |
| Payout rail | How money leaves Endl — `ACH`, `SEPA`, `SWIFT`, `PIX`, `SPEI`, `UPI`, `CRYPTO`, … |
| Chain | The network a stablecoin leg sits on, lower-case: `ethereum`, `base`, `tron`. |
| `fxRate` | The cumulative partner rate, **before** markup. Markup is a fee line. |
| Expiry | Ten minutes from generation, enforced at Create pre-transaction. |
| Reference id | A prefixed public identifier — `qut_`, `cus_`, `wlt_`, `acct_`, `txn_`. |

<Note>
  Written against `Api-Version: 2026-09.1`. Rail availability, per-rail caps and
  currency support are configuration and vary by customer and country — read them
  from [List rails](/api-reference/reference/list-rails) and
  [Get recipient required fields](/api-reference/reference/get-recipient-required-fields)
  rather than hardcoding this page.
</Note>


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