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

# Itemized quotations

> Price virtual-account flows with a line-by-line fee breakdown using POST /v1/quotations on API version 2026-06-01, and lock the price into a redeemable quote.

The **itemized quotation** endpoint (`POST /v1/quotations`, API version `2026-06-01`) is the pricing surface for virtual-account flows. It returns a line-by-line fee breakdown — every fee as its own object with a stable `code`, who it is `charged_by` (Kira vs. your markup), and the inputs that produced it — plus the tier context and the depeg-aware conversion rate. A VA-bound quote is **persisted** and returns a `quote_id` you redeem when you create the payout, locking the price for **15 minutes**.

This guide covers:

* How to engage the itemized shape (the version header).
* Creating a **preview** (pre-VA, ephemeral) and a **VA-bound quotation** (persisted, redeemable).
* Redeeming a quote in a **payout**.
* **Migrating** from the previous pricing model (`payout/preview` + `create_quote`).

Every example uses the sandbox base URL `https://api.balampay.com/sandbox`; in production, drop the `/sandbox` prefix.

<Info>
  **Two independent axes — read this first.**

  * **Response shape** is controlled by **you**, via the `X-Api-Version` header. Sending `2026-06-01` is what switches `POST /v1/quotations` to the itemized envelope below.
  * **Which pricing engine computes your fees** is configured by Kira per account (a controlled rollout). The itemized shape renders whichever engine is active for your account. You do not toggle the engine from the API — contact your account manager to be enabled.
</Info>

## Engage the itemized shape

Every request carries the standard credentials plus the version header that selects the itemized envelope:

```bash theme={null}
curl https://api.balampay.com/sandbox/v1/quotations \
  -H "X-Api-Version: 2026-06-01" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "x-api-key: $KIRA_API_KEY" \
  -H "Content-Type: application/json"
```

Like every authenticated Kira call, a quotation request needs **both** the `x-api-key` header and the `Authorization: Bearer <access token>` token from `POST /auth` (see [Authentication](/guides/authentication)).

<Note>
  Without `X-Api-Version: 2026-06-01`, `POST /v1/quotations` returns the previous (aggregated) preview shape. The header is what selects the itemized envelope documented here.
</Note>

## A. Preview — price before you have a virtual account

Use `quote_for` (the origin rail + currency) to price a hypothetical flow during onboarding, before any VA exists. A preview is **ephemeral**: `quote_id` is `null`, nothing is persisted, and it cannot be redeemed.

```bash theme={null}
curl https://api.balampay.com/sandbox/v1/quotations \
  -H "X-Api-Version: 2026-06-01" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "x-api-key: $KIRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "quote_for": { "rail": "WIRE_DOMESTIC", "currency": "USD" },
    "amount": "50000.00",
    "target": { "currency": "USDC", "network": "solana" }
  }'
```

Request rules:

| Field                              | Rule                                                                                                  |
| ---------------------------------- | ----------------------------------------------------------------------------------------------------- |
| `quote_for` / `virtual_account_id` | **Exactly one** must be present (sending both, or neither, returns `400`).                            |
| `quote_for.rail`                   | One of `ACH_STANDARD`, `ACH_SAME_DAY`, `WIRE_DOMESTIC`, `TRON`, `SOLANA`, `POLYGON`.                  |
| `quote_for.currency`               | `USD`, `USDT`, or `USDC`.                                                                             |
| `amount`                           | Two-decimal positive string, e.g. `"50000.00"`.                                                       |
| `target`                           | Required for a stablecoin delivery; `target.network` is required when `target.currency` is not `USD`. |

<Note>
  `quote_for` previews do **not** accept `rail` (top-level), `payment_instructions`, or `from_held_balance` — those are VA-bound only. Sending any of them with `quote_for` returns `400`.
</Note>

## B. VA-bound quotation — persisted and redeemable

Identical to a preview but keyed by `virtual_account_id`. The quote is **persisted**, returns a `quote_id`, and is **redeemable for 15 minutes** in a subsequent payout.

