Unirail

API reference

Every v1 endpoint, rendered from the public contract the API and SDK are built on.

The Unirail API is REST over JSON at https://api.unirail.dev, with every path under /v1. The endpoints below are rendered from @unirail/contract, the same contract the API serves and the SDK is typed by, and the machine-readable spec is at https://api.unirail.dev/v1/openapi.json.

Conventions

TopicRule
AuthenticationAuthorization: Bearer ur_test_sk_…. The key's environment decides where the call runs.
VersioningUnirail-Version: 2026-10-08. The SDK sends it for you.
IdempotencyIdempotency-Key on every write (idempotency).
Amounts{ value, asset }: integer minor units as a string, and iso4217:GBP or a CAIP-19 asset id.
TimestampsISO 8601 strings with an offset.
IdsPrefixed: cus_, acct_, lnk_, ins_, qt_, pi_, leg_, evt_, con_.
Listslimit (1–100, default 25) and after (the last id you saw). Responses are { data, hasMore, next }.
ErrorsOne envelope with a stable data.code (errors).

Parameters marked * are required. With the SDK, call the method shown on the right of each endpoint, for example unirail.routeQuotes.create({ … }).

Rails

GET/v1/railsrails.list()

The provider catalogue: integrated and listed rails.

ParameterInTypeNotes
countryquerystringISO 3166-1 alpha-2
statusquery"listed" | "in-development" | "integrated" | "certified"Only listings with this status.

Returns { data }.

Addresses

POST/v1/addresses/parseaddresses.parse()

Validate and mask a payto:// address without storing it.

ParameterInTypeNotes
payto*bodystringA payto:// URI, for example payto://scan/040004/12345678?receiver-name=Ada%20Lovelace. (max 2048 chars)

Returns { scheme, known, masked, country, receiverName }.

Institutions

GET/v1/institutionsinstitutions.list()

Banks reachable through this environment's connections.

ParameterInTypeNotes
country*querystringISO 3166-1 alpha-2
qquerystringSearch by bank name. (max 80 chars)
limitqueryintegerPage size. (default 25, 1–100)

Returns { data }.

Customers

POST/v1/customerscustomers.create()

Create or return the customer for your user id.

ParameterInTypeNotes
externalId*bodystringYour user id. Unique per environment; creating twice returns the same customer. (max 128 chars)
namebodystring (max 140 chars)
emailbodystring (email)
countrybodystringISO 3166-1 alpha-2
metadatabodyRecord<string, string>Up to 20 keys you can use to find objects again.

Returns a customer object with HTTP 201.

GET/v1/customers/{id}customers.retrieve()
ParameterInTypeNotes
id*pathstringcus_… identifier

Returns a customer object.

GET/v1/customerscustomers.list()
ParameterInTypeNotes
externalIdquerystringYour user id. Unique per environment; creating twice returns the same customer.
afterquerystringCursor: the id of the last object you received. Returns the objects after it.
limitqueryintegerPage size. (default 25, 1–100)

Returns a page of customer objects: { data, hasMore, next }.

POST/v1/link_sessionslinkSessions.create()

Start linking a customer's bank account.

ParameterInTypeNotes
customer*bodystringThe customer (cus_…).
country*bodystringISO 3166-1 alpha-2
institutionbodystringAn institution from GET /v1/institutions.
railbodystringForce a rail. Otherwise the environment’s policy picks one for the institution.
channelbody"web" | "native"Where your user is: web or native. (default "web")
returnUri*bodystring (uri)Where the user lands after the provider step; your app's URL or universal link
psubody{ ip, userAgent, deviceId, psuType }Context from your user’s device (IP, user agent, device id, personal or business). Some banks require it.

Returns a link_session object with HTTP 201.

POST/v1/link_sessions/{id}/advancelinkSessions.advance()

Continue after the user returns from the provider.

ParameterInTypeNotes
id*pathstringlnk_… identifier
returnParamsbodyRecord<string, string>The query parameters your return URI received, verbatim.
sdkResultbodyRecord<string, string>For sdk actions, what the provider SDK handed back (for example Plaid’s public token).

Returns a link_session object.

GET/v1/link_sessions/{id}linkSessions.retrieve()
ParameterInTypeNotes
id*pathstringlnk_… identifier

Returns a link_session object.

Accounts

POST/v1/accountsaccounts.create()

Register account details a payee gave you (not linked through a provider).

ParameterInTypeNotes
customerbodystringThe customer (cus_…).
holderName*bodystringThe account holder’s name, as the payee gave it to you. (max 140 chars)
payto*bodystringA payto:// URI, for example payto://scan/040004/12345678?receiver-name=Ada%20Lovelace. (max 2048 chars)
metadatabodyRecord<string, string>Up to 20 keys you can use to find objects again.

Returns an account object with HTTP 201.

GET/v1/accounts/{id}accounts.retrieve()
ParameterInTypeNotes
id*pathstringacct_… identifier

Returns an account object.

GET/v1/accountsaccounts.list()
ParameterInTypeNotes
customerquerystringThe customer (cus_…).
afterquerystringCursor: the id of the last object you received. Returns the objects after it.
limitqueryintegerPage size. (default 25, 1–100)

Returns a page of account objects: { data, hasMore, next }.

Route quotes

POST/v1/route_quotesrouteQuotes.create()

Find and price the routes between two accounts.

ParameterInTypeNotes
from*bodystringacct_… identifier
to*bodystringacct_… identifier
amount*body{ value, asset }Integer minor units as a decimal string, plus an asset such as iso4217:GBP.
preferencebody"cheapest" | "fastest" | "recommended"Which label to rank first. Defaults to the environment’s routing policy.
preferRailbodystringRank routes through this rail first when several are feasible.
channelbody"web" | "native"Where your user is: web or native. (default "web")

