Quote

Price a BRL to asset conversion, with the full fee breakdown, before you run an on-ramp or off-ramp.

POST /api/quote

Returns a price + itemised fee breakdown for a pair, so you can show the user what they will get before they commit.

A quote is pricing, not a rate lock. For a PIX payout there is now one that is: POST /api/wallet/payout/beneficiary resolves the payee and returns a quoteId that /api/wallet/payout settles against. This endpoint prices any pair and locks nothing. No other endpoint accepts a quoteToken today — /api/deposit/asset and /api/wallet/payout both price at execution time. Treat outputAmount as indicative, quote immediately before executing, and show the user the amount the operation response returns as the final one.

expiresAt is set 2 minutes after creation. It marks when the displayed price is stale enough that you should re-quote — it does not reserve anything.

Request

curl --request POST \
  --url https://api.hodle.com.br/api/quote \
  --header "Authorization: Bearer $API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "inputCurrency": "BRL",
    "inputPaymentMethod": "PIX",
    "outputCurrency": "USDC",
    "outputPaymentMethod": "BASE",
    "inputAmount": "100.00"
  }'
const res = await fetch('https://api.hodle.com.br/api/quote', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.HODLE_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    inputCurrency: 'BRL',
    inputPaymentMethod: 'PIX',
    outputCurrency: 'USDC',
    outputPaymentMethod: 'BASE',
    inputAmount: '100.00',
  }),
})
const data = await res.json()
import os, requests

res = requests.post(
    "https://api.hodle.com.br/api/quote",
    headers={
        "Authorization": f"Bearer {os.environ['HODLE_API_KEY']}",
        "Content-Type": "application/json",
    },
    json={
        "inputCurrency": "BRL",
        "inputPaymentMethod": "PIX",
        "outputCurrency": "USDC",
        "outputPaymentMethod": "BASE",
        "inputAmount": "100.00",
    },
)
data = res.json()

Parameters

FieldTypeRequiredDescription
inputCurrencystringYesBRL, USDT, USDC, USDB, BTC, BRS.
inputPaymentMethodstringYesPIX, POLYGON, BASE, TRON, LIGHTNING, SOLANA, SPARK.
outputCurrencystringYesSame set as inputCurrency.
outputPaymentMethodstringYesSame set as inputPaymentMethod.
inputAmountstringYes (or outputAmount)Amount in inputCurrency. Decimal string.
outputAmountstringYes (or inputAmount)Amount in outputCurrency. Use this for exact-out quotes.
outputBrCodestringNoPIX BR Code, when the output goes to a known PIX recipient. Used to route and price this quote. It carries no third-party permission — third-party operations are authorised per account, see Third-party operations.
addressstringNoBRL → BRS only. The Solana address the BRS will be delivered to. An address outside the account adds the deliveryFee block (external delivery must be enabled on the account — ask support).

Pass exactly one of inputAmount or outputAmount.

Response

200 OK
{
  "success": true,
  "data": {
    "quoteToken": "qt_8f3a4b...",
    "pairName": "BRL-USDC",
    "basePrice": "0.18634",
    "inputCurrency": "BRL",
    "outputCurrency": "USDC",
    "inputAmount": "100.00",
    "outputAmount": "18.41",
    "appliedFees": [
      { "type": "MARKUP",        "amount": "0.20", "currency": "USDC" },
      { "type": "INPUT_FIXED",   "amount": "0.50", "currency": "BRL"  },
      { "type": "OUTPUT_FIXED",  "amount": "0.05", "currency": "USDC" }
    ],
    "expiresAt": "2026-05-09T22:02:00.000Z"
  }
}
FieldTypeDescription
data.quoteTokenstringProvider-side reference, returned for traceability. No Hodle endpoint accepts it as input — it does not lock the rate.
data.basePricestringMid-market outputCurrency per 1 unit of inputCurrency before fees.
data.outputAmountstringWhat the user actually receives after fees are applied.
data.inputAmountSatsintegerBTC sats required for a BTC → BRL exact-out quote. Present only for this route.
data.outputAmountSatsintegerBTC sats delivered by a BRL → BTC quote. Present only for this route.
data.feeInCentsintegerTotal fee charged to the authenticated API-key owner, after its custom fee override and floor.
data.appliedFeesarrayItemised — your UI can render each line.
data.expiresAtstringISO timestamp; after this, re-quote.
data.settlementobjectPresent for composed Solana and Flashnet routes; identifies the priced settlement path.

Solana quotes

SOLANA is routable with USDT, USDC and BRS; any other currency on that payment method answers 400 "No route ... at this time". BRL on Solana is BRS — send outputCurrency: "BRS", not BRL.

BRS

BRS is quoted against BRL over Pix in both directions, at basePrice 1:

PairAmount fieldMeaning
BRL/PIX → BRS/SOLANAinputAmountThe Pix charge; outputAmount is the BRS minted after fees.
BRS/SOLANA → BRL/PIXoutputAmountThe Pix payout; inputAmount is the BRS debited, fee included.

