# Quotes and exchange rates

A quote gives you a **confirmed preview of the cost and outcome of a proposed payment before you commit to sending it**. Every payment in Payments Direct starts with a quote: you request pricing for a proposed transfer, review the exchange rate and fees, and then use the quote to create the payment. If the quote expires before you act on it, you request a new one with updated pricing.

## What a quote includes

A quote captures all the financial terms for a proposed payment at a specific point in time:

- **Exchange rate** (`adjustedExchangeRate`): The rate Ripple will use to convert between the source and destination currencies. See [Exchange rates and FX margin](#exchange-rates-and-fx-margin) below.
- **Source and destination amounts** (`sourceAmount`, `destinationAmount`): How much leaves your account and how much arrives at the beneficiary. Depending on how you request the quote, one amount is the input you provide and the other is calculated from the exchange rate and fees. Amounts are rounded per the [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) standard for the relevant currency (for example, 2 decimal places for USD/EUR, 0 for JPY/KRW), using HALF_UP rounding.
- **Fees** (`fees`): Ripple's service charges for processing the payment. See [Fees](#fees) below.
- **Taxes** (`taxes`): Applicable consumption taxes on Ripple's service fees, where required by local regulations. See [Taxes on fees](#taxes-on-fees) below.
- **Payment rail**: The specific payment network used to deliver funds, such as ACH for US dollar payouts, SEPA for euro payouts, and SPEI for Mexican peso payouts.
- **Validity window** (`createdAt`, `expiresAt`, `quoteStatus`): When the quote was created and when it expires.


## Exchange rates and FX margin

When you request a quote, Ripple sources the current exchange rate for your currency pair from external market data providers. This base rate reflects prevailing market conditions at that moment.

Ripple then applies an **FX margin** to arrive at the **adjusted exchange rate** shown in your quote. The margin accounts for Ripple's cost of providing the foreign exchange service and managing rate risk between the time a quote is issued and the payment settles. The margin is expressed in basis points (hundredths of a percent) and is applied as a spread on top of the base rate.

The adjusted exchange rate is the only rate shown in your quote. The underlying base rate is not disclosed separately.

Rate lock
Once a quote is issued, the exchange rate is locked for the duration of the quote's validity window. Market movements during that time do not affect the rate shown in your quote.

## Fees

Each quote includes a **fee breakdown** showing Ripple's service charges for the payment. Fees can take two forms:

- **Flat fee**: A fixed charge regardless of the payment amount (for example, $3.00 per transaction).
- **Percentage fee**: A charge calculated as a percentage of the payment amount, expressed in basis points (for example, 50 basis points = 0.5%).


Some payments include both a flat fee and a percentage fee. The applicable fee structure depends on the currency corridor, payment rail, and your specific agreement with Ripple. All fees are reflected in the total cost shown in the quote before you commit to a payment.

## Taxes on fees

In jurisdictions where Ripple is required to collect consumption taxes (such as VAT or GST), those taxes apply to **Ripple's service fees only**, not to the payment principal. For example, if Ripple's service fee is $5.00 and the applicable tax rate is 10%, the tax is $0.50. The amount delivered to the beneficiary is not reduced by the tax.

If no taxes apply to a given payment, the `taxes` object will not appear in the quote response.

## Quote validity and expiration

Quotes are time-limited. By default, a quote is valid for **15 minutes** from the time it is issued, though your account may be configured with a different validity window.

- `quoteStatus=ACTIVE` means the quote can be used to create a payment.
- `quoteStatus=EXPIRED` means the quote is no longer valid and must be regenerated.


Use the `expiresAt` field to implement client-side logic, for example, prompting a refresh before the quote lapses. If a quote expires, request a new one. The new quote will reflect the current rate and fees at that moment.

## Quoting by source amount or destination amount

When you request a quote, you specify either the amount you want to send or the amount you want the beneficiary to receive:

- **Source amount** (`quoteAmountType: SOURCE_AMOUNT`): You specify how much to send (for example, 1,000 USD). Ripple calculates how much the beneficiary receives after applying the exchange rate and fees.
- **Destination amount** (`quoteAmountType: DESTINATION_AMOUNT`): You specify how much the beneficiary must receive (for example, 20,000 MXN). Ripple calculates how much you need to send to cover the exchange and fees.


Use source amount when the sender controls the spend. Use destination amount when the beneficiary needs to receive a precise amount, for example when paying an invoice denominated in the destination currency.

## Quote collections and payment rails

When you request a quote for a currency corridor, the response is organized as a **quote collection**. A quote collection can contain one or more quotes, one for each payment rail available for that corridor.

For example, a USD-to-EUR quote collection might include separate quotes for `SEPA_INSTANT` and `SEPA_STANDARD`, each with its own fee structure. This lets you compare available options before committing to a payment. When only one rail is available for a corridor, the collection contains a single quote.

If you need to route a payment over a specific rail, include `paymentRail` in your quote request to filter to that rail only.

Use the quote collection ID to retrieve the original set of quotes; use an individual quote ID to retrieve or act on a specific quote.

V3 required
Multi-rail quote collections are available on the `/v3/quotes/quote-collection` endpoint only. If your integration uses the `/v2/` endpoint, you will continue to receive single-quote responses and will not see per-rail quotes until you migrate to `/v3/`.

## How quotes connect to payments

A quote must be `ACTIVE` when you use it to initiate a payment. The typical flow is:

1. **Create a quote collection** for a proposed payment. Returns one quote per supported payment rail for the requested corridor.
2. **Select a quote** using the returned `quoteId`.
3. **Create the payment** using the quote information.


Once you create a payment referencing a quote, the quoted exchange rate and fees become the binding financial terms for that payment. The remaining validity time on the quote no longer applies after the payment is created.

Each quote is specific to one payment request. If a payment fails or is declined, you will need to request a new quote before retrying.

Implementation note
Internally, quote IDs are used in payment creation such that the `quoteId` used becomes the `paymentId`. Treat quote identifiers consistently across the quote and payment steps.

For the states a payment moves through after it is created, see [Payment lifecycle](/products/payments-direct-2/introduction/concepts/payment-lifecycle). For details on how your account balance is used once a payment is initiated, see [Funding methods](/products/payments-direct-2/introduction/concepts/funding-methods).

## Funding model (payinCategory)

The `payinCategory` field in the quote request specifies how you fund the payment. For an overview of each funding model, see [Funding methods](/products/payments-direct-2/introduction/concepts/funding-methods). The API values are:

| Value | Description |
|  --- | --- |
| `PRE_FUNDING` | The payment is funded from a balance you have pre-deposited with Ripple. Use this for funded account models where you maintain a prefunded ledger balance. |
| `CREDIT_FUNDING` | The payment is settled via an outstanding invoice. Ripple executes the payment and invoices you for the amount afterward. |
| `JIT_FUNDING` | The payment is funded just-in-time: you must transfer the funds to your Ripple ledger account before the payment expires. See [JIT-funded payments](#jit-funded-payments) below. |


Migrating from v2?
The values `FUNDED` and `T_PLUS_ONE` were accepted by the v2 endpoint but are **not valid** for `/v3/quotes/quote-collection`. Sending either value to the v3 endpoint returns a validation error. Use `PRE_FUNDING` in place of `FUNDED`, and `CREDIT_FUNDING` in place of `T_PLUS_ONE`.

### JIT-funded payments

When you use `JIT_FUNDING`, the payment enters an `AWAITING_FUNDING` state after it is created. You must transfer the required funds to your Ripple ledger account before the `jitFundingExpiresAt` timestamp on the payment object. If funding is not received in time, the payment expires and you must create a new quote and payment.

For more information on payment states including `AWAITING_FUNDING`, see [Payment lifecycle](/products/payments-direct-2/introduction/concepts/payment-lifecycle).

## Required inputs for quoting

To create a quote collection, provide:

- `quoteAmount` and `quoteAmountType` (`SOURCE_AMOUNT` or `DESTINATION_AMOUNT`)
- `sourceCurrency`, `destinationCurrency`
- `payinCategory` (`PRE_FUNDING`, `CREDIT_FUNDING`, or `JIT_FUNDING`)
- Optional: `sourceCountry`, `destinationCountry`
- Optional: `paymentRail`: filters quotes to a specific payment rail; when omitted, quotes are returned for all supported rails


## Endpoints

Enablement required
Access to the `/v3/quotes/quote-collection` endpoint requires explicit enablement by Ripple. If you attempt to use this endpoint before enablement is complete, requests will fail. Contact your Ripple representative to confirm your account is configured for V3 quotes before beginning integration.

Use these endpoints to create and retrieve quotes:

- `POST /v3/quotes/quote-collection`: Create a quote collection for a proposed payment.
- `GET /v3/quotes/quote-collection/{quote-collection-id}`: Retrieve a previously created quote collection.
- `GET /v3/quotes/{quote-id}`: Retrieve a specific quote by ID.


## Error handling

Authentication failures return standard HTTP 401/403 responses. Quote-specific errors use three prefixes:

- **USR_xxx**: validation errors (missing or invalid request fields)
- **SYS_xxx**: internal or upstream failures
- **CFG_xxx**: configuration errors (for example, unsupported corridor or missing tenant configuration)


Errors return a structured object with a status and an `errors` array. Each entry includes: `code`, `title`, `type`, `description`, and `timestamp`.

## Best practices

- Use `SOURCE_AMOUNT` when the sender knows what they want to send.
- Use `DESTINATION_AMOUNT` when the sender needs the beneficiary to receive an exact amount.


## Example: Create a quote collection

This example requests quotes for a USD-to-EUR payment. Because two payment rails are supported for this corridor (SEPA Instant and SEPA Standard), the response contains one quote per rail. You select the quote that best fits your needs before creating the payment.

**Request**


```bash
curl -i -X POST \
  https://{base-url}/v3/quotes/quote-collection \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Content-Type: application/json' \
  -d '{
    "quoteAmount": 1000.00,
    "quoteAmountType": "SOURCE_AMOUNT",
    "sourceCurrency": "USD",
    "destinationCurrency": "EUR",
    "sourceCountry": "US",
    "destinationCountry": "DE",
    "payinCategory": "PRE_FUNDING"
  }'
```

**Response (excerpt)**


```json
{
  "quoteCollectionId": "11111111-aaaa-2222-bbbb-222222222222",
  "quotes": [
    {
      "quoteId": "7ea3399c-1234-5678-8d8f-d320ea406630",
      "quoteStatus": "ACTIVE",
      "quoteAmountType": "SOURCE_AMOUNT",
      "sourceAmount": 1000.00,
      "destinationAmount": 921.45,
      "sourceCurrency": "USD",
      "destinationCurrency": "EUR",
      "destinationCountry": "DE",
      "payinCategory": "PRE_FUNDING",
      "paymentRail": "SEPA_INSTANT",
      "adjustedExchangeRate": {
        "adjustedRate": 0.9238
      },
      "fees": [
        {
          "totalFee": 8.50,
          "feeCurrency": "USD",
          "feeBreakdown": [
            {
              "calculatedFee": 0.50,
              "feeName": "Fixed service fee",
              "feeDescription": "Fixed service fee for payment rail SEPA_INSTANT.",
              "paymentRail": "SEPA_INSTANT"
            },
            {
              "calculatedFee": 8.00,
              "feeName": "Variable service fee",
              "feeDescription": "Variable service fee for payment rail SEPA_INSTANT.",
              "paymentRail": "SEPA_INSTANT"
            }
          ]
        }
      ],
      "createdAt": "2025-11-02T18:26:00.000123Z",
      "expiresAt": "2025-11-02T18:41:00.000123Z"
    },
    {
      "quoteId": "9fb4500d-2345-6789-9e9g-e431fb517741",
      "quoteStatus": "ACTIVE",
      "quoteAmountType": "SOURCE_AMOUNT",
      "sourceAmount": 1000.00,
      "destinationAmount": 921.45,
      "sourceCurrency": "USD",
      "destinationCurrency": "EUR",
      "destinationCountry": "DE",
      "payinCategory": "PRE_FUNDING",
      "paymentRail": "SEPA_STANDARD",
      "adjustedExchangeRate": {
        "adjustedRate": 0.9238
      },
      "fees": [
        {
          "totalFee": 5.25,
          "feeCurrency": "USD",
          "feeBreakdown": [
            {
              "calculatedFee": 0.25,
              "feeName": "Fixed service fee",
              "feeDescription": "Fixed service fee for payment rail SEPA_STANDARD.",
              "paymentRail": "SEPA_STANDARD"
            },
            {
              "calculatedFee": 5.00,
              "feeName": "Variable service fee",
              "feeDescription": "Variable service fee for payment rail SEPA_STANDARD.",
              "paymentRail": "SEPA_STANDARD"
            }
          ]
        }
      ],
      "createdAt": "2025-11-02T18:26:00.000123Z",
      "expiresAt": "2025-11-02T18:41:00.000123Z"
    }
  ]
}
```