Skip to main content
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.
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.

Engage the itemized shape

Every request carries the standard credentials plus the version header that selects the itemized envelope:
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).
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.

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.
Request rules:
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.

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.
VA-bound optional fields:
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.

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:
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.

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.
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: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.
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.
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.
Response fragment for the request above (fixed_minor: 500 + percentage_bps: 50 on a $50,000.00 source → client_markup_total: 25500):

Response shape

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

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.
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.
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.

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:

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:
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:
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.
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).

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.
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).
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. OTP does not apply to API-key integrators — see Known limitations → Payouts.

Migrating from payout/preview + create_quote

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.
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.
1

Pin the version

Add X-Api-Version: 2026-06-01 to your quotation calls.
2

Switch amount parsing

Read integer minor units + precision (stop parsing decimal strings as floats).
3

Iterate fees[]

Replace bucket reads with iterating fees[]; filter on charged_by to separate Kira’s fees from your markup.
4

Quote once, redeem once

Store the returned quote_id and pass it to the payout — instead of re-pricing at each step.
5

Handle expiry

A quote is valid for 15 minutes (quote_expires_at); re-quote if it lapses.
6

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.

Errors

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

Next steps

API reference

The full request/response schema for POST /v1/quotations and the payout endpoints.

Versioning

How to pin X-Api-Version and upgrade your account default.

Authentication

The token lifecycle and the x-api-key + bearer credentials every call needs.

Quickstart

The end-to-end sandbox flow: user, virtual account, deposit, payout.