The account needs BRS enabled (403 otherwise) and the minimum is R$5.00. BRS settles on the account's own Hodle Solana wallet unless you send address on a BRL → BRS quote: when external delivery is enabled on the account, an external Solana address is priced with its delivery fee (the total in BRS, paid from the account's own Hodle wallet), without signing or debiting anything. Otherwise an external address answers 403 — ask support to enable it. See BRS to an external Solana wallet.

200 OK — BRL → BRS to an external address
{
  "success": true,
  "data": {
    "pairName": "BRLBRS",
    "inputAmount": "100.00",
    "outputAmount": "100.00",
    "feeInCents": 0,
    "settlement": {
      "deliveryNetwork": "SOLANA",
      "destination": "EXTERNAL_SOLANA_WALLET"
    },
    "deliveryFee": {
      "feeCents": 144,
      "recipientHasTokenAccount": false,
      "paidIn": "BRS",
      "paidFrom": "OWN_HODLE_SOLANA_WALLET"
    }
  }
}

A BRS → BRL quote prices from where the wallet's BRS came from, which is read in the background. The request waits up to ~5 seconds for that read and normally answers 200. Only if it takes longer does it answer 202 {"success": true, "status": "PENDING", "pricingPending": true}; retry the same request after a second or two.

200 OK — BRL → BRS
{
  "success": true,
  "data": {
    "pairName": "BRLBRS",
    "basePrice": "1",
    "inputAmount": "100.00",
    "outputAmount": "99.00",
    "feeInCents": 100,
    "appliedFees": [{ "type": "MARKUP_FEE", "amount": "1.00", "currency": "BRL" }],
    "settlement": {
      "provider": "NORA",
      "deliveryNetwork": "SOLANA",
      "destination": "OWN_HODLE_SOLANA_WALLET"
    }
  }
}

A Solana quote is priced on the Base leg, because that is where the money actually lands before it moves: a Pix on-ramp settles USDC on Base and Relay then bridges it to the destination Solana address. The response says so explicitly:

200 OK — BRL → USDC on Solana
{
  "success": true,
  "data": {
    "outputAmount": "18.41",
    "settlement": {
      "quotedNetwork": "BASE",
      "deliveryNetwork": "SOLANA",
      "bridge": "relay",
      "bridgeFeeIncluded": false
    }
  }
}

bridgeFeeIncluded: false is the part to plan around — the Relay bridge cost is charged on delivery, so the amount that lands on Solana is marginally below outputAmount. Do not treat a Solana quote as an exact-out guarantee.

BRL ↔ BTC quotes

BTC quotes use the same Flashnet/Orchestra swap route as settlement and price the fee of the user that owns the API key. This matters for accounts with a custom LIGHTNING percentage, fixed fee, or minimum fee.

  • BRL → BTC is an exact-input quote: send inputAmount in BRL with PIX → LIGHTNING or SPARK. The response returns outputAmountSats.
  • BTC → BRL is an exact-output quote: send outputAmount in BRL with LIGHTNING or SPARK → PIX. The response returns inputAmountSats, the exact invoice amount to request from the payer.
BRL → BTC — supported deposit-sized quote
{
  "inputCurrency": "BRL",
  "inputPaymentMethod": "PIX",
  "outputCurrency": "BTC",
  "outputPaymentMethod": "LIGHTNING",
  "inputAmount": "21.00"
}

inputAmount is the gross Pix amount, matching deposit/asset.value. The quoted sats are calculated from the amount left after feeInCents; for the Passbit fee of 1.5% with a R$ 1 minimum, R$ 20 is converted on a R$ 21 charge.

A R$ 10 Passbit simulation is rejected with 400 "Valor mínimo para compra de BTC é R$ 20". This is intentional: the matching BTC/Lightning deposit rail has the same minimum, so the quote endpoint does not promise sats for a charge that cannot settle.

BTC → BRL exact output
{
  "inputCurrency": "BTC",
  "inputPaymentMethod": "LIGHTNING",
  "outputCurrency": "BRL",
  "outputPaymentMethod": "PIX",
  "outputAmount": "100.00"
}

Errors

400 — pair not supported
{ "success": false, "error": "No route for BRL-USDC at this time" }
400 — amount below minimum
{ "success": false, "error": "inputAmount must be at least 1.00 BRL" }

There is no "expired quote" error, because nothing consumes the quote. Once expiresAt has passed, call /api/quote again.

Common quote shapes

Use caseInputOutput
BRL → USDC (Base) on-rampBRL / PIXUSDC / BASE
BRL → USDT (Polygon) on-rampBRL / PIXUSDT / POLYGON
BRL → USDC (Solana) on-rampBRL / PIXUSDC / SOLANA
USDC (Solana) → BRL off-rampUSDC / SOLANABRL / PIX
USDT (Tron) → BRL off-rampUSDT / TRONBRL / PIX
BRL → BTC (Lightning) on-rampBRL / PIXBTC / LIGHTNING
Lightning → BRL off-rampBTC / LIGHTNINGBRL / PIX
Lightning → USDB (Spark via Flashnet AMM)BTC / LIGHTNINGUSDB / SPARK