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

# Currency Quotes

> Show and confirm the final amount with the buyer before authorization when your company passes the currency spread on to them.

Some companies present prices in one currency and settle in another. When the company is configured
to pass the currency spread on to the buyer, the buyer is charged **more** than the commercial price —
and that final amount must be shown to them and confirmed **before** authorization.

The hosted Checkout does this on its own. Everywhere else — the Payment Element, a direct API
integration, your own payment page — the screen belongs to you, so the guarantee is contractual: you
create a quote, display it, and pass its id when creating the transaction.

<Note>
  This page only applies to companies configured for buyer-paid currency spread. If your company
  absorbs the spread, nothing changes: keep creating transactions exactly as you do today.
</Note>

## The flow

<Steps>
  <Step title="Create the quote">
    Call `POST /v2/fx/quotes` with the commercial amount, the currency you present to the buyer, and the
    payment method you will charge.

    ```bash theme={null}
    curl -X POST https://api.pagou.ai/v2/fx/quotes \
      -H "Authorization: Bearer $PAGOU_SECRET_KEY" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: order-1029" \
      -d '{ "amount": 1000, "currency": "BRL", "payment_method": "pix" }'
    ```

    ```json theme={null}
    {
      "data": {
        "id": "0198f3a0-1c2d-7e00-9a3b-6f8a2c4d1e5f",
        "presented_currency": "BRL",
        "wallet_currency": "USD",
        "payment_method": "pix",
        "base_rate": "0.2000000000",
        "spread_bps": 500,
        "presented_amount": 1000,
        "spread_amount": 50,
        "buyer_charged_amount": 1050,
        "expires_at": "2026-08-13T12:05:00.000Z"
      }
    }
    ```

    Amounts are integers in the currency's smallest unit. `Idempotency-Key` is optional; repeating a call
    with the same key returns the same quote instead of freezing a second rate.
  </Step>

  <Step title="Show the buyer what they will pay">
    Before the buyer authorizes, your page must display:

    * **the final amount** (`buyer_charged_amount`), in the charged currency, as the prominent figure — it
      must equal what gets authorized;
    * the commercial amount (`presented_amount`) and its currency, when different;
    * the applied rate (`base_rate`) and the destination currency;
    * a neutral, consistent label.

    Never:

    * call it a tax, IOF, MDR, card fee, or network fee — it is none of those;
    * present it as a choice the buyer makes;
    * change the amount between confirmation and authorization;
    * reveal the spread only after the buyer picks a payment method.

    <Warning>
      The last two are not stylistic. An amount that changes after confirmation, or a markup that appears
      only once a method is chosen, changes what the charge legally is.
    </Warning>
  </Step>

  <Step title="Create the transaction with the quote">
    Pass `fx_quote_id` on `POST /v2/transactions`. The charge uses the rate frozen in the quote, not the
    current one, so the buyer is authorized for exactly the amount they confirmed.

    ```json theme={null}
    {
      "amount": 1000,
      "currency": "BRL",
      "method": "pix",
      "fx_quote_id": "0198f3a0-1c2d-7e00-9a3b-6f8a2c4d1e5f",
      "buyer": { "name": "...", "email": "..." }
    }
    ```

    `amount` stays the **commercial** amount — the same one you quoted. The buyer-facing total is derived
    from the quote; sending it here would be quoting one figure and charging another.
  </Step>
</Steps>

## With the Payment Element

The Element tokenizes the card and handles 3DS; it never renders an amount. So the quote lives in your
own flow, around `submit`:

```js theme={null}
const quote = await fetch("/api/fx-quote", { method: "POST" }).then((r) => r.json());

renderTotal(quote); // your UI, following the display rules above

const result = await elements.submit({
  createTransaction: ({ token }) =>
    fetch("/api/pay", {
      method: "POST",
      body: JSON.stringify({ token, fx_quote_id: quote.id }),
    }).then((r) => r.json()),
});
```

Your backend forwards `fx_quote_id` to `POST /v2/transactions`.

## Errors

| code                         | what happened                                                                                                 |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `FX_QUOTE_REQUIRED`          | The company passes the spread to the buyer and no `fx_quote_id` was sent. Create a quote, show it, and retry. |
| `FX_QUOTE_EXPIRED`           | The quote lapsed. Create a new one and confirm the new total with the buyer — never reuse the old figure.     |
| `FX_QUOTE_REUSED`            | The quote already authorized another transaction. Quotes are single-use.                                      |
| `FX_QUOTE_AMOUNT_MISMATCH`   | The transaction amount differs from the quoted commercial amount.                                             |
| `FX_PASSTHROUGH_NOT_ALLOWED` | This method and currency are not approved for passing the spread to the buyer.                                |
| `503`                        | No usable exchange rate. The charge is refused rather than converted at a made-up rate — retry shortly.       |

## Notes

* Quotes are single-use and expire with the rate that produced them; expect to create one per checkout
  attempt.
* A quote belongs to one company. Quotes from another company return `404`.
* Same presented and wallet currency means there is no currency conversion, and `POST /v2/fx/quotes`
  returns `422`.
