Getting started

The following steps and examples walk you through the API and the basic flows, common to all services.

After completing this guide, you will know how to:

  • Install YAXI’s client library.

  • Create authenticated input data in your backend for use by your frontend.

  • Call YAXI services from your frontend.

Generic flow

The following diagram illustrates the generic flow for using YAXI’s Open Banking services. This guide will explain each step in detail, though the UI implementation will not be covered. Backend refers to a server system where you control which services your users can access. Frontend refers to the part of your system under the user’s control, typically a pre-installed app or a web browser.

The diagram assumes that the bank connection and credentials are not known beforehand; otherwise, no user interaction would be needed.

sequenceDiagram actor User participant FE as Your Frontend participant BE as Your Backend participant YAXI participant Bank BE->>FE: Service ticket opt Unless already known FE->>User: Ask for bank User->>FE: Enters either IBAN,
bank code, BIC or bank name FE->>YAXI: Request list of banks matching user's input YAXI->>FE: Bank information for banks matching the filter FE->>User: Bank login form User->>FE: Enters credentials end FE->>YAXI: Trigger service using login credentials YAXI->>Bank: Login, take required steps Bank->>YAXI: Data YAXI->>FE: Results opt FE->>BE: Results end

The basic steps are:

  • Issue a service ticket in your backend and provide it to the frontend.

  • Let the user select a bank (unless already known).

  • Prompt the user for credentials (unless already known).

  • Invoke a YAXI Open Banking service.

  • Handle interrupts (if any).

  • Process results in the frontend as needed.

  • In case you have to process the results in your backend:

    • Forward the results to the backend.

    • Verify the results in the backend.

    • Process the results in the backend as needed.

Issue a service ticket backend

Any service you use requires an authorization token called a service ticket. Your backend issues tickets, authenticated by a secret key YAXI provides. YAXI uses JSON Web Tokens (JWT) with HMAC-SHA256 as the signature algorithm.

Keep your secret keys private at any time. Failing to do so may cause unauthorized parties to use your tickets at your costs.

In addition to authorizing your user for a service, tickets can carry input data that the user cannot influence, thanks to the ticket’s cryptographic signature. When you process results for a ticket in your backend, you can be certain that only the data specified in the ticket was used.

The following example shows how to define a generic function to issue tickets for different services and input data.

The maximum allowed lifetime for the tickets is 15 minutes.
Ticket identifiers must be unique and may not contain any identifiable data. Ideally, just generate a UUID for each ticket.

Issue a service ticket:

  • Python

  • Java

  • Rust

  • PHP

from datetime import datetime, timedelta

import jwt

HMAC_KEY = bytes.fromhex("YOUR_SECRET_KEY")
HMAC_KEY_ID = "YOUR_KEY_ID"


def issue_ticket(ticket_id: str, service: str, data) -> str:
    return jwt.encode(
        {
            "data": {
                "service": service,
                "id": ticket_id,
                "data": data,
            },
            "exp": int(
                (datetime.utcnow() + timedelta(minutes=10)).timestamp()
            ),
        },
        HMAC_KEY,
        algorithm="HS256",
        headers={
            "kid": HMAC_KEY_ID,
        },
    )
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;

import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.HexFormat;
import java.util.Map;
import static java.util.concurrent.TimeUnit.MINUTES;

public final class TicketGenerator {
    private static final byte[] HMAC_KEY = HexFormat.of().parseHex("YOUR_SECRET_KEY");
    private static final String HMAC_KEY_ID = "YOUR_KEY_ID";

    public static String issueTicket(String ticketId, String service, Object data) {
        Map<String, Object> claimsData = new HashMap<>(Map.of(
            "service", service,
            "id", ticketId
        ));
        claimsData.put("data", data);

        return Jwts.builder()
            .setHeader(Map.of("kid", HMAC_KEY_ID))
            .setClaims(Map.of("data", claimsData))
            .setExpiration(new Date(System.currentTimeMillis() + MINUTES.toMillis(10)))
            .signWith(Keys.hmacShaKeyFor(HMAC_KEY), SignatureAlgorithm.HS256)
            .compact();
    }
}
use std::time::{SystemTime, UNIX_EPOCH};

use serde::Serialize;
use serde_json::json;

const HMAC_KEY: &[u8] = b"YOUR_SECRET_KEY";
const HMAC_KEY_ID: &str = "YOUR_KEY_ID";

fn issue_ticket<T>(
    ticket_id: &str,
    service: &str,
    data: T,
) -> Result<String, jsonwebtoken::errors::Error>
where
    T: Serialize,
{
    let header = jsonwebtoken::Header {
        alg: jsonwebtoken::Algorithm::HS256,
        kid: Some(HMAC_KEY_ID.to_string()),
        ..Default::default()
    };

    jsonwebtoken::encode(
        &header,
        &json!({
            "data": {
                "service": service,
                "id": ticket_id,
                "data": data,
            },
            "exp": SystemTime::now()
                       .duration_since(UNIX_EPOCH)
                       .expect("Time went backwards")
                       .as_secs()
                       + 600,
        }),
        &jsonwebtoken::EncodingKey::from_secret(HMAC_KEY),
    )
}
$HMAC_KEY = hex2bin('YOUR_SECRET_KEY');
$HMAC_KEY_ID = 'YOUR_KEY_ID';

