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

# Quote System

> How exchange rates, pricing, and payment execution work

Quotes are Grid's mechanism for providing locked-in exchange rates, transparent fee calculations, and payment instructions. Every transfer goes through a quote, whether or not a currency conversion is involved.

## What is a Quote?

A **quote** locks in:

* An exchange rate between two currencies
* Total fees for the transaction
* Exact amounts to be sent and received
* Payment instructions (if JIT funding is needed)
* An expiration time (typically 1-5 minutes, up to 15 minutes depending on the payment type)

Quotes ensure that your customers know exactly what they'll pay and what the recipient will receive before committing to a transaction.

## When Do You Need a Quote?

Use `POST /quotes` for every transfer:

* **Cross-currency transfers** (USD → EUR, BRL → MXN)
* **Fiat-to-crypto conversion** (USD → BTC)
* **Crypto-to-fiat conversion** (BTC → USD)
* **UMA payments**
* **JIT funded payments** (need payment instructions)
* **Same-currency transfers** (USD → USD), including pulling funds from a
  pullable external account into an internal account

For a same-currency transfer there is no exchange rate to lock, so the two-step
create-then-execute flow rarely buys you anything. Set `immediatelyExecute` to
`true` and the quote is created and executed in a single request.

> [!NOTE]
>
> The `/transfer-out` and `/transfer-in` endpoints previously served same-currency
> transfers and are now deprecated. They still work and their request and response
> shapes are unchanged, but new integrations should use `/quotes`.

## Creating a Quote

### Basic Cross-Currency Quote

