Route quotes
Priced routes between two accounts, labelled for your user to choose from, with a digest that binds their approval to exactly what they saw.
A route quote (qt_…) answers "how could this money move, what would it cost, and how long would it take?" for one payer account, one payee account and one amount.
const result = await unirail.routeQuotes.create({
from: "acct_payer…",
to: "acct_payee…",
amount: { value: "5000", asset: "iso4217:GBP" },
preference: "recommended", // optional; the environment's policy sets the default
});Amounts are integer minor units in a string ("5000" is £50.00) plus an asset: iso4217:GBP for fiat, or a CAIP-19 id for on-chain assets. No float ever carries money.
What comes back
{
quotes: RouteQuote[]; // up to three, labelled
infeasible: InfeasibleRoute[]; // every route that can't work, and why
suggestion?: "link-another-account"; // when nothing is feasible
}Unirail checks every connected provider that could serve the pair against schemes, amounts, currencies, institution restrictions and your routing policy, then returns up to three quotes labelled cheapest, fastest and recommended. Show them to your user and let them choose.
| Field | Meaning |
|---|---|
label | cheapest, fastest or recommended. |
legs[] | The hops the money takes: from, to, rail, amount, custodyAfter and the userActions each needs. |
totalCost | payer (what the payer pays in total) and a breakdown of fee lines, each with kind (provider, bank, platform, fx-spread, network) and paidBy (payer, payee or platform). |
eta | p50Seconds and p90Seconds. |
successLikelihood | Between 0 and 1. |
obligations | What you must do or show before this route can run, with the stage it applies to. |
restrictionsApplied | Restrictions that shaped this route. |
firmness | firm, or indicative when the price can still move. |
digest | A hash of payer, payee, amount, legs and cost. |
expiresAt | After this, the quote can't be paid. |
infeasible lists the routes that didn't make it, each with reasons and an explanation you can show. If nothing is feasible, Unirail suggests linking another account rather than blocking the user.
Binding your user's approval
The approval your user gives must cover exactly the route they saw. Bind it to the quote's id and digest, then pass both when you pay:
// 1. When the user picks a quote, derive the challenge they sign (a passkey, a PIN, a confirm step) from it.
const challenge = await sha256(`${quote.id}.${quote.digest}`);
// 2. After you've verified their approval, pay that quote.
const intent = await unirail.paymentIntents.create(
{ quote: quote.id, digest: quote.digest, returnUri },
{ context: { idempotencyKey: `payment:${payment.id}` } },
);If the quote has expired, Unirail refuses with quote_expired. If anything the digest covers no longer holds, it refuses with quote_changed. In both cases quote again and ask the user again: never pay a route your user didn't approve.
Custody
Every leg says who holds the money after it (custodyAfter). Today's routes run bank to bank with custody none: the payer's bank pays the payee's bank directly and no one holds funds in between. Meta-providers such as Airwallex route through their own collection accounts, so their legs carry provider-transit. See payment intents for every value.
Routing policy
Each environment's routing policy, edited in the dashboard, decides:
- which connections are eligible in which countries, and at which stage (
off,internal,beta,ga,paused); - which custody kinds are admitted, so an environment that admits only
nonesees transit routes as infeasible, with that reason; - the default preference when a request doesn't set one.
Quote ranking is never paid placement.