Choose the provider for every payment by cost, speed and success rate
For each payment, bank link or identity check, Unirail tells your backend which of your providers to use, what it will cost under your contracts and why. It routes to Plaid, Yapily and Enable Banking today. Your backend can make the call itself with your own keys. Unirail never holds or moves money.
How one payment is routed in Decide mode
In Decide mode, the default for regulated platforms, Unirail makes the decision and your backend makes every provider call. Scroll to follow a £50.00 payment between two UK accounts from the first request to the outcome your backend reports back.
-
Your backend requests a route decision
Before it moves money, links a bank or checks an identity, your backend sends Unirail a route decision request. This one is a £50.00 payment from a UK sort code and account number to a UK IBAN, asking for the recommended rail.
POST /v1/route_decisionsExample
- operation
- banking.pis.single
- amount
- "5000" iso4217:GBP
- payer
- GB · scan · fp_9xQ2…
- payee
- GB · iban
- preference
- recommended
-
What the request contains
Names, account numbers and contact details stay on your platform. Each account goes as a fingerprint you compute with your own HMAC key. Unirail can recognise the same account next time but can't reverse the fingerprint. It rejects any request with a field it doesn't expect, such as a name.
Sent to Unirail
- Operation
- Countries and currency
- Amount or amount band
- Bank ids
- Payto scheme names
- Account fingerprints
Stays with you
- Names
- Account numbers
- Emails and addresses
- Provider credentials
- Your fingerprint key
-
What the decision contains
Unirail answers with the provider and rail to use, what it will cost under your own contracts, the ETA and how likely it is to succeed. You also get the ranked alternatives, the options it ruled out with a reason for each, the policy version and a digest you can bind your user's approval to.
Decision · routedExample values
- Use
- Provider A · Pay by bank
- Cost
- £0.15 under your contract
- ETA
- 9 s at p50
- Success
- 99% likely
- Alternative
- Provider B · £0.22 · 7 s · 98%
- Ruled out
- Provider C · no coverage for this payment in GB
- Policy
- v12 · digest 3f9c…e21a
-
Your backend calls the provider
@unirail/executorruns Unirail's provider adapters inside your own process. Your backend uses it to call Provider A with credentials from your own vault. In Decide mode those credentials never leave your platform.With Execute via Unirail, Unirail makes this call for you using the credential from your vault.
Who calls whom
- Your vault → your backend
- provider credentials
- Your backend → Provider A
- the payment call
- Unirail
- not on this call
-
The payment moves between the banks
Provider A starts the payment under your contract. The £50.00 goes from the payer's bank straight to the payee's bank without passing through Unirail.
£50.00 · payer's bank to payee's bank
- Initiated by
- Provider A, under your contract
- Route
- account to account
- Through Unirail
- no funds
-
Your backend reports the outcome
Your backend sends the rail it used, the status, the latency and the provider's fee to
POST /v1/outcomes, with no names or account numbers. Unirail uses outcomes to track reliability for each rail, operation and bank. It also counts them against your contract for the billing period. The next decision is then priced at your current volume tier.outcomes.createExample
await unirail.outcomes.create({ decision: decision.id, rail: decision.rail, status: "succeeded", latencyMs: 1840, });- Updates
- reliability per rail, operation and bank
- Counts toward
- contract usage this billing period
What you get with Unirail
You keep your contracts with PISPs, aggregators and KYC vendors. Unirail adds these four things on top.
-
One API and SDK for all your providers
You set up Plaid, Yapily and Enable Banking in the dashboard. Your code calls the same SDK whichever provider handles the payment.
Start with the quickstart -
Works with your Infisical vault
Provider credentials stay in your Infisical vault. Adding a provider doesn't add environment variables to your services.
Read the security model -
Deal tracking for your provider contracts
Unirail keeps versioned fees, volume tiers and renewal dates for each provider contract and prices every decision with them. Before you renegotiate, you can see what each provider costs you and what switching would save.
See deal tracking -
A routing decision for every operation
Each decision lists ranked alternatives and gives a reason for every option it ruled out.
How routing decisions work
Get a routing decision with one API call
Add the SDK to your backend, create a client with your test key and send a route decision request. Field names match the published v1 contract (RouteDecisionRequest, OutcomeRequest). Amounts are strings in minor units ("5000" is £50.00).
pnpm add @unirail/sdk Placeholder
The SDK isn't on npm yet. We give you access to the package when we set up your account.
- POST /v1/route_decisions
- POST /v1/outcomes
- GET /v1/routing_snapshot
import { createUnirail, maskAccount } from "@unirail/sdk";
const unirail = createUnirail({ apiKey: process.env.UNIRAIL_SECRET_KEY });
const decision = await unirail.routeDecisions.create({
operation: "banking.pis.single",
amount: { value: "5000", asset: "iso4217:GBP" },
payer: {
country: "GB",
scheme: "scan",
accountRef: await maskAccount("payto://scan/040004/12345678", process.env.UNIRAIL_FINGERPRINT_KEY),
},
payee: { country: "GB", scheme: "scan" },
preference: "recommended",
});
if (decision.status === "routed") {
// Your backend calls decision.rail with your own credentials (@unirail/executor),
// then reports what happened so the next decision learns from it.
const started = Date.now();
const result = await callProvider(decision); // your code
await unirail.outcomes.create({
decision: decision.id,
rail: decision.rail,
status: result.ok ? "succeeded" : "failed",
latencyMs: Date.now() - started,
});
}
How Unirail handles your data and credentials
In Decide mode Unirail sees masked metadata only. With Execute via Unirail it reads each credential from your vault when it needs it and never writes one to disk or a database.
-
Short-lived access to your vault
Unirail gets read-only access to one folder in your Infisical vault and keeps each credential in memory for up to five minutes. Revoking access stops new reads straight away.
-
API keys are stored as hashes
Unirail stores only a hash and the prefix of each test and live key.
-
Encrypted configuration
Your stored configuration is encrypted with a separate data key for each environment (AES-GCM envelope encryption).
-
Signed webhooks
Every webhook is signed under the Standard Webhooks scheme. The SDK checks the signature for you.
-
EU data residency
The control plane's database runs in Cloudflare's EU jurisdiction.
Read the security details Ask about self-hosting the decision engine
Start with a test key
Accounts are open by request while Unirail is in early access. We'll set yours up with a test key
(ur_test_sk_…) and access to the SDK so you can send decisions from your backend. We haven't published
pricing yet. Talk to us about it before you go live.