```bash theme={null}
curl https://api.balampay.com/sandbox/v1/quotations \
  -H "X-Api-Version: 2026-06-01" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "x-api-key: $KIRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "virtual_account_id": "8f3c2b10-0000-0000-0000-000000000000",
    "amount": "50000.00",
    "rail": "WIRE_DOMESTIC",
    "target": { "currency": "USDC", "network": "solana" }
  }'
```

VA-bound optional fields:

| Field                  | Purpose                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `rail`                 | Bank-rail hint (`ACH_STANDARD`, `ACH_SAME_DAY`, `WIRE_DOMESTIC`). The VA does not pin a single rail, so declare the intended one to price its fixed fee. Omit it and no rail line is priced (settlement charges the actual rail of the actual movement).                                                                                                                                                                                                                                                                                                                                                           |
| `payment_instructions` | Crypto-funded payouts: `{ "currency": "USDC" \| "USDT", "network": "solana" \| "polygon" \| "tron" }` — the token/network you will deposit into the per-payout wallet. Because that deposit is a **fresh receipt**, the quote prices the `inbound` receipt fee **and** the conversion leg (`conversion_off_ramp` for a stablecoin→fiat payout) — see [Crypto-funded off-ramp](#crypto-funded-off-ramp-both-inbound-and-conversion). **Mutually exclusive with `from_held_balance`** (an on-book balance mints no deposit wallet, and already paid `inbound` at its original receipt) — sending both returns `400`. |
| `from_held_balance`    | `true` when `amount` is an on-book balance you already hold; suppresses the inbound legs and prices only the conversion + outbound legs. Requires `virtual_account_id`, and is **incompatible with `payment_instructions`**. A payout that draws from the held balance **must** redeem a quote issued with this flag (see [Redeem the quote in a payout](#c-redeem-the-quote-in-a-payout)).                                                                                                                                                                                                                        |
| `inverse`              | `true` to back-solve: you give the amount the recipient should receive, and the quote returns the `source.amount` required. The recipient receives **at least** the requested amount — when a percentage fee/markup is in play, rounding may deliver up to one minor unit more (never less).                                                                                                                                                                                                                                                                                                                       |
| `client_markup`        | A **per-transaction markup override** that replaces your account-configured markup for this quote — `{ "fixed_minor": <minor units of the currency you fund with>, "percentage_bps": <bps> }`. See [Per-transaction markup override](#per-transaction-markup-override) below.                                                                                                                                                                                                                                                                                                                                      |

<Warning>
  **The quote must match the payout you will create.** Redemption re-validates the quote against the actual payout, so the quote and the payout must agree on:

  * the **delivery currency/network** (`target` must match the recipient — a USDC-wallet recipient needs a `target` of `{ "currency": "USDC", "network": … }`, not a fiat/USD target), and
  * the **funding mode** (`from_held_balance: true` for a balance payout).

  A mismatch returns `400` at payout time even though the quote itself succeeded.
</Warning>

### Picking `rail` for your recipient

`rail` and the recipient's `account_type` are **two different vocabularies for the same rail** — the quote names the settlement rail, the recipient names the account kind. Map from the recipient you are about to pay:

| Recipient `account_type` | Quotation `rail`                                                               |
| ------------------------ | ------------------------------------------------------------------------------ |
| `ACH`                    | `ACH_STANDARD`, or `ACH_SAME_DAY` for same-day settlement                      |
| `WIRE`                   | `WIRE_DOMESTIC`                                                                |
| `WALLET`                 | **omit `rail`** — set `target` to the wallet's `{ currency, network }` instead |

<Note>
  `ACH` maps to two rails, and the recipient does not tell you which — the same ACH recipient can be paid `ACH_STANDARD` or `ACH_SAME_DAY`, and the fixed fee differs. Choose the one you intend to settle on; the quote prices that choice.

  A crypto (`WALLET`) recipient has no bank rail — price it with `target` alone. On a `quote_for` preview, a rail/currency mismatch is rejected with `400`; on a VA-bound quote it is **not** validated, so a stray `rail` is accepted and silently prices a bank-rail fee onto a crypto delivery. Omit it.
</Note>

### Per-transaction markup override

By default your markup is **configured per account** and surfaces as `charged_by: "client"` lines (e.g. `inbound_markup`). To charge a **one-off markup for a single quote** instead, send `client_markup` — it **replaces** your account-configured markup for that quote only.

```bash theme={null}
curl https://api.balampay.com/sandbox/v1/quotations \
  -H "X-Api-Version: 2026-06-01" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "x-api-key: $KIRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "virtual_account_id": "8f3c2b10-0000-0000-0000-000000000000",
    "amount": "50000.00",
    "rail": "WIRE_DOMESTIC",
    "target": { "currency": "USDC", "network": "solana" },
    "client_markup": { "fixed_minor": 500, "percentage_bps": 50 }
  }'
```

| Field            | Rule                                                                                                                                                                                                                                                                                                |
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `fixed_minor`    | Flat markup in the minor units of the **currency you fund with**, integer ≥ 0. Funding in USD (precision 2): `500` = `$5.00`. Funding in USDT/USDC (precision 6): `10000000` = `$10.00`. Kira re-denominates it onto the quote's USD notional, so the emitted `client_markup_fixed` line reads USD. |
| `percentage_bps` | Percentage markup in **basis points** on the gross source, integer `0`–`10000` (`50` = `0.50%`; the `10000` cap is 100%).                                                                                                                                                                           |

<Warning>
  **`fixed_minor` scales with the currency you fund with, not with USD.** This is the one field where you hand us a raw minor-units integer, so getting the scale wrong charges the wrong amount by 10,000× — silently, with a `201`.

  For a **\$10.00** flat markup:

  | You fund with | Send       | Because                     |
  | ------------- | ---------- | --------------------------- |
  | USD           | `1000`     | 2 decimals → `$10.00 × 100` |
  | USDC / USDT   | `10000000` | 6 decimals → `$10.00 × 10⁶` |

  Sending `1000` on a **USDC**-funded quote asks for `$0.001`, not `$10.00`. Read the funding currency off the virtual account (or your `payment_instructions.currency`) and scale by **its** precision — never by the `source.precision` in the response, which is always `2` because the quote is priced on the USD face value.
</Warning>

Rules and behavior:

* **At least one leg must be greater than 0.** An all-zero or empty override, a `percentage_bps` above `10000`, or any unknown key is rejected with `400 validation_error` (the body is `.strict`).
* **Engine-native units** — send integers, *not* decimals: the flat leg in the minor units of the currency you fund with (×100 for USD, ×10⁶ for USDT/USDC) and the percentage leg in bps. Kira applies that precision shift onto the quote's USD notional for you and performs no other conversion, so a value scaled at the wrong precision still charges the wrong amount.
* It emits up to two `charged_by: "client"` lines — `client_markup_fixed` (the flat leg) and `client_markup_percentage` (bps on the gross source) — which roll into `totals.client_markup_total`.
* It **replaces** the account-configured markup for this quote: you are **not** also charged the configured `*_markup` lines.
* It is **not** suppressed by `transactional_waived` — an explicit per-transaction instruction always applies.
* On an `inverse` quote it **is** counted by the back-solve: `source.amount` grosses up to cover it.

<Warning>
  **The payout endpoint also accepts a `client_markup`, and it is a different shape — do not reuse this one.** The quotation takes engine-native integers (`fixed_minor`, `percentage_bps`); the payout takes decimal **strings** (`fixed_fee`, `percentage_fee`, and an optional `fx_markup`, with `percentage_fee` expressed as a fraction — `"0.01"` for 1%). Same field name, incompatible bodies. Because the quotation body is `.strict`, sending the payout shape here fails with `400 validation_error` naming the unknown keys.

  **If you are redeeming a quote you do not need it twice** — the fees are locked from the quote, so send `client_markup` on the quotation and omit it on the payout. Only a payout created *without* a `quote_id` needs the payout-shaped override.
</Warning>

Response fragment for the request above (`fixed_minor: 500` + `percentage_bps: 50` on a `$50,000.00` source → `client_markup_total: 25500`):

```json theme={null}
"fees": [
  {
    "code": "client_markup_fixed", "kind": "fixed", "charged_by": "client",
    "status": "active", "version": "v1", "deprecated_at": null,
    "amount": 500, "currency": "USD", "precision": 2
  },
  {
    "code": "client_markup_percentage", "kind": "percentage", "charged_by": "client",
    "status": "active", "version": "v1", "deprecated_at": null,
    "amount": 25000, "currency": "USD", "precision": 2,
    "calculation_method": "percentage", "rate_bps": 50,
    "basis_amount": 5000000, "basis_currency": "USD"
  }
]
```

## Response shape

The envelope is identical for previews and VA-bound quotes — only `quote_id` differs (`null` for previews).

```json theme={null}
{
  "quote_id": "a1b2c3d4-0000-0000-0000-000000000000",
  "quote_expires_at": "2026-06-10T18:15:00.000Z",
  "source":    { "amount": 5000000, "currency": "USD",  "precision": 2 },
  "recipient": { "amount": 49920000000, "currency": "USDC", "precision": 6 },
  "pricing_context": {
    "tier": "tier_1",
    "starting_tier": "tier_1",
    "intro_period_active": false,
    "transactional_waived": false,
    "month_cumulative": 0,
    "month_cumulative_currency": "USD",
    "from_held_balance": false
  },
  "conversion": {
    "pair": "USD/USDC",
    "rate": "1.000000",
    "market_rate": "0.999700",
    "clamp": "pinned_to_peg",
    "rate_source": "kraken",
    "source_amount": 4992000,
    "source_currency": "USD",
    "target_amount": 49920000000,
    "target_currency": "USDC",
    "locked_at": "2026-06-10T18:00:00.000Z",
    "ttl_seconds": 900
  },
  "fees": [
    {
      "code": "inbound", "kind": "percentage", "charged_by": "kira",
      "status": "active", "version": "v1", "deprecated_at": null,
      "amount": 4000, "currency": "USD", "precision": 2,
      "calculation_method": "percentage", "rate_bps": 8,
      "basis_amount": 5000000, "basis_currency": "USD"
    },
    {
      "code": "wire_domestic_inbound", "kind": "fixed", "charged_by": "kira",
      "status": "active", "version": "v1", "deprecated_at": null,
      "amount": 1500, "currency": "USD", "precision": 2
    },
    {
      "code": "inbound_markup", "kind": "percentage", "charged_by": "client",
      "status": "active", "version": "v1", "deprecated_at": null,
      "amount": 2500, "currency": "USD", "precision": 2,
      "calculation_method": "percentage", "rate_bps": 5,
      "basis_amount": 5000000, "basis_currency": "USD"
    }
  ],
  "totals": {
    "kira_revenue_total": 5500,
    "client_markup_total": 2500,
    "fee_total": 8000,
    "source_net_amount": 4992000,
    "currency": "USD",
    "precision": 2
  }
}
```

### Reading the response

* **Amounts are integer minor units paired with `precision`** — not decimal strings. `5000000` with `precision: 2` is `$50,000.00`; `49920000000` with `precision: 6` is `49,920.00 USDC`. Format with `amount / 10^precision`; do not parse as a float.
* **`fees[]` is itemized.** Each line has a `code`, a `kind` (`fixed` | `percentage`), `charged_by` (`kira` = Kira revenue; `client` = your markup), a `status` (`active` | `waived` | `deprecating`), a `version`, and `deprecated_at` (`null` unless the code is being phased out). Percentage lines additionally carry `calculation_method`, `rate_bps`, `basis_amount`, and `basis_currency`. Per-transaction markup overrides surface here as `client_markup_fixed` / `client_markup_percentage`.
* **`totals` are disjoint.** `kira_revenue_total` and `client_markup_total` never overlap; `fee_total = kira_revenue_total + client_markup_total`, and `source_net_amount = source.amount − fee_total`.
* **`pricing_context.tier`** is `tier_1` | `tier_2` | `tier_3`. **`month_cumulative`** is your running month-to-date inbound volume (in `month_cumulative_currency` minor units) that drives the tier; treat it as informational — it is `0` until the per-client TPV snapshot is enabled for your account.
* **`conversion`** exposes both the applied `rate` and the underlying `market_rate`, plus the `clamp` and `rate_source`. Fees are always computed on the face-value 1:1 notional; the rate only affects `recipient.amount`.

<Note>
  **`balance_sufficient`** (top-level, **held-balance quotes only**) is an advisory boolean: `true` when your on-book balance for the source currency covered `source.amount` at quote time. It is **absent** on non-held quotes (and if the snapshot read fails). It is **not a reservation** — the authoritative check is the row-locked read at redemption — but it lets you surface early "insufficient funds" feedback.
</Note>

<Tip>
  A waived percentage fee appears with `status: "waived"` and `amount: 0`, but its `rate_bps` is preserved so you can see the rate you would otherwise pay. Do not add waived lines to your own totals — they are already `0` and excluded from `fee_total`.
</Tip>

### Returned fee codes

Every `fees[]` line carries a `code`. The codes Kira may return — `kira` lines are Kira revenue, `client` lines are your markup, and the two never overlap:

| `code`                                             | `kind`             | `charged_by` | Emitted when                                                                                                                 |
| -------------------------------------------------- | ------------------ | ------------ | ---------------------------------------------------------------------------------------------------------------------------- |
| `inbound`                                          | percentage         | `kira`       | On every **fresh receipt** — a deposit, or a crypto-funded payout's funding deposit. Suppressed only by `from_held_balance`. |
| `conversion_on_ramp`                               | percentage         | `kira`       | On a **fiat→stablecoin** conversion leg.                                                                                     |
| `conversion_off_ramp`                              | percentage         | `kira`       | On a **stablecoin→fiat** conversion leg.                                                                                     |
| `intro_tier_iii_adjustment`                        | percentage         | `kira`       | Intro-period Tier III pricing adjustment, when active.                                                                       |
| `<rail>_<direction>`                               | fixed              | `kira`       | Bank-rail pass-through — e.g. `wire_domestic_outbound`, `ach_standard_inbound`, `ach_same_day_outbound`.                     |
| `<network>_<direction>`                            | fixed              | `kira`       | Blockchain network pass-through — e.g. `solana_inbound`, `tron_inbound`, `polygon_outbound`.                                 |
| `inbound_markup`                                   | percentage         | `client`     | Your account-configured percentage markup on the inbound leg.                                                                |
| `conversion_markup`                                | percentage         | `client`     | Your account-configured percentage markup on the conversion leg.                                                             |
| `<code>_markup`                                    | fixed              | `client`     | Your account-configured per-rail/network fixed markup — e.g. `wire_domestic_outbound_markup`.                                |
| `client_markup_fixed` / `client_markup_percentage` | fixed / percentage | `client`     | Per-transaction markup override (see [Per-transaction markup override](#per-transaction-markup-override)).                   |

### Crypto-funded off-ramp: both inbound and conversion

A crypto-funded payout — `payment_instructions` set, `from_held_balance` **absent** — quotes an **outbound** delivery whose funding is a **fresh crypto deposit**. Because that deposit is a new receipt of external money, the quote prices the `inbound` receipt fee **and** the `conversion_off_ramp` spread on the stablecoin→fiat leg. Only `from_held_balance: true` suppresses the `inbound` leg — an on-book balance already paid it at its original receipt.

Request — deposit USDC on Solana, deliver USD by domestic wire:

```json theme={null}
{
  "virtual_account_id": "8f3c2b10-0000-0000-0000-000000000000",
  "amount": "300000.00",
  "rail": "WIRE_DOMESTIC",
  "target": { "currency": "USD" },
  "payment_instructions": { "currency": "USDC", "network": "solana" }
}
```

The `fees[]` array carries **both** Kira percentage legs (amounts illustrative, at Tier I rates).
A USD-pegged stablecoin source is priced on its **USD face value** (1:1), so `source`, `fees[]` and
`totals` are all denominated in USD — the `$300,000.00` deposit is `source.amount: 30000000` at
`precision: 2`, and `conversion.pair` (`"USDC/USD"`) is what names the stablecoin you deposit:

```json theme={null}
"pricing_context": { "tier": "tier_1", "from_held_balance": false, "…": "…" },
"fees": [
  {
    "code": "inbound", "kind": "percentage", "charged_by": "kira",
    "status": "active", "version": "v1", "deprecated_at": null,
    "amount": 24000, "currency": "USD", "precision": 2,
    "calculation_method": "percentage", "rate_bps": 8,
    "basis_amount": 30000000, "basis_currency": "USD"
  },
  {
    "code": "conversion_off_ramp", "kind": "percentage", "charged_by": "kira",
    "status": "active", "version": "v1", "deprecated_at": null,
    "amount": 18000, "currency": "USD", "precision": 2,
    "calculation_method": "percentage", "rate_bps": 6,
    "basis_amount": 30000000, "basis_currency": "USD"
  },
  {
    "code": "wire_domestic_outbound", "kind": "fixed", "charged_by": "kira",
    "status": "active", "version": "v1", "deprecated_at": null,
    "amount": 1500, "currency": "USD", "precision": 2
  },
  {
    "code": "solana_inbound", "kind": "fixed", "charged_by": "kira",
    "status": "active", "version": "v1", "deprecated_at": null,
    "amount": 0, "currency": "USD", "precision": 2
  }
]
```

`inbound` is `$240.00` (8 bps) and `conversion_off_ramp` is `$180.00` (6 bps) — the percentage legs
bill on the USD notional at the same `rate_bps` they would on a USD-funded quote of the same size.

The funding deposit's network pass-through appears as a `<network>_<direction>` fixed line (here `solana_inbound`, `$0` at this tier) — a `$0` leg is still itemized, so include it in your reconciliation rather than assuming it is absent.

<Note>
  On the **on-ramp** direction — a fiat deposit converted to stablecoin — the conversion leg is `conversion_on_ramp` instead; the `inbound` leg is identical. Fixed rail/network lines carry their own `currency`, which can differ from the percentage legs' currency (here USDC legs alongside a USD wire line).
</Note>

## C. Redeem the quote in a payout

Pass the `quote_id` from a VA-bound quote to the payout. The fees are **locked from the quote** — settlement does not re-price.

```bash theme={null}
curl https://api.balampay.com/sandbox/v1/virtual-accounts/8f3c2b10-0000-0000-0000-000000000000/payout \
  -H "X-Api-Version: 2026-06-01" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "x-api-key: $KIRA_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": "50000.00",
    "mode": "CRYPTO",
    "recipient_id": "rec_0000",
    "quote_id": "a1b2c3d4-0000-0000-0000-000000000000",
    "payment_instructions": { "currency": "USDC", "network": "solana" }
  }'
```

Redemption is atomic and idempotent:

* A quote redeems **once**. Re-using a redeemed `quote_id` for a different payout returns `400`.
* An expired or cancelled quote returns `400`.
* The quote **must match the payout**: redeeming a quote whose `target` currency/network differs from the recipient returns `400` (`Recipient currency … does not match quote currency …`), and a payout drawn from the held balance requires a quote issued with `from_held_balance: true` (`… requires a quote issued with from_held_balance: true`). Re-quote with the correct flags if you hit either.
* If the payout fails before the transfer is created, the quote is released back to redeemable.
* `quote_id` is **optional** on a payout — omit it and settlement prices the fees fresh at execution time (without the 15-minute lock).

<Note>
  `amount` on the payout body is a **two-decimal string** (`"50000.00"`), not a number. The full payout request shape — including the recipient and mode — is documented under **Payouts** in the [API reference](/api-reference). OTP does not apply to API-key integrators — see [Known limitations → Payouts](/guides/known-limitations#payouts).
</Note>

## Migrating from `payout/preview` + `create_quote`

<Warning>
  **`payout/preview` is deprecated as of July 16, 2026.** New integrations should price payouts with `POST /v1/quotations` (this page), not `payout/preview` + `create_quote`. The `payout/preview` documentation below remains accurate for existing integrations still on it, but don't build a new integration against it.
</Warning>

The previous pricing model used `POST /v1/virtual-accounts/{id}/payout/preview` (optionally with `create_quote: true` to lock a quote). The itemized endpoint replaces it for new integrations. Both flows share the same lock window (`GENERAL_QUOTATION_LIFESPAN_MINUTES`, default 15 minutes) — what changes is the fee shape, not the TTL.

| Aspect                    | Previous (`payout/preview`, `2026-04-14`)       | Itemized (`POST /v1/quotations`, `2026-06-01`)                         |
| ------------------------- | ----------------------------------------------- | ---------------------------------------------------------------------- |
| Endpoint                  | `POST /v1/virtual-accounts/{id}/payout/preview` | `POST /v1/quotations`                                                  |
| Pre-VA pricing            | not available                                   | `quote_for` previews                                                   |
| Quote lock                | `create_quote: true` → 15 min                   | VA-bound quote → 15 min (same `lifespanMinutes`)                       |
| Amounts                   | decimal strings (`"500.00"`)                    | **integer minor units + `precision`**                                  |
| Fees                      | aggregated buckets                              | **itemized `fees[]`** with `code` / `kind` / `charged_by` / `rate_bps` |
| Tier / conversion context | not surfaced                                    | `pricing_context` + `conversion` blocks                                |
| Kira vs. your markup      | not separable                                   | `totals.kira_revenue_total` vs. `client_markup_total`                  |

<Steps>
  <Step title="Pin the version">
    Add `X-Api-Version: 2026-06-01` to your quotation calls.
  </Step>

  <Step title="Switch amount parsing">
    Read **integer minor units + `precision`** (stop parsing decimal strings as floats).
  </Step>

  <Step title="Iterate fees[]">
    Replace bucket reads with iterating `fees[]`; filter on `charged_by` to separate Kira's fees from your markup.
  </Step>

  <Step title="Quote once, redeem once">
    Store the returned `quote_id` and pass it to the payout — instead of re-pricing at each step.
  </Step>

  <Step title="Handle expiry">
    A quote is valid for 15 minutes (`quote_expires_at`); re-quote if it lapses.
  </Step>

  <Step title="Enable the engine">
    Ask your account manager to enable the new pricing engine for your account when you are ready to see itemized fees from it.
  </Step>
</Steps>

## Errors

Errors carry a machine-readable `code` plus a human `message`; switch on `code`.

| Status | `code`               | When                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| ------ | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | `validation_error`   | Request failed validation: both or neither of `quote_for` / `virtual_account_id`; `amount` not a two-decimal string; missing `target.network` for a stablecoin delivery; `from_held_balance` + `payment_instructions` together; `from_held_balance` / `rail` / `payment_instructions` sent with `quote_for`; rail/currency mismatch; `client_markup` with no positive leg, `percentage_bps` above `10000`, or an unknown key. The failing field(s) are in the `details` array (Zod issues); `message` is the constant `"Invalid request body"`. |
| `400`  | `fees_exceed_amount` | Total fees ≥ the source amount. `details` carries `{ amount, total_fees }` (minor units). Increase the amount or lower the markup.                                                                                                                                                                                                                                                                                                                                                                                                              |
| `400`  | *(redeem)*           | At payout time: the `quote_id` is expired, cancelled, or already redeemed; or the quote doesn't match the payout — a balance payout needs a quote issued with `from_held_balance: true` (`…requires a quote issued with from_held_balance: true`), and the quote's `target` must match the recipient (`Recipient currency (USDC) does not match quote currency (USD)`).                                                                                                                                                                         |
| `401`  | `unauthorized`       | Missing or invalid API key.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `404`  | `not_found`          | `virtual_account_id` not found for your account.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |

## Next steps

<CardGroup cols={2}>
  <Card title="API reference" icon="code" href="/api-reference">
    The full request/response schema for `POST /v1/quotations` and the payout endpoints.
  </Card>

  <Card title="Versioning" icon="code-branch" href="/guides/versioning">
    How to pin `X-Api-Version` and upgrade your account default.
  </Card>

  <Card title="Authentication" icon="key" href="/guides/authentication">
    The token lifecycle and the `x-api-key` + bearer credentials every call needs.
  </Card>

  <Card title="Quickstart" icon="rocket" href="/quickstart">
    The end-to-end sandbox flow: user, virtual account, deposit, payout.
  </Card>
</CardGroup>
