Quickstart

Five steps take you from an empty project to routing decisions that use your own outcomes. Each step works in test mode.

  1. Get early access

    Ask us for test access. Once we've set up your account, create an API key in the test environment. It starts with ur_test_sk_. Copy the key when it's shown, because Unirail stores only a hash of it and its prefix.

    Decide mode also needs a fingerprint key of your own, 32 characters or more. Because the key never leaves your backend, Unirail can't reverse the account fingerprints you make with it.

    Your server's environment
    export UNIRAIL_SECRET_KEY=ur_test_sk_…
    export UNIRAIL_FINGERPRINT_KEY=$(openssl rand -base64 32)
  2. Get the SDK

    The SDK is TypeScript and runs on your server. It's the client for every v1 endpoint, plus webhook verification and fingerprints.

    Placeholder The package isn't on npm yet. Access comes with your account. The command below will work once the package is published at launch.

    Until then, every endpoint works over HTTPS with your test key. The next step makes your first decision with curl.

    Install, once it's on npm
    pnpm add @unirail/sdk
  3. Ask for your first decision

    Send the operation, the amount, the countries and schemes and a fingerprint of the payer's account. The response names the rail, its estimated cost and ETA, its success likelihood, ranked alternatives and every rail it ruled out, each with the reason.

    Field names match the published v1 contract (RouteDecisionRequest, OutcomeRequest). Amounts are strings in minor units ("5000" is £50.00).

    With curl, today
    curl https://api.unirail.dev/v1/route_decisions \
      -H "Authorization: Bearer $UNIRAIL_SECRET_KEY" \
      -H "Content-Type: application/json" \
      -H "unirail-version: 2026-10-08" \
      -d '{
        "operation": "banking.pis.single",
        "amount": { "value": "5000", "asset": "iso4217:GBP" },
        "payer": { "country": "GB", "scheme": "scan" },
        "payee": { "country": "GB", "scheme": "scan" },
        "preference": "recommended"
      }'
    decide.ts, with the SDK
    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,
      });
    }
  4. Report the outcome

    Call the provider from your backend through @unirail/executor and tell Unirail how it went. A retry doesn't count twice, because outcomes are idempotent on decision and status. Outcomes update reliability per rail, operation and bank, along with the contract usage the next decision is priced on.

    outcome.ts
    // After your backend has called the provider:
    await unirail.outcomes.create({
      decision: decision.id,
      rail: decision.rail,
      status: "failed",
      latencyMs: 2300,
      failureCode: "rejected",
    });
  5. Go live

    Live keys start with ur_live_sk_ and belong to the live environment, with its own routing policy. Before you switch:

    • If Unirail will call providers for you, connect your Infisical vault. Unirail gets read-only access to one folder and keeps credentials in memory only, for up to five minutes. How credential federation works.
    • Record your provider contracts as deals. Decisions are then priced under your real terms. Provider deal tracking.
    • Get production access from each provider, under your own contract with it:
    • Plaid

      Sandbox: self-serve · Production: under your contract

      Production Payment Initiation needs Plaid's approval of the use case; VRP is enabled separately.

    • Yapily

      Sandbox: self-serve · Production: under your contract

      Under Yapily Connect, Yapily is the regulated party and the platform shows Yapily's disclosure. The catalogue lists a £15,000 cap per payment, not yet verified against a source.

    • Enable Banking

      Sandbox: self-serve · Production: under your contract

      Without a contract, production runs in restricted mode (own accounts only).

SDK packages and functions

Of the three packages, start with the SDK and add the executor when you move to Decide mode. The router is only for self-hosting, by arrangement.

@unirail/sdk

Not on npm yet

The API client, webhook verification and account fingerprints. Use it on your server only, because it takes a secret key.

@unirail/executor

Not on npm yet

The provider adapters Unirail uses, running inside your process with credentials from your own vault. Decide mode calls providers through it.

@unirail/router

Private: talk to us

The decision engine, for making decisions inside your own network from the routing snapshot. It's private and available by arrangement.

What @unirail/sdk exports

createUnirail({ apiKey })
A typed client for every v1 endpoint. Rejects a key that isn't ur_test_sk_… or ur_live_sk_….
webhooks.verify / sign / generateSecret
Check a delivery's Standard Webhooks signature against the raw body, or sign payloads in your tests.
maskAccount(payto, key)
Turn a payto:// address into an fp_ fingerprint with your HMAC key (32 characters or more).
canonicalPayto(payto)
Return an address in normalised form. The same account then always gives the same fingerprint.
paytoScheme(payto)
Return the scheme alone (scan or iban). Apart from the fingerprint, it's the only part of an address a Decide request carries.
keyMode(apiKey)
Return test or live, read from the key's prefix.

Webhooks

Webhook events are signed with the Standard Webhooks scheme. Verify the signature against the raw body before you parse it. If a delivery goes missing, page through GET /v1/events with after to replay it.

webhook.ts
import { webhooks } from "@unirail/sdk";

// Pass several secrets while you rotate them.
const event = await webhooks.verify({
  body: await request.text(),
  headers: request.headers,
  secret: process.env.UNIRAIL_WEBHOOK_SECRET,
});

Next steps