Accounts 🏦

This file will explain how to add and manage bank accounts in Yoli. If you are using the Yoli API without a connected bank account and want to update your users manually, jump to the Using Yoli without a Bank Account section.

Syncing

NOTE: This feature is currently not available, but will be implemented soon. Stay tuned!

By default the information and transactions of already added accounts will be synced every few hours. (Note that some banks only refresh their online banking data much less often than that, which unfortunately can't be worked around).

Multiple Accounts Behind One Login

Due to the nature of banks, there actually might be more than one bank account behind single set of online-banking credentials.

For example a user might have a normal account, a daily savings account and a partner account, all behind a single login.

This adds complexity, as we cannot simply remove a single account from being periodically synced without also removing the rest. (More info in the relevant section)

Adding a Bank Login

To add a bank account you need to be properly authenticated.

Initiating a Bank Login

To retrieve your bank account data and credentials, you can use our in-build bank search. Use the addBankLogin mutation with appropriate bank code and credentials to initiate a bank login. The required credentials and loginInterface can be queried by our bank search.

To test this functionality, you can use the provided test login below.

mutation {
  addBankLogin(
    bankCode: "10020000",
    loginInterface: "FINTS_SERVER", 
    credentials: [
      {
        "label": "username",
        "value": "fino01"
      },
      {
        "label": "password", 
        "value": "654321"
      }
    ]
  )
}
{
  "data": {
    "addBankLogin": "<TASK_TOKEN>"
  }
}

Due to the complex nature of bank login systems, this process may take up to a few minutes and may take several steps in-between.

To handle this easier and eliminate the need for a constant internet connection, every started bank login will give you a "task token" which you can use to check on the state of the bank login and extract information on why it may have failed.

Note that each bank login automatically triggers the available modules for the user, such as contracts, documents, smartfeed, etc..

Before you can access your bank account data, every login requires a two-factor-authentication (2FA). In our context, this process is called a "challenge". To complete this challenge, multiple steps can be required depending on the chosen challenge of the user's bank: * Choosing the challenge type (optional) * Choosing a medium for the challenge (optional) * Sending the challenge input (TAN)

After initiating the bank login, you can check on the next required step via the Bank Login Status

Bank Login Status

You can check on the status of a bank login by using the bankLoginStatus. The status field describes the status of the complete bank login and will either be pending, success or failed. To get more information on why a login failed you use the message and providerError fields. For every step, we have a predefined timeout. This can be queried via the stepTimeLimitSecs property. Via the stepRemainingTime property, you can check how many seconds are left for the current step.

In addition, this query is used to retrieve information on the required 2FA challenge. Depending on the challenge process, the first steps will be either chooseChallengeType to choose the challenge type or chooseChallengeMedium to choose the challenge medium and finally challengeResponse or photoTanChallengeResponse for the challenge input. The different challenge options will be described here.

{
  bankLoginStatus(taskToken: "<task token>") {
    status
    step
    message
    stepTimeLimitSecs
    stepRemainingTime
  }
}
{
  "data": {
    "bankLoginStatus": {
      "status": "pending",
      "step": "chooseChallengeType",
      "stepTimeLimitSecs": 180,
      "stepRemaingTime": 78
    }
  }
}

Choose challenge type

Attention: This step is optional and only is required, if the user has activated several challenge types for his bank account.

First, check the bankLoginStatus for the available challengeTypes:

{
    "data": {
        "bankLoginStatus": {
            "status": "pending",
            "step": "chooseChallengeType",
            "challengeTypes": [
                {
                    "id": "930",
                    "name": "Mobile TAN per SMS"
                },
                {
                    "id": "931",
                    "name": "Push-TAN per App"
                },
                {
                    "id": "932",
                    "name": "Photo-TAN"
                },
                {
                    "id": "910",
                    "name": "Chip-TAN manuell HHD 1.3.2"
                },
                {
                    "id": "920",
                    "name": "Chip-TAN manuell HHD 1.4"
                },
                {
                    "id": "911",
                    "name": "Chip-TAN optisch HHD 1.3.2"
                },
                {
                    "id": "921",
                    "name": "Chip-TAN optisch HHD 1.4"
                },
                {
                    "id": "913",
                    "name": "Chip-TAN QR"
                },
                {
                    "id": "912",
                    "name": "Chip-TAN USB"
                }
            ],
            "challengeMedia": null
        }
    }
}

Send the id of the chosen challenge type via the sendChallengeInput mutation.

mutation {
  sendChallengeInput(taskToken: "foo", "input": "930")
}
{
  "data": {
    "sendChallengeInput": "<TASK_TOKEN>"
  }
}

Choose challenge medium

Attention: This step is optional and only is required, if the user has activated several challenge media for his bank account.

First, check the bankLoginStatus for the available challengeMedia:

{
    "data": {
        "bankLoginStatus": {
            "status": "pending",
            "step": "chooseChallengeMedium",
            "challengePhotoTanData": null,
            "challengeMedia": [
                {
                    "id": "Handy Eins",
                    "name": "********5679"
                },
                {
                    "id": "Handy Zwei",
                    "name": ""
                }
            ]
        }
    }
}

Send the id of the chosen challenge medium via the sendChallengeInput mutation.

mutation {
  sendChallengeInput(taskToken: "foo", "input": "Handy Eins")
}
{
  "data": {
    "sendChallengeInput": "<TASK_TOKEN>"
  }
}

Send challenge input

First, check the bankLoginStatus for the specifc step. The step should either be challengeResponse, photoTanChallengeResponse, flickerTanChallengeResponse or decoupledChallengeResponse. In addition, this step contains a challengeInputInstructions property which can be used as an advice for the user, how he can retrieve his TAN. You can use the challengeInputLabel for the required input property.

Scenario 1: challengeResponse:

{
    "data": {
        "bankLoginStatus": {
            "status": "pending",
            "challengeInputLabel": "TAN",
            "challengeInputInstructions": "Bitte entnehmen sie die TAN der gesendeten SMS ihrer Bank und geben Sie sie hier ein.",
            "step": "challengeResponse"
        }
    }
}

Scenario 2: photoTanChallengeResponse: The challengePhotoTanData will contain a base64 encoded picture to display for the photo tan procedure.

{
    "data": {
        "bankLoginStatus": {
            "status": "pending",
            "challengeInputLabel": "TAN",
            "challengeInputInstructions": "Bitte scannen sie das Photo-TAN Bild und geben Sie die TAN ihrer Bank hier ein.",
            "step": "photoTanChallengeResponse",
            "challengePhotoTanData": "base64string",
        }
    }
}

Scenario 3: flickerTanChallengeResponse: The challengeFlickerTanData will contain a flicker code, which can be used to generate the flicker image for the user.

{
    "data": {
        "bankLoginStatus": {
            "status": "pending",
            "challengeInputLabel": "TAN",
            "challengeInputInstructions": "Bitte scannen sie das Flicker-TAN Bild und geben Sie die TAN ihrer Bank hier ein.",
            "step": "flickerTanChallengeResponse",
            "challengeFlickerTanData": "123,45",
        }
    }
}

Scenario 4: decoupledChallengeResponse: The challengeFlickerTanData will contain a flicker code, which can be used to generate the flicker image for the user.

{
    "data": {
        "bankLoginStatus": {
            "status": "pending",
            "challengeInputLabel": "Login Freigabe",
            "challengeInputInstructions": "Bitte wechseln sie in ihre Banking App und bestätigen sie den Konto-Zugriff.",
            "step": "decoupledChallengeResponse",
        }
    }
}

For the scenarios 1-3, the user should have received a transaction authentication number(TAN) via his chosen challenge type and medium. Send the TAN via the sendChallengeInput mutation. For scenario 4 the user will have to switch to his banking app and approve the bank access. He will not receive a TAN in this case, but you will need to trigger the sendChallengeInput with an empy input. After finishing the bank login your bank account data will be retrievable via the me query on the property accounts.

mutation {
  sendChallengeInput(taskToken: "foo", "input": "123456")
}
{
  "data": {
    "sendChallengeInput": "<TASK_TOKEN>"
  }
}

Synchronize a Bank Login

After successfully connecting a bank login, you can synchronize a bank login and fetch the latest transaction data via the syncBankLogin query. The required bankLoginId can be found on the Account object. The syncBankLogin query returns a task token, which you can use in the same way as the task token of the addBankLogin mutation in the Initiation a bank login section. For legal reasons, it might be required to repeat the challenge procedure. Use the task token with the Bank Login Status query to check for the next step.

Attention: To avoid abusing this mechanism, synchronizing your account is limited to once every hour. To check your last successful sync, have a look at the timestampLastSync property on the Account object. In addition, you can look up the timeToNextSync property on the Account object which returns the remaining time to the next sync in seconds. If you try to execute syncBankLogin anyhow, you will receive a #syncNotOldEnough error.

{
  syncBankLogin(bankLoginId: "<BANK_LOGIN_ID>")
}

After finishing the bank login synchronization, your updated transaction data will be retrievable via the me query on the property accounts.

Select Account

The final step of the bank access process is to select the accounts behind a bank login.

You can add the availableAccounts array field to the bankLoginStatus query in order to retrieve the account Ids.

{
  bankLoginStatus(taskToken: "<TASK_TOKEN>") {
    status
    message
    availableAccounts {
      accountId
    }
  }
}
{
  "data": {
    "bankLoginStatus": {
      "status": "success",
      "message": null,
      "availableAccounts": [
        {
          "accountId": "<ACCOUNT_ID>"
        }
      ]
    }
  }
}

Send the chosen account ids via the selectAccount query or do not specify any account ids to choose all accounts.

{
  #select all accounts
  selectAccount(taskToken: "<TASK_TOKEN>")
}
{
  #select specific accounts
  selectAccount(taskToken: "<TASK_TOKEN>", accountIds:["<ACCOUNT_ID_1>", "<ACCOUNT_ID_2>"])
}

The returning result contains a selectAccount field with true if the selection was successful.

{
  "data": {
    "selectAccount": true
  }
}

Managing Bank Accounts

Customizing a Bank Account

You may want to add new or modified information to existing bank accounts. This can be done via the customizeAccount mutation.

mutation {
  customizeAccount(accountId: "<ACCOUNT_ID>", customName: "foo bar")
}

Removing a Single Account

If you want to only remove a single account from the accounts behind a set of credentials, you can hide that account.

This will prevent it from being considered in any backend operation or frontend query except when explicitly asked.

To achieve this you can use the customizeAccount mutation and set the hidden field to true.

mutation {
  customizeAccount(accountId: "<ACCOUNT_ID>", hidden: true)
}

Removing a Bank Login

Removing a bank login (a set of credentials) will prevent these accounts from being synced and remove all their associated data.

Just use the removeBankLogin mutation to achieve this.

mutation {
  removeBankLogin(bankLoginId: "<BANK_LOGIN_ID>")
}

Using Yoli without a Bank Account

Update Transactions

In order to update your contracts, smart alerts, etc. manually, simply use the updateTransactions mutation. Yoli will synchronize your user's data without saving the transactions to a bank account.

Attention: To guarantee seamless functionality of all features of the Yoli API, the accountId and transactionId fields should be unique and continous. Otherwise, you might experience faulty behaviour of the contracts feature i.e.

mutation {
  updateTransactions(defaultInterval: "MONTHLY", transactions: [
    {
      "paymentPartner": {
        "name": "Fino Digital"
      },
      "accountId": "ua4kUE8z1195",
      "amount": 210748,
      "dateOfTransaction": "2018-10-05T14:48:00.000Z",
      "purpose": "Gehalt Oktober 2018",
      "transactionCode": 999,
      "transactionId": "p4iBJ89M583afaF28rSuPJwF2"
    },
    {
      "paymentPartner": {
        "name": "Fino Digital"
      },
      "accountId": "ua4kUE8z1195",
      "amount": 210748,
      "dateOfTransaction": "2018-11-05T14:48:00.000Z",
      "purpose": "Gehalt November 2018",
      "transactionCode": 999,
      "transactionId": "eQzjT8mOWy6eEzsiajP431O7H"
    }
  ])
}