This is a basic quote for a cross-currency transfer from an internal account to an external account,
which were pre-created as described in the [Account Model](https://agents-docs.lightspark.com/platform-overview/core-concepts/account-model.md) section.

```bash theme={null}
curl -X POST https://api.lightspark.com/grid/2025-10-13/quotes \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "source": {
      "sourceType": "ACCOUNT",
      "accountId": "InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965"
    },
    "destination": {
      "destinationType": "ACCOUNT",
      "accountId": "ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123"
    },
    "lockedCurrencySide": "SENDING",
    "lockedCurrencyAmount": 100000
  }'
```

**Response:**

```json theme={null}
{
  "id": "Quote:019542f5-b3e7-1d02-0000-000000000020",
  "status": "PROCESSING",
  "createdAt": "2025-10-03T15:00:00Z",
  "expiresAt": "2025-10-03T15:05:00Z",
  "source": {
    "sourceType": "ACCOUNT",
    "accountId": "InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965"
  },
  "destination": {
    "destinationType": "ACCOUNT",
    "accountId": "ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123"
  },
  "sendingCurrency": {
    "code": "USD",
    "name": "United States Dollar",
    "symbol": "$",
    "decimals": 2
  },
  "receivingCurrency": {
    "code": "BTC",
    "name": "Bitcoin",
    "symbol": "₿",
    "decimals": 8
  },
  "totalSendingAmount": 100000,
  "totalReceivingAmount": 828835,
  "exchangeRate": 120048.01920768,
  "feesIncluded": 500,
  "platformFeesIncluded": 0,
  "transactionId": "Transaction:019542f5-b3e7-1d02-0000-000000000025"
}
```

This quote says: Send \$1,000 USD, recipient receives 0.00828835 BTC, fees are \$5.00, expires in 5 minutes.

### Locked Currency Side

You can lock either the **sending** or **receiving** amount:

> [tab] Lock Sending Amount
>
> **Use when:** Customer knows exactly how much they want to send
>
> ```json theme={null}
> {
>   "lockedCurrencySide": "SENDING",
>   "lockedCurrencyAmount": 100000
> }
> ```
>
> Grid calculates what the recipient will receive based on current exchange rates.

> [tab] Lock Receiving Amount
>
> **Use when:** Recipient must receive an exact amount (e.g., invoice payment)
>
> ```json theme={null}
> {
>   "lockedCurrencySide": "RECEIVING",
>   "lockedCurrencyAmount": 92000
> }
> ```
>
> Grid calculates how much the sender must send to ensure recipient gets exactly €920.

## Funding Models

Grid supports two funding models for quotes:

### Prefunded (From Internal Account)

Source is an existing internal account with available balance:

```json theme={null}
{
  "source": {
    "sourceType": "ACCOUNT",
    "accountId": "InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965"
  }
}
```

* Funds are debited immediately when quote is executed
* No payment instructions needed
* Best for: Customers with pre-loaded balances

### Just-In-Time (JIT) Funding

Source is the customer ID or the platform itself — Grid provides payment instructions:

```json theme={null}
{
  "source": {
    "sourceType": "REALTIME_FUNDING",
    "customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001",
    "currency": "USD"
  }
}
```

**Quote response includes payment instructions:**

```json theme={null}
{
  "id": "Quote:...",
  "paymentInstructions": [
    {
      "instructionsNotes": "Please ensure the reference code is included in the payment memo/description field",
      "accountOrWalletInfo": {
        "reference": "UMA-Q12345-REF",
        "accountType": "USD_ACCOUNT",
        "accountNumber": "9876543210",
        "routingNumber": "110000000",
        "bankAccountType": "CHECKING",
        "bankName": "Chase Bank"
      }
    },
    {
      "accountOrWalletInfo": {
        "accountType": "SOLANA_WALLET",
        "assetType": "USDC",
        "address": "4Nd1m6Qkq7RfKuE5vQ9qP9Tn6H94Ueqb4xXHzsAbd8Wg"
      }
    }
  ]
}
```

* Customer sends funds to provided account with reference
* Quote executes automatically when Grid receives payment
* Best for: On-demand payments without maintaining balances

## Executing a Quote

For a prefunded quote or one from a pullable external account source, once a quote is created, execute it before it expires:

```bash theme={null}
curl -X POST https://api.lightspark.com/grid/2025-10-13/quotes/Quote:abc123/execute \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
  -H "Content-Type: application/json"
```

**Response:**

```json theme={null}
{
  "id": "Quote:019542f5-b3e7-1d02-0000-000000000050",
  "status": "PROCESSING",
  "transactionId": "Transaction:019542f5-b3e7-1d02-0000-000000000060",
  "totalSendingAmount": 100000,
  "totalReceivingAmount": 91540,
  "feesIncluded": 500,
  "platformFeesIncluded": 0
}
```

### Strong Customer Authentication (EU Customers)

For customers in regions where Strong Customer Authentication (SCA) is required (e.g., EU), quotes may enter a `PENDING_AUTHORIZATION` status with an `scaChallenge` object. The transfer is not released until the customer authorizes it.

Where the challenge appears depends on how the quote is funded:

* **Prefunded** — `POST /quotes/{quoteId}/execute` returns `PENDING_AUTHORIZATION` with the challenge instead of initiating the transfer. The challenge does not exist until `execute` is called, so the proof always goes on the follow-up authorize; re-calling `execute` returns `409`.
* **Realtime (JIT) funded** — the challenge arrives earlier and `paymentInstructions` are withheld until it is satisfied.

**Quote response with SCA challenge:**

```json theme={null}
{
  "id": "Quote:019542f5-b3e7-1d02-0000-000000000050",
  "status": "PENDING_AUTHORIZATION",
  "scaChallenge": {
    "id": "ScaChallenge:...",
    "expiresAt": "2025-10-03T12:05:00Z",
    "factor": "SMS_OTP",
    "availableFactors": ["SMS_OTP", "PASSKEY"],
    "purpose": "PAYOUT"
  }
}
```

To authorize the quote, call `POST /quotes/{quoteId}/authorize` with the appropriate proof:

```bash theme={null}
curl -X POST https://api.lightspark.com/grid/2025-10-13/quotes/Quote:abc123/authorize \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"code": "000000"}'
```

> [!NOTE]
>
> Multiple authorizations may be required in sequence (e.g., currency conversion + payout). After each authorization, check the quote status — if still `PENDING_AUTHORIZATION`, authorize the next challenge.

### Execution Timing

> [tab] Prefunded
>
> * Transaction created with status `PENDING`
> * Funds debited from source account immediately
> * Settlement begins right away

> [tab] JIT Funded
>
> * Quote waits for payment receipt
> * Once Grid receives payment with correct reference
> * Quote executes automatically
> * Transaction created and settlement begins

## Immediate Execution

For **market rate execution** without quote approval, use the `immediatelyExecute` flag:

```bash Immediate quote execution theme={null}
curl -X POST https://api.lightspark.com/grid/2025-10-13/quotes \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "source": {"sourceType": "ACCOUNT", "accountId": "InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965"},
    "destination": {"destinationType": "ACCOUNT", "accountId": "ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123"},
    "lockedCurrencySide": "SENDING",
    "lockedCurrencyAmount": 100000,
    "immediatelyExecute": true
  }'
```

> [accordion] Response
>
> ```json theme={null}
> {
>   "id": "Quote:019542f5-b3e7-1d02-0000-000000000020",
>   "status": "COMPLETED",
>   "createdAt": "2025-10-03T15:00:00Z",
>   "expiresAt": "2025-10-03T15:05:00Z",
>   "source": {
>     "sourceType": "ACCOUNT",
>     "accountId": "InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965"
>   },
>   "destination": {
>     "destinationType": "ACCOUNT",
>     "accountId": "ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123"
>   },
>   "sendingCurrency": {
>     "code": "USD",
>     "name": "United States Dollar",
>     "symbol": "$",
>     "decimals": 2
>   },
>   "receivingCurrency": {
>     "code": "EUR",
>     "name": "Euro",
>     "symbol": "€",
>     "decimals": 2
>   },
>   "totalSendingAmount": 100000,
>   "totalReceivingAmount": 91540,
>   "exchangeRate": 1.08695652,
>   "feesIncluded": 500,
>   "platformFeesIncluded": 0,
>   "transactionId": "Transaction:019542f5-b3e7-1d02-0000-000000000025"
> }
> ```

* Quote is created and executed in one API call
* Best for: Rewards distribution, micro-payments, time-sensitive transfers

> [!NOTE]
>
> `immediatelyExecute` can only be used for quotes with a source that is either an internal account or has direct pull functionality (e.g., ACH pull with an external account).

> [!WARNING]
>
> Customer doesn't see rate before execution. If you want to lock a quote and confirm fees and exchange rate details before executing the quote, set `immediatelyExecute` to `false` or omit the field.

## Fees

All fees are transparently displayed in the quote response:

```json theme={null}
{
  "id": "Quote:019542f5-b3e7-1d02-0000-000000000020",
  "feesIncluded": 500,
  "platformFeesIncluded": 0,
  ... quote response ...
  "rateDetails": {
    "counterpartyMultiplier": 1.08,
    "counterpartyFixedFee": 10,
    "gridApiMultiplier": 0.925,
    "gridApiFixedFee": 10,
    "gridApiVariableFeeRate": 0.003,
    "gridApiVariableFeeAmount": 300
  }
}
```

**Rate Details Breakdown:**

* **`counterpartyMultiplier`**: Exchange rate from mSATs to receiving currency (1.08 = 1 mSAT = 1.08 cents EUR)
* **`counterpartyFixedFee`**: Fixed fee charged by counterparty (10 cents EUR)
* **`gridApiMultiplier`**: Exchange rate from sending currency to mSATs including variable fees (0.925 = \$1 USD = 0.925 mSATs)
* **`gridApiFixedFee`**: Fixed fee charged by Grid API (10 cents USD)
* **`gridApiVariableFeeRate`**: Variable fee rate as percentage (0.003 = 0.3%)
* **`gridApiVariableFeeAmount`**: Variable fee amount (300 cents USD for \$1,000 transaction)

Fees are deducted from the sending amount, so:

* **Customer sends**: \$1,000
* **Fees**: \$5.00
* **Amount converted**: \$995.00
* **Recipient receives**: €915.40 (1 USD ≈ 0.92 EUR, or exchangeRate: 1.08695652)

> [!NOTE]
>
> Quoted fees may fluctuate between quotes. Some fee components (e.g.
> `counterpartyFixedFee` on outgoing transfers, and `gridApiFixedFee` on
> incoming transfers) are denominated in the receiving currency, so their
> value in the sending currency moves with the FX rate. Always reference the
> fees in the latest quote response — they are locked only for the lifetime
> of that quote.

### How the variable fee is applied

The variable fee (`gridApiVariableFeeRate`) is always assessed as a fraction of the **sending** amount. How that plays out depends on which side of the quote you lock (see [Locked Currency Side](#locked-currency-side)).

**Sending amount locked** (`lockedCurrencySide: "SENDING"`) — the fee is taken out of the amount you send, and the recipient receives the remainder:

```text theme={null}
received = send − send × fee
```

Send \$50 at a 10 bps (0.10%, `fee = 0.001`) rate: `received = 50 − 50 × 0.001 = 49.95`.

**Receiving amount locked** (`lockedCurrencySide: "RECEIVING"`) — Grid solves for the send amount that nets the exact received amount *after* the same fee:

```text theme={null}
send = received / (1 − fee)
```

Receive \$50 at 10 bps: `send = 50 / 0.999 = 50.05005005…`.

> [!NOTE]
>
> Receiver-locked quotes divide by `(1 − fee)` rather than adding `received × fee` so that locking the receiving side is never cheaper than locking the sending side at the same rate. Solving `send × (1 − fee) = received` for `send` keeps the effective fee rate identical across both locked sides.

For cross-currency quotes the exchange rate applies on top of this: the fee is taken on the sending currency, then the net amount is converted at the quoted rate. The same-currency examples above (for example USDC → USD at 1:1) show the fee in isolation.

## Best Practices

> [accordion] Always show the quote to customers before executing
>
> Let customers review the exchange rate, fees, and final amounts before committing:
>
> ```javascript theme={null}
> // ✅ Good: Show quote details, await confirmation
> const quote = await createQuote(params);
> showQuoteToUser(quote);
> if (await userConfirms()) {
>   await executeQuote(quote.id);
> }
>
> // ❌ Bad: Immediate execution without review (unless micro-payments/rewards)
> await createQuote({...params, immediatelyExecute: true});
> ```

> [accordion] Handle expiration gracefully
>
> Store quote parameters so you can recreate expired quotes:
>
> ```javascript theme={null}
> const quoteParams = {
>   source: {accountId: customerAccount},
>   destination: {accountId: recipientAccount},
>   lockedCurrencySide: 'SENDING',
>   lockedCurrencyAmount: amount
> };
>
> let quote = await createQuote(quoteParams);
>
> // Later, if expired...
> try {
>   await executeQuote(quote.id);
> } catch (error) {
>   if (error.code === 'QUOTE_EXPIRED' || error.code === 'QUOTE_RATE_UNAVAILABLE') {
>     quote = await createQuote(quoteParams); // Recreate with fresh rate
>     await executeQuote(quote.id);
>   }
> }
> ```
>
> `QUOTE_RATE_UNAVAILABLE` means the quoted rate was refused when the quote was
> executed, rather than the quote's own expiry window elapsing. Nothing was
> exchanged in either case, and the recovery is the same: create a new quote.

> [accordion] Monitor quote status via webhooks
>
> Subscribe to quote-related webhooks:
>
> ```javascript theme={null}
> app.post('/webhooks/grid', (req, res) => {
>   const {data: transaction, type} = req.body;
>
>   if (type.startsWith('OUTGOING_PAYMENT.') && transaction.quoteId) {
>     // Quote was executed, transaction created
>     updateCustomerUI(transaction);
>   }
>
>   res.status(200).send();
> });
> ```


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