function issueTicket(string $ticketId, string $service, $data) {
    return Firebase\JWT\JWT::encode(
        [
            'data' => [
                'service' => $service,
                'id' => $ticketId,
                'data' => $data,
            ],
            'exp' => time() + 600
        ],
        $HMAC_KEY,
        'HS256',
        $HMAC_KEY_ID
    );
}

Example ticket for the Collect Payment service:

  • Ticket JWT

  • Decoded JWT header

  • Decoded JWT payload

eyJhbGciOiJIUzI1NiIsImtpZCI6IjY1Yjc5MDZkLTc4OGQtNDgxMy04M2U1LWF
lZjliZDVjMjY5ZiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7InNlcnZpY2UiOiJDb
2xsZWN0UGF5bWVudCIsImlkIjoiYzgzMjczNTAtMzhjMi00NGU1LWI5OWYtNTNm
M2NmOWU3NjBiIiwiZGF0YSI6eyJhbW91bnQiOnsiY3VycmVuY3kiOiJFVVIiLCJ
hbW91bnQiOiIxMDAifSwiY3JlZGl0b3JBY2NvdW50Ijp7ImliYW4iOiJOTDU4WU
FYSTEyMzQ1Njc4OTAifSwiY3JlZGl0b3JOYW1lIjoiWUFYSSBHbWJIIiwicmVta
XR0YW5jZSI6IlNpZ24tdXAgZmVlIHJvdXRleCAxMjM0NTY3ODkifX0sImV4cCI6
MTczMjcxODg5M30.N0W7MP6dkyXGeeCkwnZISX-Uh79o0HDkgy4hH5_XMII
{
  "alg": "HS256",
  "kid": "65b7906d-788d-4813-83e5-aef9bd5c269f",
  "typ": "JWT"
}
{
  "data": {
    "service": "CollectPayment",
    "id": "c8327350-38c2-44e5-b99f-53f3cf9e760b",
    "data": {
      "amount": {
        "currency": "EUR",
        "amount": "100"
      },
      "creditorAccount": {
        "iban": "NL58YAXI1234567890"
      },
      "creditorName": "YAXI GmbH",
      "remittance": "Sign-up fee routex 123456789"
    }
  },
  "exp": 1732718893
}

Set up the client frontend

The client lets you easily use YAXI’s services. It establishes a secure, verifiable channel from your frontend to YAXI’s services.

Install the client library:

  • npm

npm install routex-client --save

Initialize the client:

  • JavaScript

  • Kotlin

  • Rust

const client = new RoutexClient()
val client = RoutexClient()
let client = RoutexClient::new();
Environments

The client can be configured with a base URL, defaulting to the production environment if none is provided.

Initialize the client for the integration environment:

  • JavaScript

  • Kotlin

  • Rust

const client = new RoutexClient(
  new URL("https://integration.yaxi.tech/")
)
val client = RoutexClient(java.net.URI("https://integration.yaxi.tech/"))
let client = RoutexClient::with_url(
    "https://integration.yaxi.tech/".parse().expect("valid URL")
);

Let the user select a bank frontend

First, the user needs to select a bank. Use YAXI’s bank search API to implement the selection prompt. It allows you to find a bank by its name, aliases, BICs, bank codes, or IBAN.

Consider splitting the name into multiple terms for improved name matching.

Search for a bank:

  • JavaScript

  • Kotlin

  • Rust

import { ConnectionType } from 'routex-client';

// Split input at whitespace for improved name matching
const filters = userInput.split(/\s+/).map((term) => { return { term } })

filters.push({ types: [ConnectionType.Production] })

const connectionInfos = await client.search({
  ticket, (1)
  filters,
  ibanDetection: true,
  limit: 20
})
1 The search accepts any valid ticket for any service.
import tech.yaxi.routex_client.SearchFilter

// Split input at whitespace for improved name matching
val filters = term.split("\\s".toRegex()).map { SearchFilter.Term(it) }

val connectionInfos = client.search(
    ticket, (1)
    filters,
    ibanDetection = true,
    limit = 20u,
)
1 The search accepts any valid ticket for any service.
use routex_client::prelude::*;

// Split input at whitespace for improved name matching
let mut filters: Vec<_> = user_input
    .split_whitespace()
    .map(|term| SearchFilter::Term(term.to_string()))
    .collect();

filters.push(SearchFilter::Types(vec![ConnectionType::Production]));

let connection_infos = client
    .search(ticket, filters, true, Some(20)) (1)
    .await?;
1 The search accepts any valid ticket for any service.

The connection info represents a bank. It provides you with an ID, a display name, an optional advice to present to the user, a label for the user ID and an optional icon name.