Returns { quotes, infeasible, suggestion }.

Payment intents

POST/v1/payment_intentspaymentIntents.create()

Pay along an accepted quote.

ParameterInTypeNotes
quote*bodystringThe quote (qt_…) your user approved.
digest*bodystringThe digest of the quote your user approved.
returnUri*bodystring (uri)Where the user lands after the provider step; your app's URL or universal link
referencebodystringPayment reference, up to 140 characters. (max 140 chars)
psubody{ ip, userAgent, deviceId, psuType }Context from your user’s device (IP, user agent, device id, personal or business). Some banks require it.
metadatabodyRecord<string, string>Up to 20 keys you can use to find objects again.

Returns a payment_intent object with HTTP 201.

GET/v1/payment_intents/{id}paymentIntents.retrieve()
ParameterInTypeNotes
id*pathstringpi_… identifier

Returns a payment_intent object.

POST/v1/payment_intents/{id}/advancepaymentIntents.advance()

Continue after the user returns from approving.

ParameterInTypeNotes
id*pathstringpi_… identifier
returnParamsbodyRecord<string, string>The query parameters your return URI received, verbatim.
sdkResultbodyRecord<string, string>For sdk actions, what the provider SDK handed back (for example Plaid’s public token).

Returns a payment_intent object.

POST/v1/payment_intents/{id}/cancelpaymentIntents.cancel()
ParameterInTypeNotes
id*pathstringpi_… identifier

Returns a payment_intent object.

GET/v1/payment_intentspaymentIntents.list()
ParameterInTypeNotes
customerquerystringThe customer (cus_…).
afterquerystringCursor: the id of the last object you received. Returns the objects after it.
limitqueryintegerPage size. (default 25, 1–100)

Returns a page of payment_intent objects: { data, hasMore, next }.

Route decisions

Decide mode: a routing decision from masked metadata, for when your backend calls the provider itself. See Decide mode.

POST/v1/route_decisionsrouteDecisions.create()

Decide the rail and cost for an operation from masked metadata (no personal data).

ParameterInTypeNotes
operation*body"banking.ais.account-details" | "banking.ais.balances" | "banking.ais.transactions" | "banking.ais.insights" | "banking.pis.single" | "banking.pis.scheduled" | "banking.pis.vrp-sweeping" | "banking.pis.vrp-commercial" | "banking.pis.bulk" | "banking.payouts" | "banking.pay-ins.virtual-accounts" | "banking.pay-ins.debit" | "verification.cop" | "verification.vop" | "verification.ownership" | "verification.micro-deposits" | "identity.kyc.document" | "identity.kyc.biometric" | "identity.kyc.database" | "identity.kyc.eid" | "identity.kyb" | "identity.prefill" | "compliance.screening.sanctions" | "compliance.screening.pep" | "compliance.screening.adverse-media" | "compliance.screening.payments" | "compliance.transaction-monitoring" | "compliance.travel-rule" | "risk.payment-return" | "risk.fraud-signals" | "money.fx" | "money.cross-border" | "money.stablecoin-ramp"A capability id, for example banking.pis.single.
amountbody{ value, asset } | { asset, band }Minor units, or { asset, band: { min, max } } to keep the exact figure to yourself. Required for payments.
payer*body{ country, institution, scheme, accountRef, linkedRails }Metadata only: country, and optionally institution, scheme (a payto scheme name), accountRef (from maskAccount) and linkedRails. Any other field is refused.
payeebody{ country, institution, scheme, accountRef, linkedRails }Like payer. Required for payments.
preferencebody"cheapest" | "fastest" | "recommended"Which label to rank first. Defaults to the environment’s routing policy.
channelbody"web" | "native"Where your user is: web or native. (default "web")
constraintsbody{ excludeRails, custodyAdmitted }excludeRails, and custodyAdmitted to narrow what your policy admits.

Returns a route_decision object.

Outcomes

POST/v1/outcomesoutcomes.create()

Report how a decided operation went; feeds reliability and contract usage.

ParameterInTypeNotes
decision*bodystringThe decision (dec_…) this outcome is for.
rail*bodystringThe rail you actually used, which may be one of the alternatives.
status*body"succeeded" | "failed" | "cancelled"succeeded, failed or cancelled. Reporting the same status twice returns the first record.
latencyMs*bodyintegerFrom sending the request to the provider until this status. (0–2592000000)
providerFeebody{ amount, currency }What the provider charged, in decimal major units, if you know it.
failureCodebodystringA short code such as rejected. Never the provider’s message.
contextbody{ operation, country, institution }Only for decisions your own @unirail/router made, which Unirail never saw: the operation, country and optional institution.

Returns an outcome object with HTTP 201.

Routing snapshot

GET/v1/routing_snapshotroutingSnapshot.get()

Everything the decision engine reads, to run @unirail/router yourself (no secrets).

Returns a routing_snapshot object.

Events

GET/v1/eventsevents.list()

Events in order; walk with `after` to replay anything a webhook missed.

ParameterInTypeNotes
afterquerystringCursor: the id of the last object you received. Returns the objects after it.
typequery"link_session.completed" | "link_session.failed" | "account.updated" | "account.reauth_required" | "payment_intent.requires_action" | "payment_intent.processing" | "payment_intent.succeeded" | "payment_intent.failed" | "payment_intent.cancelled" | "leg.updated" | "connection.degraded"Only events of this type.
limitqueryintegerPage size. (default 25, 1–100)

Returns a page of event objects: { data, hasMore, next }.

GET/v1/events/{id}events.retrieve()
ParameterInTypeNotes
id*pathstringevt_… identifier

Returns an event object.

On this page