A web service to fetch connection icons dynamically from YAXI is not generally available yet. You can bundle them into your app or service.
It often makes sense to display a default list of "most used" banks before the user starts typing. You can query this data from YAXI’s API and embed it into your application.

If you have a well-known connection ID at hand or one stored from a previous search, you can look up its information:

Look up a connection info:

  • JavaScript

  • Kotlin

  • Rust

let connectionInfo = await client.info(ticket, connectionId)
val connectionInfo = client.info(ticket, connectionId)
let connection_info = client.info(ticket, connection_id).await?;

Prompt the user for credentials frontend

Using your credentials form, ask the user for their user ID and password. You can include the advice text to guide the user in finding the right information.

See Providing Credentials for more information on the credentials form.

Collect credentials:

  • JavaScript

  • Kotlin

  • Rust

const credentials = {
  connectionId,
  userId,
  password
}
val credentials = Credentials(connectionId, userId, password)
let credentials = Credentials {
    connection_id,
    user_id,
    password,
    connection_data: None,
};

Use the Open Banking services frontend

See individual Services for their invocation. Let us take Collect Payment as an example:

Call the service:

  • JavaScript

  • Kotlin

  • Rust

let response = await client.collectPayment({
  credentials,
  ticket,
})
var response = client.collectPayment(credentials, ticket = ticket)
let response = client.collect_payment(
    credentials,
    None,
    ticket,
    None,
).await?;

The immediate response will either be the desired data or an interrupt. On first use, you will typically receive an interrupt for strong customer authentication (SCA) with the user’s bank. Let us assume a decoupled SCA process where the user authorizes access in a banking app. The response will be an SCA confirmation dialog in that case (see your client SDK for the specific types).

Confirm dialog without input for the accounts service:

  • JavaScript

  • Kotlin

  • Rust

if (response.constructor === Dialog &&
  response.input.constructor === Confirmation) {
  // Wait for user confirmation

  response = await client.confirmAccounts({
    ticket,
    context: response.input.context
  })
}
if (response is Response.Dialog &&
    response.input is DialogInput.Confirmation) {
    response = client.confirmAccounts(
        ticket,
        (response.input as DialogInput.Confirmation).context,
    )
}
if let OBResponse::Dialog(Dialog {
    input: DialogInput::Confirmation { context, .. },
    ..
}) = response
{
    // Wait for user confirmation

    response = client.confirm_accounts(ticket, context).await?;
}
This is an example for the Accounts service. See your client SDK for the respective methods for other services.

See Handling interrupts for details.

The result is, again, either another interrupt or the desired data. Let us assume the result contains the data now.

Extract result:

  • JavaScript

  • Kotlin

  • Rust

if (response.constructor === Result) {
  let result = response.jwt
  let session = response.session
  let connectionData = response.connectionData

  ...
}
if (response is Response.Result) {
    val result = response.result
    val session = response.session
    val connectionData = response.connectionData

    ...
}
if let OBResponse::Result(result, session, connection_data) = response {
    ...
}

Example result for the Accounts service:

  • Result JWT

  • Decoded JWT header

  • Decoded JWT payload

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6IjY1Yjc5MDZkLTc4OGQ
tNDgxMy04M2U1LWFlZjliZDVjMjY5ZiJ9.eyJkYXRhIjp7ImRhdGEiOlt7ImliY
W4iOiJOTDU4WUFYSTEyMzQ1Njc4OTAiLCJiaWMiOiJZQVhJREU3MVhYWCIsImN1
cnJlbmN5IjoiRVVSIiwib3duZXJOYW1lIjoiWUFYSSBHbWJIIn1dLCJ0aWNrZXR
JZCI6ImQwNWExNDhmLTc0NmQtNDE3Ny04OGFmLTZhOTIzMWM4NDI0ZCIsInRpbW
VzdGFtcCI6IjIwMjQtMTEtMjdUMTU6MzU6MDUuNDQ5OTQ5NzIwWiJ9fQ.0r3a6X
If8_Kp_7SGnSLNU6GOabCYB6rne2NTYw4EiK0
{
  "alg": "HS256",
  "kid": "65b7906d-788d-4813-83e5-aef9bd5c269f",
  "typ": "JWT"
}
{
  "data": {
    "data": [
      {
        "iban": "NL58YAXI1234567890",
        "bic": "YAXIDE71XXX",
        "currency": "EUR",
        "ownerName": "YAXI GmbH"
      }
    ],
    "ticketId": "d05a148f-746d-4177-88af-6a9231c8424d",
    "timestamp": "2024-11-27T15:35:05.449949720Z"
  }
}
Your backend must never trust results without first verifying their signature.

You may need to use the results only in your frontend or also in your backend, depending on your use case:

  • In your frontend, you can immediately process the results for display, storage, and similar actions.

  • In your backend, however, you must verify the authenticity of the results before processing them in any way. To that end, use your HMAC secret key to ensure the result data has not been altered. See Verify results for details.