├── .gitignore
├── php_example
├── .gitignore
├── composer.json
└── enablebanking.php
├── python_example
├── .gitignore
├── requirements.txt
├── README.md
├── payment_initiation.py
└── account_information.py
├── js_example
├── .gitignore
├── package.json
├── README.md
├── utils.js
├── paymentInitiation.js
└── accountInformation.js
├── ruby_example
├── Gemfile
├── .gitignore
├── README.md
├── Gemfile.lock
└── lib
│ └── account_information.rb
├── go_example
├── go.mod
├── go.sum
├── models.go
├── utils.go
├── pis.go
└── ais.go
├── postman_example
├── postman-screenshot.png
├── README.md
└── Enable Banking.postman_collection.json
├── config.json
├── cs_example
├── cs_example.csproj
├── README.md
└── Program.cs
├── README.md
└── LICENSE
/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 | *.key
3 | *.pem
4 |
--------------------------------------------------------------------------------
/php_example/.gitignore:
--------------------------------------------------------------------------------
1 | vendor
2 | composer.lock
--------------------------------------------------------------------------------
/python_example/.gitignore:
--------------------------------------------------------------------------------
1 | .python-version
2 | *.pyc
3 |
--------------------------------------------------------------------------------
/js_example/.gitignore:
--------------------------------------------------------------------------------
1 | package-lock.json
2 | /node_modules
3 |
--------------------------------------------------------------------------------
/python_example/requirements.txt:
--------------------------------------------------------------------------------
1 | pyjwt[crypto]==2.0.1
2 | requests==2.24.0
3 |
--------------------------------------------------------------------------------
/php_example/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "require": {
3 | "firebase/php-jwt": "^5.2"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/ruby_example/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gem 'faraday'
4 | gem 'json'
5 | gem 'jwt'
6 |
--------------------------------------------------------------------------------
/go_example/go.mod:
--------------------------------------------------------------------------------
1 | module example
2 |
3 | go 1.17
4 |
5 | require github.com/google/uuid v1.3.0 // indirect
6 |
--------------------------------------------------------------------------------
/ruby_example/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 | *.rbc
3 | /.config
4 | /.bundle/
5 | /vendor/bundle
6 | /lib/bundler/man/
7 |
--------------------------------------------------------------------------------
/postman_example/postman-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enablebanking/enablebanking-api-samples/HEAD/postman_example/postman-screenshot.png
--------------------------------------------------------------------------------
/go_example/go.sum:
--------------------------------------------------------------------------------
1 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
2 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
3 |
--------------------------------------------------------------------------------
/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "keyPath": "0377783c-3414-4bb1-a7d4-0c3eb54cd4b0.pem",
3 | "applicationId": "0377783c-3414-4bb1-a7d4-0c3eb54cd4b0",
4 | "redirectUrl": "http://localhost:8080/auth_redirect"
5 | }
6 |
--------------------------------------------------------------------------------
/postman_example/README.md:
--------------------------------------------------------------------------------
1 | Please note: Each API request needs a JWT token, which is generated in the pre-request script from the collection. For using this, add your application private key (variable name - 'private_key') and kid (variable name - 'kid') to the collection variables.
2 |
3 | 
4 |
--------------------------------------------------------------------------------
/ruby_example/README.md:
--------------------------------------------------------------------------------
1 | This implementation uses [Ruby JWT](https://github.com/jwt/ruby-jwt)
2 | and [Faraday](https://lostisland.github.io/faraday/) gems.
3 |
4 | ## Installing dependencies
5 |
6 | ```bash
7 | bundle install --path vendor/bundle
8 | ```
9 |
10 | ## Running the sample
11 |
12 | ```bash
13 | ruby lib/account_information.rb
14 | ```
15 |
--------------------------------------------------------------------------------
/cs_example/cs_example.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net7.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/cs_example/README.md:
--------------------------------------------------------------------------------
1 | This implementation uses [System.IdentityModel.Tokens.Jwt](https://www.nuget.org/packages/System.IdentityModel.Tokens.Jwt/) package for creating JWT.
2 |
3 | The code has been tested on .NET Core 7.0 only.
4 |
5 | Update `config.json` to use your own key and application id.
6 |
7 | ## Running the sample
8 |
9 | ```bash
10 | dotnet run
11 | ```
12 |
--------------------------------------------------------------------------------
/js_example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "js_exmaple",
3 | "version": "1.0.0",
4 | "main": "accountInformation.js",
5 | "scripts": {
6 | "test": "echo \"Error: no test specified\" && exit 1"
7 | },
8 | "author": "Pavel Botsman",
9 | "license": "ISC",
10 | "engines": {
11 | "node": ">=14"
12 | },
13 | "dependencies": {
14 | "jwa": "^2.0.0",
15 | "node-fetch": "^2.6.1"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/js_example/README.md:
--------------------------------------------------------------------------------
1 | This implementation uses [node-jwa](https://github.com/auth0/node-jwa)
2 | and [node-fetch](https://github.com/node-fetch/node-fetch) libraries.
3 |
4 | ## Installing dependencies
5 |
6 | ```bash
7 | npm install
8 | ```
9 |
10 | ## Running the samples
11 |
12 | Account information:
13 |
14 | ```bash
15 | node accountInformation.js
16 | ```
17 |
18 | Payment initiation:
19 |
20 | ```bash
21 | node paymentInitiation.js
22 | ```
23 |
--------------------------------------------------------------------------------
/python_example/README.md:
--------------------------------------------------------------------------------
1 | This implementation uses [PyJWT](https://pyjwt.readthedocs.io/)
2 | and [Requests](https://requests.readthedocs.io/) libraries.
3 |
4 | ## Installing dependencies
5 |
6 | ```bash
7 | pip3 install -r requirements.txt
8 | ```
9 |
10 | ## Running the samples
11 |
12 | Account information:
13 |
14 | ```bash
15 | python3 account_information.py
16 | ```
17 |
18 | Payment initiation:
19 |
20 | ```bash
21 | python3 payment_initiation.py
22 | ```
23 |
--------------------------------------------------------------------------------
/ruby_example/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | faraday (1.3.0)
5 | faraday-net_http (~> 1.0)
6 | multipart-post (>= 1.2, < 3)
7 | ruby2_keywords
8 | faraday-net_http (1.0.1)
9 | json (2.5.1)
10 | jwt (2.2.2)
11 | multipart-post (2.1.1)
12 | ruby2_keywords (0.0.2)
13 |
14 | PLATFORMS
15 | ruby
16 |
17 | DEPENDENCIES
18 | faraday
19 | json
20 | jwt
21 |
22 | BUNDLED WITH
23 | 1.17.2
24 |
--------------------------------------------------------------------------------
/go_example/models.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | type Config struct {
4 | KeyPath string `json:"keyPath"`
5 | ApplicationId string `json:"applicationId"`
6 | RedirectUrl string `json:"redirectUrl"`
7 | }
8 |
9 | type Access struct {
10 | ValidUntil string `json:"valid_until"`
11 | }
12 |
13 | type Aspsp struct {
14 | Name string `json:"name"`
15 | Country string `json:"country"`
16 | }
17 |
18 | type AuthorizationBody struct {
19 | Access Access `json:"access"`
20 | Aspsp Aspsp `json:"aspsp"`
21 | State string `json:"state"`
22 | RedirectUrl string `json:"redirect_url"`
23 | PsuType string `json:"psu_type"`
24 | }
25 |
26 | // Only used fields are added
27 | type AuthorizeSessionBody struct {
28 | Code string `json:"code"`
29 | }
30 |
31 | type Account struct {
32 | Uid string `json:"uid"`
33 | }
34 |
35 | type AuthorizedSession struct {
36 | SessionId string `json:"session_id"`
37 | Accounts []Account
38 | }
39 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Enable Banking API Code Samples
2 |
3 | This repository contains code samples for utilizing the [Enable Banking API](https://api.enablebanking.com/).
4 |
5 | ## Prerequisites
6 |
7 | To successfully use these samples, please follow these steps:
8 |
9 | 1. Create a developer account at https://enablebanking.com/ by pressing "Get started".
10 |
11 | 2. Register a new application at https://enablebanking.com/cp/applications.
12 | If you opt to generate the private key for the application in the browser, the application's
13 | private key file will be downloaded to your computer. It will be named with the ID assigned to
14 | the application and have a ".pem" extension (e.g., `aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee.pem`).
15 |
16 | 4. Modify values of the `keyPath` and `applicationId` fields in the `config.json` file with the
17 | values obtained in previous step.
18 | *This step in not necessary if you are going to use our Postman collection.*
19 |
20 | 5. Navigate to a folder with code samples in a language of your choice and follow the instructions
21 | provided in the README.md file, which can be found there.
22 |
23 | For further information please refer to the [Enable Banking Docs](https://enablebanking.com/docs/)
24 |
--------------------------------------------------------------------------------
/js_example/utils.js:
--------------------------------------------------------------------------------
1 | const fs = require("fs");
2 | const jwa = require("jwa");
3 | const path = require("path");
4 | const readline = require('readline');
5 |
6 | const config = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "config.json")))
7 | const KEY_PATH = config.keyPath;
8 | const APPLICATION_ID = config.applicationId;
9 |
10 |
11 | const base64AddPadding = (str) => {
12 | return str + "=".repeat((4 - str.length % 4) % 4);
13 | }
14 |
15 | const urlunsafeSignature = (signature) => {
16 | return signature.replace(/\_/g, "/").replace(/\-/g, "+");
17 | }
18 |
19 | const getJWTHeader = () => {
20 | return encodeData({
21 | typ: "JWT",
22 | alg: "RS256",
23 | kid: APPLICATION_ID
24 | })
25 | }
26 |
27 | const encodeData = (data) => {
28 | return Buffer.from(JSON.stringify(data)).toString("base64").replace("=", "")
29 | }
30 |
31 | const getJWTBody = (exp) => {
32 | const timestamp = Math.floor((new Date()).getTime() / 1000)
33 | return encodeData({
34 | iss: "enablebanking.com",
35 | aud: "api.enablebanking.com",
36 | iat: timestamp,
37 | exp: timestamp + exp,
38 | })
39 | }
40 |
41 | const signWithKey = (data) => {
42 | const key = fs.readFileSync(KEY_PATH, "utf8");
43 | const hmac = jwa("RS256");
44 | return hmac.sign(data, key);
45 | }
46 |
47 | const getJWT = (exp = 3600) => {
48 | const jwtHeaders = getJWTHeader()
49 | const jwtBody = getJWTBody(exp);
50 | const jwtSignature = signWithKey(`${jwtHeaders}.${jwtBody}`)
51 | return `${jwtHeaders}.${jwtBody}.${jwtSignature}`
52 | }
53 |
54 | function input(query) {
55 | const rl = readline.createInterface({
56 | input: process.stdin,
57 | output: process.stdout,
58 | });
59 |
60 | return new Promise(resolve => rl.question(query, ans => {
61 | rl.close();
62 | resolve(ans);
63 | }))
64 | }
65 |
66 | function getCode(url) {
67 | const query = url.split("?")[1];
68 | for (const pair of query.split("&")) {
69 | const [key, val] = pair.split("=")
70 | if (key === "code") {
71 | return val;
72 | }
73 | }
74 | }
75 |
76 | module.exports = {
77 | getJWT: getJWT,
78 | config: config,
79 | input: input,
80 | getCode: getCode
81 | }
82 |
--------------------------------------------------------------------------------
/js_example/paymentInitiation.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fetch = require('node-fetch');
4 | const { getJWT, config } = require("./utils")
5 |
6 |
7 | const main = async function () {
8 | const JWT = getJWT()
9 | const BASE_URL = "https://api.enablebanking.com"
10 | const REDIRECT_URL = config.redirectUrl
11 | const ASPSP_NAME = "S-Pankki"
12 | const ASPSP_COUNTRY = "FI"
13 | const baseHeaders = {
14 | Authorization: `Bearer ${JWT}`,
15 | "Content-Type": "application/json"
16 | }
17 | const applicationResponse = await fetch(`${BASE_URL}/application`, {
18 | headers: baseHeaders
19 | })
20 | console.log(`Application data: ${await applicationResponse.text()}`)
21 |
22 | const aspspsResponse = await fetch(`${BASE_URL}/aspsps`, {
23 | headers: baseHeaders
24 | })
25 | console.log(`ASPSPS data: ${await aspspsResponse.text()}`)
26 |
27 | // Initiating payment
28 | const body = {
29 | "payment_type": "SEPA",
30 | "payment_request": {
31 | "credit_transfer_transaction": [
32 | {
33 | "beneficiary": {
34 | "creditor_account": {
35 | "scheme_name": "IBAN",
36 | "identification": "FI7473834510057469",
37 | },
38 | "creditor": {
39 | "name": "Test",
40 | },
41 | },
42 | "instructed_amount": { "amount": "2.00", "currency": "EUR" },
43 | "reference_number": "123",
44 | }
45 | ],
46 | },
47 | "aspsp": { "name": ASPSP_NAME, "country": ASPSP_COUNTRY },
48 | "state": "some_test_state",
49 | "redirect_url": REDIRECT_URL,
50 | "psu_type": "personal",
51 | }
52 | const paymentResponse = await fetch(`${BASE_URL}/payments`, {
53 | method: "POST",
54 | headers: baseHeaders,
55 | body: JSON.stringify(body)
56 | })
57 | const paymentData = await paymentResponse.text();
58 | console.log(`Payment data: ${paymentData}`)
59 | const paymentDataJson = JSON.parse(paymentData)
60 |
61 | console.log("Use following credentials to authenticate: customera / 12345678")
62 | console.log("To authenticate open URL:")
63 | console.log(paymentDataJson.url)
64 |
65 | // Getting payment status
66 | // This request can be called multiple times to check the status of the payment
67 | const paymentId = paymentDataJson.payment_id
68 | const paymentStatusResponse = await fetch(`${BASE_URL}/payments/${paymentId}`, {
69 | headers: baseHeaders
70 | })
71 | const paymentStatusData = await paymentStatusResponse.text()
72 | console.log(`Payment status data: ${paymentStatusData}`)
73 | };
74 |
75 | (async () => {
76 | try {
77 | await main()
78 | } catch (error) {
79 | console.log(`Unexpected error happened: ${error}`)
80 | }
81 | })();
82 |
--------------------------------------------------------------------------------
/go_example/utils.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "crypto"
5 | "crypto/rand"
6 | "crypto/rsa"
7 | "crypto/sha256"
8 | "crypto/x509"
9 | "encoding/base64"
10 | "encoding/json"
11 | "encoding/pem"
12 | "errors"
13 | "fmt"
14 | "io/ioutil"
15 | "time"
16 | )
17 |
18 | func readKey(path string) ([]byte, error) {
19 | file, err := ioutil.ReadFile(path)
20 | if err != nil {
21 | return nil, err
22 | }
23 | return file, nil
24 | }
25 |
26 | func loadPrivateKey(path string) (*rsa.PrivateKey, error) {
27 | keyContent, err := readKey(path)
28 | if err != nil {
29 | return nil, err
30 | }
31 | block, _ := pem.Decode(keyContent)
32 | if block == nil {
33 | return nil, errors.New("failed to parse PEM private key")
34 | }
35 | switch block.Type {
36 | case "RSA PRIVATE KEY":
37 | privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
38 | if err != nil {
39 | return nil, err
40 | }
41 | return privateKey, nil
42 | case "PRIVATE KEY":
43 | privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
44 | if err != nil {
45 | return nil, err
46 | }
47 | return privateKey.(*rsa.PrivateKey), nil
48 | default:
49 | fmt.Println("Unsupported key type:", block.Type)
50 | return nil, errors.New("failed to parse PEM private key")
51 | }
52 | }
53 |
54 | func sign(privateKey *rsa.PrivateKey, data []byte) (string, error) {
55 | hash := sha256.New()
56 | hash.Write(data)
57 | hashed := hash.Sum(nil)
58 | signature, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hashed)
59 | if err != nil {
60 | return "", err
61 | }
62 | encodedSignature := base64.RawURLEncoding.EncodeToString(signature)
63 | return encodedSignature, nil
64 | }
65 |
66 | func getJwtHeader(appId string) (string, error) {
67 | encodedHeader, err := json.Marshal(struct {
68 | Alg string `json:"alg"`
69 | Typ string `json:"typ"`
70 | Kid string `json:"kid"`
71 | }{
72 | Alg: "RS256",
73 | Typ: "JWT",
74 | Kid: appId,
75 | })
76 | if err != nil {
77 | return "", err
78 | }
79 | return base64.RawURLEncoding.EncodeToString(encodedHeader), nil
80 | }
81 |
82 | func getJwtBody() (string, error) {
83 | iat := time.Now().Unix()
84 | encodedBody, err := json.Marshal(struct {
85 | Iss string `json:"iss"`
86 | Aud string `json:"aud"`
87 | Iat int64 `json:"iat"`
88 | Exp int64 `json:"exp"`
89 | }{
90 | Iss: "enablebanking.com",
91 | Aud: "api.enablebanking.com",
92 | Iat: iat,
93 | Exp: iat + 3600,
94 | })
95 | if err != nil {
96 | return "", err
97 | }
98 | return base64.RawURLEncoding.EncodeToString(encodedBody), nil
99 | }
100 |
101 | func getJwt(keyPath string, appId string) (string, error) {
102 | header, err := getJwtHeader(appId)
103 | if err != nil {
104 | return "", err
105 | }
106 | body, err := getJwtBody()
107 | if err != nil {
108 | return "", err
109 | }
110 | signBody := fmt.Sprintf("%s.%s", header, body)
111 | privateKey, err := loadPrivateKey(keyPath)
112 | if err != nil {
113 | return "", err
114 | }
115 | signature, err := sign(privateKey, []byte(signBody))
116 | if err != nil {
117 | return "", err
118 | }
119 | return fmt.Sprintf("%s.%s", signBody, signature), nil
120 | }
121 |
--------------------------------------------------------------------------------
/js_example/accountInformation.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fetch = require('node-fetch');
4 | const {getJWT, config, input, getCode} = require("./utils")
5 |
6 |
7 | const main = async function() {
8 | const JWT = getJWT()
9 | const BASE_URL = "https://api.enablebanking.com"
10 | const REDIRECT_URL = config.redirectUrl
11 | const BANK_NAME = "Nordea"
12 | const BANK_COUNTRY = "FI"
13 | const baseHeaders = {
14 | Authorization: `Bearer ${JWT}`,
15 | "Content-Type": "application/json"
16 | }
17 | const applicationResponse = await fetch(`${BASE_URL}/application`, {
18 | headers: baseHeaders
19 | })
20 | console.log(`Application data: ${await applicationResponse.text()}`)
21 |
22 | const aspspsResponse = await fetch(`${BASE_URL}/aspsps`, {
23 | headers: baseHeaders
24 | })
25 | // If you want you can override BANK_NAME and BANK_COUNTRY with any bank from this list
26 | console.log(`ASPSPS data: ${await aspspsResponse.text()}`)
27 |
28 | // 10 days ahead
29 | const validUntil = new Date(new Date().getTime() + 10 * 24 * 60 * 60 * 1000);
30 | const startAuthorizationBody = {
31 | access: {
32 | valid_until: validUntil.toISOString()
33 | },
34 | aspsp: {
35 | name: BANK_NAME,
36 | country: BANK_COUNTRY
37 | },
38 | state: "some_test_state",
39 | redirect_url: REDIRECT_URL,
40 | psu_type: "personal"
41 | }
42 | const psuHeaders = {
43 | ...baseHeaders,
44 | "psu-ip-address": "10.10.10.10",
45 | "psu-user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:80.0) Gecko/20100101 Firefox/80.0"
46 | }
47 | const startAuthorizationResponse = await fetch(`${BASE_URL}/auth`, {
48 | method: "POST",
49 | headers: psuHeaders,
50 | body: JSON.stringify(startAuthorizationBody)
51 | })
52 | const startAuthorizationData = await startAuthorizationResponse.text();
53 | console.log(`Start authorization data: ${startAuthorizationData}`)
54 | const redirectedUrl = await input(`Please go to ${ JSON.parse(startAuthorizationData)["url"] }, authorize consent and paste here the url you have been redirected to: `)
55 |
56 | const createSessionBody = {
57 | code: getCode(redirectedUrl)
58 | }
59 | const createSessionResponse = await fetch(`${BASE_URL}/sessions`, {
60 | method: "POST",
61 | headers: psuHeaders,
62 | body: JSON.stringify(createSessionBody)
63 | })
64 | const createSessionData = await createSessionResponse.text()
65 | console.log(`Create session data: ${createSessionData}`)
66 | const sessionId = JSON.parse(createSessionData).session_id
67 |
68 | const sessionResponse = await fetch(`${BASE_URL}/sessions/${sessionId}`, {
69 | headers: baseHeaders
70 | })
71 | const sessionData = await sessionResponse.text()
72 | console.log(`Session data ${sessionData}`)
73 |
74 | const accountId = JSON.parse(sessionData).accounts[0]
75 | const accountBalancesResponse = await fetch(`${BASE_URL}/accounts/${accountId}/balances`, {
76 | headers: psuHeaders
77 | })
78 | console.log(`Account balances data: ${await accountBalancesResponse.text()}`)
79 |
80 | const accountTransactionsResponse = await fetch(`${BASE_URL}/accounts/${accountId}/transactions`, {
81 | headers: psuHeaders
82 | })
83 | console.log(`Account transactions data: ${await accountTransactionsResponse.text()}`)
84 | };
85 |
86 | (async () => {
87 | try{
88 | await main()
89 | } catch (error) {
90 | console.log(`Unexpected error happened: ${error}`)
91 | }
92 | })();
93 |
--------------------------------------------------------------------------------
/python_example/payment_initiation.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 | import sys
4 | import uuid
5 | from datetime import datetime, timezone, timedelta
6 | from pprint import pprint
7 | from urllib.parse import urlparse, parse_qs
8 |
9 | import requests
10 | import jwt as pyjwt
11 |
12 |
13 | API_ORIGIN = "https://api.enablebanking.com"
14 | ASPSP_NAME = "S-Pankki"
15 | ASPSP_COUNTRY = "FI"
16 |
17 | def main():
18 | file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "config.json")
19 | with open(file_path, "r") as f:
20 | config = json.load(f)
21 | iat = int(datetime.now().timestamp())
22 | jwt_body = {
23 | "iss": "enablebanking.com",
24 | "aud": "api.enablebanking.com",
25 | "iat": iat,
26 | "exp": iat + 3600,
27 | }
28 | jwt = pyjwt.encode(
29 | jwt_body,
30 | open(os.path.join('..', config["keyPath"]), "rb").read(),
31 | algorithm="RS256",
32 | headers={"kid": config["applicationId"]},
33 | )
34 | print(jwt)
35 |
36 | base_headers = {"Authorization": f"Bearer {jwt}"}
37 |
38 | # Requesting application details
39 | r = requests.get(f"{API_ORIGIN}/application", headers=base_headers)
40 | if r.status_code != 200:
41 | print(f"Error response {r.status_code}:", r.text)
42 | return
43 | app = r.json()
44 | print("Application details:")
45 | pprint(app)
46 |
47 | # Requesting available ASPSPs
48 | r = requests.get(f"{API_ORIGIN}/aspsps", headers=base_headers)
49 | if r.status_code != 200:
50 | print(f"Error response {r.status_code}:", r.text)
51 | return
52 | print("Available ASPSPs:")
53 | pprint(r.json()["aspsps"])
54 |
55 | # Initiating payment
56 | body = {
57 | "payment_type": "SEPA",
58 | "payment_request": {
59 | "credit_transfer_transaction": [
60 | {
61 | "beneficiary": {
62 | "creditor_account": {
63 | "scheme_name": "IBAN",
64 | "identification": "FI7473834510057469",
65 | },
66 | "creditor": {
67 | "name": "Test",
68 | },
69 | },
70 | "instructed_amount": {"amount": "2.00", "currency": "EUR"},
71 | "reference_number": "123",
72 | }
73 | ],
74 | },
75 | "aspsp": {"name": ASPSP_NAME, "country": ASPSP_COUNTRY},
76 | "state": str(uuid.uuid4()),
77 | "redirect_url": config["redirectUrl"],
78 | "psu_type": "personal",
79 | }
80 | r = requests.post(f"{API_ORIGIN}/payments", json=body, headers=base_headers)
81 | if r.status_code != 200:
82 | print(f"Error response {r.status_code}:", r.text)
83 | return
84 |
85 | payment = r.json()
86 | print("Use following credentials to authenticate: customera / 12345678")
87 | print("To authenticate open URL:")
88 | print(payment["url"])
89 |
90 | # Getting payment status
91 | # This request can be called multiple times to check the status of the payment
92 | payment_id = payment["payment_id"]
93 | r = requests.get(f"{API_ORIGIN}/payments/{payment_id}", headers=base_headers)
94 | if r.status_code != 200:
95 | print(f"Error response {r.status_code}:", r.text)
96 | return
97 | print("Payment status:")
98 | pprint(r.json())
99 |
100 |
101 |
102 | if __name__ == "__main__":
103 | main()
104 |
--------------------------------------------------------------------------------
/go_example/pis.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "fmt"
7 | "github.com/google/uuid"
8 | "io"
9 | "io/ioutil"
10 | "net/http"
11 | )
12 |
13 | const configPath = "../config.json"
14 | const APIOrigin = "https://api.enablebanking.com"
15 | const ASPSPName = "S-Pankki"
16 | const ASPSPCountry = "FI"
17 |
18 | func main() {
19 | config := Config{}
20 | configFile, err := ioutil.ReadFile(configPath)
21 | err = json.Unmarshal(configFile, &config)
22 | if err != nil {
23 | panic(err)
24 | }
25 | jwt, err := getJwt(config.KeyPath, config.ApplicationId)
26 | if err != nil {
27 | panic(err)
28 | }
29 |
30 | client := &http.Client{}
31 | request, err := http.NewRequest("GET", APIOrigin+"/application", nil)
32 | if err != nil {
33 | panic(err)
34 | }
35 | request.Header.Add("Authorization", "Bearer "+jwt)
36 | response, err := client.Do(request)
37 | if err != nil {
38 | panic(err)
39 | }
40 | defer response.Body.Close()
41 | body, err := ioutil.ReadAll(response.Body)
42 | if err != nil {
43 | panic(err)
44 | }
45 | fmt.Print("Application details: ")
46 | fmt.Println(string(body))
47 |
48 | request, err = http.NewRequest("GET", APIOrigin+"/aspsps", nil)
49 | if err != nil {
50 | panic(err)
51 | }
52 | request.Header.Add("Authorization", "Bearer "+jwt)
53 | response, err = client.Do(request)
54 | if err != nil {
55 | panic(err)
56 | }
57 | defer response.Body.Close()
58 | body, err = ioutil.ReadAll(response.Body)
59 | if err != nil {
60 | panic(err)
61 | }
62 | // Commented out, because output is too long
63 | //fmt.Print("ASPSP list: ")
64 | //fmt.Println(string(body))
65 |
66 | // Initiating a payment
67 | paymentBody := map[string]interface{}{
68 | "payment_type": "SEPA",
69 | "payment_request": map[string]interface{}{
70 | "credit_transfer_transaction": []interface{}{
71 | map[string]interface{}{
72 | "beneficiary": map[string]interface{}{
73 | "creditor_account": map[string]string{
74 | "scheme_name": "IBAN",
75 | "identification": "FI7473834510057469",
76 | },
77 | "creditor": map[string]interface{}{
78 | "name": "John Doe",
79 | },
80 | },
81 | "instructed_amount": map[string]string{
82 | "currency": "EUR",
83 | "amount": "100.00",
84 | },
85 | "reference_number": "123",
86 | },
87 | },
88 | },
89 | "aspsp": map[string]string{
90 | "name": ASPSPName,
91 | "country": ASPSPCountry,
92 | },
93 | "state": uuid.NewString(),
94 | "redirect_url": config.RedirectUrl,
95 | "psu_type": "personal",
96 | }
97 | paymentBodyJson, err := json.Marshal(paymentBody)
98 | if err != nil {
99 | panic(err)
100 | }
101 | request, err = http.NewRequest("POST", APIOrigin+"/payments", bytes.NewBuffer(paymentBodyJson))
102 | if err != nil {
103 | panic(err)
104 | }
105 | request.Header.Add("Authorization", "Bearer "+jwt)
106 | response, err = client.Do(request)
107 | if err != nil {
108 | panic(err)
109 | }
110 | defer func(Body io.ReadCloser) {
111 | err := Body.Close()
112 | if err != nil {
113 | panic(err)
114 | }
115 | }(response.Body)
116 |
117 | body, err = io.ReadAll(response.Body)
118 | if err != nil {
119 | panic(err)
120 | }
121 | responseJson := struct {
122 | Url string `json:"url"`
123 | PaymentId string `json:"payment_id"`
124 | }{}
125 | err = json.Unmarshal(body, &responseJson)
126 | if err != nil {
127 | panic(err)
128 | }
129 |
130 | fmt.Println("Use following credentials to authenticate: customera / 12345678")
131 | fmt.Print("To authenticate open URL: ")
132 | fmt.Println(responseJson.Url)
133 |
134 | // This request can be called multiple times to check the status of the payment
135 | paymentStatusRequest, err := http.NewRequest("GET", APIOrigin+"/payments/"+responseJson.PaymentId, nil)
136 | if err != nil {
137 | panic(err)
138 | }
139 | paymentStatusRequest.Header.Add("Authorization", "Bearer "+jwt)
140 | paymentStatusResponse, err := client.Do(paymentStatusRequest)
141 | if err != nil {
142 | panic(err)
143 | }
144 | defer func(Body io.ReadCloser) {
145 | err := Body.Close()
146 | if err != nil {
147 | panic(err)
148 | }
149 | }(paymentStatusResponse.Body)
150 | paymentStatusBody, err := io.ReadAll(paymentStatusResponse.Body)
151 | if err != nil {
152 | panic(err)
153 | }
154 | fmt.Print("Payment status: ")
155 | fmt.Println(string(paymentStatusBody))
156 | }
157 |
--------------------------------------------------------------------------------
/ruby_example/lib/account_information.rb:
--------------------------------------------------------------------------------
1 | require 'bundler/setup'
2 |
3 | require 'faraday'
4 | require 'json'
5 | require 'jwt'
6 | require 'pp'
7 | require 'time'
8 |
9 | def main
10 | # Reading and parsing config file
11 | config_file = File.read("../config.json")
12 | config = JSON.parse(config_file)
13 |
14 | # Loading RSA private key
15 | key_path = "../" + config["keyPath"]
16 | rsa_key = OpenSSL::PKey::RSA.new(File.read(key_path))
17 |
18 | # Creating JWT
19 | iat = Time.now.to_i
20 | jwt_header = { typ: "JWT", alg: "RS256", kid: config["applicationId"] }
21 | jwt_body = { iss: "enablebanking.com", aud: "api.enablebanking.com", iat: iat, exp: iat + 3600 }
22 | jwt = JWT.encode(jwt_body, rsa_key, 'RS256', jwt_header)
23 |
24 | # Requesting application details
25 | r = Faraday.get("https://api.enablebanking.com/application", nil, "Authorization" => "Bearer #{jwt}")
26 | if r.status == 200
27 | app = JSON.parse(r.body)
28 | puts "Application details:"
29 | puts pp(app)
30 | else
31 | puts "Error response #{r.status}:", r.body
32 | return
33 | end
34 |
35 | # Requesting available ASPSPs
36 | r = Faraday.get("https://api.enablebanking.com/aspsps", nil, "Authorization" => "Bearer #{jwt}")
37 | if r.status == 200
38 | aspsps = JSON.parse(r.body)["aspsps"]
39 | puts "Available ASPSPs:"
40 | puts pp(aspsps)
41 | else
42 | puts "Error response #{r.status}:", r.body
43 | return
44 | end
45 |
46 | # Starting authorization"
47 | valid_until = Time.now + 2*7*24*60*60 # 2 weeks
48 | body = {
49 | access: { valid_until: valid_until.utc.iso8601 },
50 | aspsp: { name: "Nordea", country: "FI" }, # { name: aspsps[0]["name"], country: aspsps[0]["country"] },
51 | state: "random",
52 | redirect_url: app["redirect_urls"][0]
53 | }
54 | headers = {
55 | "Content-Type" => "application/json",
56 | "Authorization" => "Bearer #{jwt}"
57 | }
58 | r = Faraday.post("https://api.enablebanking.com/auth", JSON.dump(body), headers)
59 | if r.status == 200
60 | auth_url = JSON.parse(r.body)["url"]
61 | puts "To authenticate open URL #{auth_url}"
62 | else
63 | puts "Error response #{r.status}:", r.body
64 | return
65 | end
66 |
67 | # Reading auth code and creating user session
68 | puts "Enter value of code parameter from the URL you were redirected to:"
69 | auth_code = nil
70 | ARGF.each_line do |line|
71 | auth_code = line.gsub(/\s/, "")
72 | break
73 | end
74 | body = { code: auth_code }
75 | r = Faraday.post("https://api.enablebanking.com/sessions", JSON.dump(body), headers)
76 | if r.status == 200
77 | session = JSON.parse(r.body)
78 | puts "New user session has been created:"
79 | puts pp(session)
80 | else
81 | puts "Error response #{r.status}:", r.body
82 | return
83 | end
84 |
85 | if session["accounts"].length() == 0
86 | puts "No accounts available"
87 | return
88 | end
89 |
90 | # Retrieving account balances
91 | account_uid = session["accounts"][0]["uid"]
92 | r = Faraday.get(
93 | "https://api.enablebanking.com/accounts/#{account_uid}/balances",
94 | nil,
95 | "Authorization" => "Bearer #{jwt}"
96 | )
97 | if r.status == 200
98 | balances = JSON.parse(r.body)["balances"]
99 | puts "Balances:"
100 | puts balances
101 | else
102 | puts "Error response #{r.status}:", r.body
103 | return
104 | end
105 |
106 | # Retrieving account transactions (since yesterday)
107 | continuation_key = nil
108 | loop do
109 | query = { date_from: Date.today.prev_day.iso8601 }
110 | if continuation_key
111 | query["continuation_key"] = continuation_key
112 | end
113 | r = Faraday.get(
114 | "https://api.enablebanking.com/accounts/#{account_uid}/transactions",
115 | query,
116 | "Authorization" => "Bearer #{jwt}"
117 | )
118 | if r.status == 200
119 | resp_data = JSON.parse(r.body)
120 | transactions = resp_data["transactions"]
121 | puts "Transactions:"
122 | puts transactions
123 | if resp_data.has_key?("continuation_key") and resp_data["continuation_key"]
124 | continuation_key = resp_data["continuation_key"]
125 | puts "Going to fetch more transaction with continuation key #{continuation_key}"
126 | else
127 | puts "No continuation key. All transactions were fetched"
128 | break
129 | end
130 | else
131 | puts "Error response #{r.status}:", r.body
132 | return
133 | end
134 | end
135 | puts "All done!"
136 | end
137 |
138 | if __FILE__ == $PROGRAM_NAME
139 | main
140 | end
141 |
--------------------------------------------------------------------------------
/php_example/enablebanking.php:
--------------------------------------------------------------------------------
1 | curl_exec($ch), 'status' => curl_getinfo($ch, CURLINFO_HTTP_CODE)];
24 | }
25 |
26 | // Reading and parsing config file
27 | $config_file = file_get_contents(dirname(__FILE__) . '/../config.json');
28 | $config = json_decode($config_file);
29 |
30 | // Loading RSA private key
31 | $key_path = $config->keyPath;
32 | $rsa_key = file_get_contents(dirname(__FILE__) . '/../' . $key_path);
33 |
34 | // Creating JWT
35 | $jwt_header = [ 'typ' => 'JWT', 'alg' => 'RS256', 'kid' => $config->applicationId ];
36 | $payload = [
37 | 'iss' => 'enablebanking.com',
38 | 'aud' => 'api.enablebanking.com',
39 | 'iat' => time(),
40 | 'exp' => time() + 3600
41 | ];
42 | $jwt = JWT::encode($payload, $rsa_key, 'RS256', $config->applicationId);
43 |
44 | $headers = [
45 | 'Authorization: Bearer ' . $jwt,
46 | 'Content-Type: application/json',
47 | ];
48 |
49 | // Requesting application details
50 | $r = request('https://api.enablebanking.com/application', $headers);
51 | if($r['status'] === 200)
52 | {
53 | $app = json_decode($r['body']);
54 | echo 'Application details:';
55 | print_r($app);
56 | } else {
57 | exit('Error response #' . $r['status'] . ':' . $r['body']);
58 | }
59 |
60 | // Requesting available ASPSPs
61 | $r = request('https://api.enablebanking.com/aspsps', $headers);
62 | if($r['status'] === 200)
63 | {
64 | $aspsps = json_decode($r['body']);
65 | echo 'Available ASPSPs:';
66 | print_r($aspsps);
67 | } else {
68 | exit('Error response #' . $r['status'] . ':' . $r['body']);
69 | }
70 |
71 | // Starting authorization
72 | $valid_until = time() + 2 * 7 * 24 * 60 * 60;
73 | $body = [
74 | 'access' => [ 'valid_until' => date('c', $valid_until) ],
75 | 'aspsp' => [ 'name' => 'Danske Bank', 'country' => 'FI' ], // { name: aspsps[0]['name'], country: aspsps[0]['country'] },
76 | 'state' => 'random',
77 | 'redirect_url' => $app->redirect_urls[0],
78 | 'psu_type' => 'personal'
79 | ];
80 |
81 | $r = request('https://api.enablebanking.com/auth', $headers, json_encode($body));
82 | if($r['status'] == 200)
83 | {
84 | $auth_url = json_decode($r['body'])->url;
85 | echo 'To authenticate open URL ' . $auth_url . PHP_EOL;
86 | } else {
87 | exit('Error response #' . $r['status'] . ':' . $r['body']);
88 | }
89 |
90 | // Reading auth code and creating user session
91 | $auth_code = readline('Enter value of code parameter from the URL you were redirected to: ');
92 |
93 | $body = json_encode([ 'code' => $auth_code ]);
94 | $r = request('https://api.enablebanking.com/sessions', $headers, $body);
95 | if($r['status'] === 200)
96 | {
97 | $session = json_decode($r['body']);
98 | echo 'New user session has been created:';
99 | print_r($session);
100 | } else {
101 | exit('Error response #' . $r['status'] . ':' . $r['body']);
102 | }
103 | $account_uid = $session->accounts[0]->uid;
104 |
105 | // Retrieving account balances
106 | $r = request('https://api.enablebanking.com/accounts/' . $account_uid . '/balances', $headers);
107 | if($r['status'] === 200)
108 | {
109 | $balances = json_decode($r['body'])->balances;
110 | echo 'Balances: ';
111 | print_r($balances);
112 | } else {
113 | exit('Error response #' . $r['status'] . ':' . $r['body']);
114 | }
115 |
116 | // Retrieving account transactions (since yesterday)
117 | $continuation_key = null;
118 | do
119 | {
120 | $params = '?date_from=' . date('Y-m-d', strtotime('-1 day', time()));
121 | if($continuation_key)
122 | {
123 | $params .= '&continuation_key=' . $continuation_key;
124 | }
125 | $r = request('https://api.enablebanking.com/accounts/' . $account_uid . '/transactions' . $params, $headers);
126 | if($r['status'] === 200)
127 | {
128 | $rest_data = json_decode($r['body']);
129 | $transactions = $rest_data->transactions;
130 | echo 'Transactions:';
131 | print_r($transactions);
132 | if(isset($rest_data->continuation_key) && $rest_data->continuation_key)
133 | {
134 | $continuation_key = $rest_data->continuation_key;
135 | echo 'Going to fetch more transaction with continaution key ' . $continuation_key;
136 | } else {
137 | echo 'No continuation key. All transactions were fetched' . PHP_EOL;
138 | break;
139 | }
140 | } else {
141 | exit('Error response #' . $r['status'] . ':' . $r['body']);
142 | }
143 | }
144 | while(true);
145 |
--------------------------------------------------------------------------------
/python_example/account_information.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 | import sys
4 | import uuid
5 | from datetime import datetime, timezone, timedelta
6 | from pprint import pprint
7 | from urllib.parse import urlparse, parse_qs
8 |
9 | import requests
10 | import jwt as pyjwt
11 |
12 |
13 | API_ORIGIN = "https://api.enablebanking.com"
14 | ASPSP_NAME = "Nordea"
15 | ASPSP_COUNTRY = "FI"
16 |
17 |
18 | def main():
19 | file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "config.json")
20 | with open(file_path, "r") as f:
21 | config = json.load(f)
22 | iat = int(datetime.now().timestamp())
23 | jwt_body = {
24 | "iss": "enablebanking.com",
25 | "aud": "api.enablebanking.com",
26 | "iat": iat,
27 | "exp": iat + 3600,
28 | }
29 | jwt = pyjwt.encode(
30 | jwt_body,
31 | open(os.path.join('..', config["keyPath"]), "rb").read(),
32 | algorithm="RS256",
33 | headers={"kid": config["applicationId"]},
34 | )
35 | print(jwt)
36 |
37 | base_headers = {"Authorization": f"Bearer {jwt}"}
38 |
39 | # Requesting application details
40 | r = requests.get(f"{API_ORIGIN}/application", headers=base_headers)
41 | if r.status_code == 200:
42 | app = r.json()
43 | print("Application details:")
44 | pprint(app)
45 | else:
46 | print(f"Error response {r.status_code}:", r.text)
47 | return
48 |
49 | # Requesting available ASPSPs
50 | r = requests.get(f"{API_ORIGIN}/aspsps", headers=base_headers)
51 | if r.status_code == 200:
52 | print("Available ASPSPs:")
53 | pprint(r.json()["aspsps"])
54 | else:
55 | print(f"Error response {r.status_code}:", r.text)
56 | return
57 |
58 | # Starting authorization
59 | body = {
60 | "access": {
61 | "valid_until": (datetime.now(timezone.utc) + timedelta(days=10)).isoformat()
62 | },
63 | "aspsp": {"name": ASPSP_NAME, "country": ASPSP_COUNTRY},
64 | "state": str(uuid.uuid4()),
65 | "redirect_url": app["redirect_urls"][0],
66 | "psu_type": "personal",
67 | }
68 | r = requests.post(f"{API_ORIGIN}/auth", json=body, headers=base_headers)
69 | if r.status_code == 200:
70 | auth_url = r.json()["url"]
71 | print(f"To authenticate open URL {auth_url}")
72 | else:
73 | print(f"Error response {r.status_code}:", r.text)
74 | return
75 |
76 | # Reading auth code and creating user session
77 | redirected_url = input("Paste here the URL you have been redirected to: ")
78 | auth_code = parse_qs(urlparse(redirected_url).query)["code"][0]
79 | r = requests.post(f"{API_ORIGIN}/sessions", json={"code": auth_code}, headers=base_headers)
80 | if r.status_code == 200:
81 | session = r.json()
82 | print("New user session has been created:")
83 | pprint(session)
84 | else:
85 | print(f"Error response {r.status_code}:", r.text)
86 | return
87 |
88 | # Fetching session details again
89 | session_id = session["session_id"]
90 | r = requests.get(f"{API_ORIGIN}/sessions/{session_id}", headers=base_headers)
91 | if r.status_code == 200:
92 | print("Stored session data:")
93 | pprint(r.json())
94 | else:
95 | print(f"Error response {r.status_code}:", r.text)
96 | return
97 |
98 | # Using the first available account for the following API calls
99 | account_uid = session["accounts"][0]["uid"]
100 |
101 | # Retrieving account balances
102 | r = requests.get(f"{API_ORIGIN}/accounts/{account_uid}/balances", headers=base_headers)
103 | if r.status_code == 200:
104 | print("Balances:")
105 | pprint(r.json())
106 | else:
107 | print(f"Error response {r.status_code}:", r.text)
108 | return
109 |
110 | # Retrieving account transactions (since 90 days ago)
111 | query = {
112 | "date_from": (datetime.now(timezone.utc) - timedelta(days=90)).date().isoformat(),
113 | }
114 | continuation_key = None
115 | while True:
116 | if continuation_key:
117 | query["continuation_key"] = continuation_key
118 | r = requests.get(
119 | f"{API_ORIGIN}/accounts/{account_uid}/transactions",
120 | params=query,
121 | headers=base_headers,
122 | )
123 | if r.status_code == 200:
124 | resp_data = r.json()
125 | print("Transactions:")
126 | pprint(resp_data["transactions"])
127 | continuation_key = resp_data.get("continuation_key")
128 | if not continuation_key:
129 | print("No continuation key. All transactions were fetched")
130 | break
131 | print(f"Going to fetch more transactions with continuation key {continuation_key}")
132 | else:
133 | print(f"Error response {r.status_code}:", r.text)
134 | return
135 |
136 | print("All done!")
137 |
138 |
139 | if __name__ == "__main__":
140 | main()
141 |
--------------------------------------------------------------------------------
/go_example/ais.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "fmt"
7 | "github.com/google/uuid"
8 | "io"
9 | "net/http"
10 | "net/url"
11 | "os"
12 | "time"
13 | )
14 |
15 | const configPath = "../config.json"
16 | const APIOrigin = "https://api.enablebanking.com"
17 | const ASPSPName = "Nordea"
18 | const ASPSPCountry = "FI"
19 |
20 | func main() {
21 | config := Config{}
22 | configFile, err := os.ReadFile(configPath)
23 | err = json.Unmarshal(configFile, &config)
24 | if err != nil {
25 | panic(err)
26 | }
27 | jwt, err := getJwt(config.KeyPath, config.ApplicationId)
28 | if err != nil {
29 | panic(err)
30 | }
31 |
32 | client := &http.Client{}
33 | request, err := http.NewRequest("GET", APIOrigin+"/application", nil)
34 | if err != nil {
35 | panic(err)
36 | }
37 | request.Header.Add("Authorization", "Bearer "+jwt)
38 | response, err := client.Do(request)
39 | if err != nil {
40 | panic(err)
41 | }
42 | defer response.Body.Close()
43 | body, err := io.ReadAll(response.Body)
44 | if err != nil {
45 | panic(err)
46 | }
47 | fmt.Print("Application details: ")
48 | fmt.Println(string(body))
49 |
50 | request, err = http.NewRequest("GET", APIOrigin+"/aspsps", nil)
51 | if err != nil {
52 | panic(err)
53 | }
54 | request.Header.Add("Authorization", "Bearer "+jwt)
55 | response, err = client.Do(request)
56 | if err != nil {
57 | panic(err)
58 | }
59 | defer response.Body.Close()
60 | body, err = io.ReadAll(response.Body)
61 | if err != nil {
62 | panic(err)
63 | }
64 | // Commented out, because output is too long
65 | //fmt.Print("ASPSP list: ")
66 | //fmt.Println(string(body))
67 |
68 | // Starting authorization
69 | validUntil := (time.Now().AddDate(0, 0, 1)).Format(time.RFC3339)
70 | startAuthRequestBody := AuthorizationBody{
71 | Access: Access{
72 | ValidUntil: validUntil,
73 | },
74 | Aspsp: Aspsp{
75 | Name: ASPSPName,
76 | Country: ASPSPCountry,
77 | },
78 | State: uuid.NewString(),
79 | RedirectUrl: config.RedirectUrl,
80 | PsuType: "personal",
81 | }
82 | jsonRequestBody, err := json.Marshal(startAuthRequestBody)
83 | if err != nil {
84 | panic(err)
85 | }
86 | request, err = http.NewRequest("POST", APIOrigin+"/auth", bytes.NewBuffer(jsonRequestBody))
87 | if err != nil {
88 | panic(err)
89 | }
90 | request.Header.Add("Authorization", "Bearer "+jwt)
91 | request.Header.Add("Content-Type", "application/json")
92 | response, err = client.Do(request)
93 | if err != nil {
94 | panic(err)
95 | }
96 | defer response.Body.Close()
97 | body, err = io.ReadAll(response.Body)
98 | if err != nil {
99 | panic(err)
100 | }
101 | fmt.Print("To authenticate open URL: ")
102 | responseJson := struct {
103 | Url string `json:"url"`
104 | }{}
105 | err = json.Unmarshal(body, &responseJson)
106 | if err != nil {
107 | panic(err)
108 | }
109 | fmt.Println(responseJson.Url)
110 |
111 | fmt.Println("Paste here the URL you have been redirected to: ")
112 | var redirectUrlString string
113 | fmt.Scanln(&redirectUrlString)
114 | redirectUrl, err := url.Parse(redirectUrlString)
115 | if err != nil {
116 | panic(err)
117 | }
118 | authorizationCode := redirectUrl.Query().Get("code")
119 | authorizeSessionRequestBody := AuthorizeSessionBody{
120 | Code: authorizationCode,
121 | }
122 | jsonRequestBody, err = json.Marshal(authorizeSessionRequestBody)
123 | if err != nil {
124 | panic(err)
125 | }
126 | request, err = http.NewRequest("POST", APIOrigin+"/sessions", bytes.NewBuffer(jsonRequestBody))
127 | if err != nil {
128 | panic(err)
129 | }
130 | request.Header.Add("Authorization", "Bearer "+jwt)
131 | request.Header.Add("Content-Type", "application/json")
132 | response, err = client.Do(request)
133 | if err != nil {
134 | panic(err)
135 | }
136 | defer response.Body.Close()
137 | body, err = io.ReadAll(response.Body)
138 | if err != nil {
139 | panic(err)
140 | }
141 | authorizeSessionResponse := AuthorizedSession{}
142 | err = json.Unmarshal(body, &authorizeSessionResponse)
143 | fmt.Printf("Session id: %s\n", authorizeSessionResponse.SessionId)
144 | accountId := authorizeSessionResponse.Accounts[0].Uid
145 |
146 | // Get account balances
147 | request, err = http.NewRequest("GET", APIOrigin+"/accounts/"+accountId+"/balances", nil)
148 | if err != nil {
149 | panic(err)
150 | }
151 | request.Header.Add("Authorization", "Bearer "+jwt)
152 | request.Header.Add("Content-Type", "application/json")
153 | response, err = client.Do(request)
154 | if err != nil {
155 | panic(err)
156 | }
157 | defer response.Body.Close()
158 | body, err = io.ReadAll(response.Body)
159 | if err != nil {
160 | panic(err)
161 | }
162 | fmt.Print("Account balances: ")
163 | fmt.Println(string(body))
164 |
165 | // Get account transactions
166 | request, err = http.NewRequest("GET", APIOrigin+"/accounts/"+accountId+"/transactions", nil)
167 | if err != nil {
168 | panic(err)
169 | }
170 | request.Header.Add("Authorization", "Bearer "+jwt)
171 | request.Header.Add("Content-Type", "application/json")
172 | response, err = client.Do(request)
173 | if err != nil {
174 | panic(err)
175 | }
176 | defer response.Body.Close()
177 | body, err = io.ReadAll(response.Body)
178 | if err != nil {
179 | panic(err)
180 | }
181 | fmt.Print("Account transactions: ")
182 | fmt.Println(string(body))
183 | }
184 |
--------------------------------------------------------------------------------
/cs_example/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.IdentityModel.Tokens.Jwt;
5 | using System.Net.Http;
6 | using System.Net.Http.Headers;
7 | using System.Security.Claims;
8 | using System.Security.Cryptography;
9 | using System.Text;
10 | using System.Text.Json;
11 | using System.Threading.Tasks;
12 | using System.Web;
13 |
14 | using Microsoft.IdentityModel.Tokens;
15 |
16 | namespace cs_example
17 | {
18 | class Program
19 | {
20 | private static readonly HttpClient client = new HttpClient();
21 | private static readonly string apiOrigin = "https://api.enablebanking.com";
22 | private static readonly string jwtAudience = "api.enablebanking.com";
23 | private static readonly string jwtIssuer = "enablebanking.com";
24 |
25 | private static string authRedirectUrl = null;
26 |
27 | static string CreateToken(string keyPath, string appKid)
28 | {
29 | using RSA rsa = RSA.Create();
30 | rsa.ImportFromPem(File.ReadAllText(keyPath));
31 |
32 | var signingCredentials = new SigningCredentials(new RsaSecurityKey(rsa), SecurityAlgorithms.RsaSha256)
33 | {
34 | CryptoProviderFactory = new CryptoProviderFactory { CacheSignatureProviders = false }
35 | };
36 |
37 | var now = DateTime.Now;
38 | var unixTimeSeconds = new DateTimeOffset(now).ToUnixTimeSeconds();
39 |
40 | var jwt = new JwtSecurityToken(
41 | audience: jwtAudience,
42 | issuer: jwtIssuer,
43 | claims: new Claim[] {
44 | new Claim(JwtRegisteredClaimNames.Iat, unixTimeSeconds.ToString(), ClaimValueTypes.Integer64)
45 | },
46 | expires: now.AddMinutes(30),
47 | signingCredentials: signingCredentials
48 | );
49 | jwt.Header.Add("kid", appKid);
50 | return new JwtSecurityTokenHandler().WriteToken(jwt);
51 | }
52 |
53 | static async Task Main(string[] args)
54 | {
55 | // Reading config
56 | Dictionary config = JsonSerializer.Deserialize>(
57 | File.ReadAllText("../config.json")
58 | );
59 | string keyPath = "../" + config["keyPath"];
60 | string appKid = config["applicationId"];
61 |
62 | // Creating JWT and setting to client
63 | string jwt = CreateToken(keyPath, appKid);
64 | Console.WriteLine("Created application JwT:");
65 | Console.WriteLine(jwt);
66 | client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", jwt);
67 |
68 | // Requesting application details
69 | var r = await client.GetAsync(apiOrigin + "/application");
70 | if (r.IsSuccessStatusCode) {
71 | string json = await r.Content.ReadAsStringAsync();
72 | var app = JsonSerializer.Deserialize>(json);
73 | Console.WriteLine($"App name: {app["name"]}");
74 | Console.WriteLine($"App description: {app["description"]}");
75 | Console.WriteLine($"App redirect URLs:");
76 | foreach (var redirectUrl in app["redirect_urls"].EnumerateArray())
77 | {
78 | Console.WriteLine($"- {redirectUrl}");
79 | // setting for later use
80 | if (authRedirectUrl == null) {
81 | authRedirectUrl = redirectUrl.ToString();
82 | }
83 | }
84 | }
85 | else {
86 | Console.WriteLine($"Error response {r.StatusCode}:");
87 | Console.WriteLine(await r.Content.ReadAsStringAsync());
88 | return;
89 | }
90 |
91 | // Requesting available ASPSPs
92 | r = await client.GetAsync(apiOrigin + "/aspsps");
93 | if (r.IsSuccessStatusCode) {
94 | string json = await r.Content.ReadAsStringAsync();
95 | var data = JsonSerializer.Deserialize>(json);
96 | Console.WriteLine("Available ASPSPs:");
97 | foreach (var aspsp in data["aspsps"].EnumerateArray())
98 | {
99 | Console.WriteLine($"- {aspsp}");
100 | }
101 | }
102 | else {
103 | Console.WriteLine($"Error response {r.StatusCode}:");
104 | Console.WriteLine(await r.Content.ReadAsStringAsync());
105 | return;
106 | }
107 |
108 | // Starting authorization
109 | var body = new Dictionary() {
110 | { "access", new Dictionary() {
111 | { "valid_until", DateTime.UtcNow.AddDays(10).ToString("u") }
112 | }},
113 | { "aspsp", new Dictionary() {
114 | { "name", "Nordea" },
115 | { "country", "FI" }
116 | }},
117 | { "state", System.Guid.NewGuid().ToString() },
118 | { "redirect_url", authRedirectUrl },
119 | { "psu_type", "personal" }
120 | };
121 | Console.WriteLine();
122 | r = await client.PostAsync(apiOrigin + "/auth", new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json"));
123 | if (r.IsSuccessStatusCode) {
124 | string json = await r.Content.ReadAsStringAsync();
125 | var data = JsonSerializer.Deserialize>(json);
126 | Console.WriteLine($"To authenticate open URL {data["url"]}");
127 | }
128 | else {
129 | Console.WriteLine($"Error response {r.StatusCode}:");
130 | Console.WriteLine(await r.Content.ReadAsStringAsync());
131 | return;
132 | }
133 |
134 | // Reading auth code and creating user session
135 | Console.Write("Paste here the URL you have been redirected to: ");
136 | var redirectedUrl = Console.ReadLine();
137 | var code = HttpUtility.ParseQueryString(new Uri(redirectedUrl).Query)["code"];
138 | body = new Dictionary() {
139 | { "code", code }
140 | };
141 | r = await client.PostAsync(apiOrigin + "/sessions", new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json"));
142 | if (r.IsSuccessStatusCode) {
143 | string json = await r.Content.ReadAsStringAsync();
144 | var data = JsonSerializer.Deserialize>(json);
145 | Console.WriteLine($"New user session {data["session_id"]} has been created. The following accounts are available:");
146 | foreach (var account in data["accounts"].EnumerateArray())
147 | {
148 | Console.WriteLine($"- {account}");
149 | }
150 | }
151 | else {
152 | Console.WriteLine($"Error response {r.StatusCode}:");
153 | Console.WriteLine(await r.Content.ReadAsStringAsync());
154 | return;
155 | }
156 | }
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/postman_example/Enable Banking.postman_collection.json:
--------------------------------------------------------------------------------
1 | {
2 | "info": {
3 | "_postman_id": "06ac5292-89fe-4d5d-937a-3968e767a3ec",
4 | "name": "Enable Banking",
5 | "description": "This is collection of sample requests to api.enablebanking.com.\nEach request needs JWT token, which generates in Pre-request Script. For using this, add your RSA private key (variable name - 'private_key') and kid (variable name - 'kid') to collection Variables.\nGo to https://enablebanking.com/cp/applications and register your application.",
6 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
7 | "_exporter_id": "7166420"
8 | },
9 | "item": [
10 | {
11 | "name": "Requesting available ASPSPs",
12 | "request": {
13 | "method": "GET",
14 | "header": [
15 | {
16 | "key": "Authorization",
17 | "value": "Bearer {{jwt_signed}}",
18 | "type": "default"
19 | }
20 | ],
21 | "url": {
22 | "raw": "https://api.enablebanking.com/aspsps",
23 | "protocol": "https",
24 | "host": [
25 | "api",
26 | "enablebanking",
27 | "com"
28 | ],
29 | "path": [
30 | "aspsps"
31 | ]
32 | }
33 | },
34 | "response": []
35 | },
36 | {
37 | "name": "Requesting application details",
38 | "request": {
39 | "method": "GET",
40 | "header": [
41 | {
42 | "key": "Authorization",
43 | "value": "Bearer {{jwt_signed}}",
44 | "type": "default"
45 | }
46 | ],
47 | "url": {
48 | "raw": "https://api.enablebanking.com/application",
49 | "protocol": "https",
50 | "host": [
51 | "api",
52 | "enablebanking",
53 | "com"
54 | ],
55 | "path": [
56 | "application"
57 | ]
58 | }
59 | },
60 | "response": []
61 | },
62 | {
63 | "name": "Starting authorization",
64 | "request": {
65 | "method": "POST",
66 | "header": [
67 | {
68 | "key": "Authorization",
69 | "value": "Bearer {{jwt_signed}}",
70 | "type": "default"
71 | }
72 | ],
73 | "body": {
74 | "mode": "raw",
75 | "raw": "{\n \"access\": {\n \"valid_until\": \"2025-04-10T16:02:08.070557+00:00\"\n },\n \"aspsp\": {\n \"name\": \"Nordea\",\n \"country\": \"FI\"\n },\n \"state\": \"7226b0c8-e5bf-463c-acdc-a562e18224d1\",\n \"redirect_url\": \"http://localhost:8080/auth_callback\",\n \"psu_type\": \"personal\"\n}",
76 | "options": {
77 | "raw": {
78 | "language": "json"
79 | }
80 | }
81 | },
82 | "url": {
83 | "raw": "https://api.enablebanking.com/auth",
84 | "protocol": "https",
85 | "host": [
86 | "api",
87 | "enablebanking",
88 | "com"
89 | ],
90 | "path": [
91 | "auth"
92 | ]
93 | }
94 | },
95 | "response": []
96 | },
97 | {
98 | "name": "Reading auth code and creating user session",
99 | "request": {
100 | "method": "POST",
101 | "header": [
102 | {
103 | "key": "Authorization",
104 | "value": "Bearer {{jwt_signed}}",
105 | "type": "default"
106 | }
107 | ],
108 | "body": {
109 | "mode": "raw",
110 | "raw": "{\n \"code\": \"42519c92-85ef-4970-8615-daa56f809a9d\"\n}",
111 | "options": {
112 | "raw": {
113 | "language": "json"
114 | }
115 | }
116 | },
117 | "url": {
118 | "raw": "https://api.enablebanking.com/sessions",
119 | "protocol": "https",
120 | "host": [
121 | "api",
122 | "enablebanking",
123 | "com"
124 | ],
125 | "path": [
126 | "sessions"
127 | ]
128 | }
129 | },
130 | "response": []
131 | },
132 | {
133 | "name": "Fetching session details",
134 | "request": {
135 | "method": "GET",
136 | "header": [
137 | {
138 | "key": "Authorization",
139 | "value": "Bearer {{jwt_signed}}",
140 | "type": "default"
141 | }
142 | ],
143 | "url": {
144 | "raw": "https://api.enablebanking.com/sessions/a94c6f56-45d6-42fb-82bb-9dc079872fe8",
145 | "protocol": "https",
146 | "host": [
147 | "api",
148 | "enablebanking",
149 | "com"
150 | ],
151 | "path": [
152 | "sessions",
153 | "a94c6f56-45d6-42fb-82bb-9dc079872fe8"
154 | ]
155 | }
156 | },
157 | "response": []
158 | },
159 | {
160 | "name": "Retrieving account balances",
161 | "request": {
162 | "method": "GET",
163 | "header": [
164 | {
165 | "key": "Authorization",
166 | "value": "Bearer {{jwt_signed}}",
167 | "type": "default"
168 | }
169 | ],
170 | "url": {
171 | "raw": "https://api.enablebanking.com/accounts/14964e48-5a7d-4393-ad55-7636704233d1/balances",
172 | "protocol": "https",
173 | "host": [
174 | "api",
175 | "enablebanking",
176 | "com"
177 | ],
178 | "path": [
179 | "accounts",
180 | "14964e48-5a7d-4393-ad55-7636704233d1",
181 | "balances"
182 | ]
183 | }
184 | },
185 | "response": []
186 | },
187 | {
188 | "name": "Retrieving account transactions (longest period)",
189 | "request": {
190 | "method": "GET",
191 | "header": [
192 | {
193 | "key": "Authorization",
194 | "value": "Bearer {{jwt_signed}}",
195 | "type": "default"
196 | }
197 | ],
198 | "url": {
199 | "raw": "https://api.enablebanking.com/accounts/14964e48-5a7d-4393-ad55-7636704233d1/transactions",
200 | "protocol": "https",
201 | "host": [
202 | "api",
203 | "enablebanking",
204 | "com"
205 | ],
206 | "path": [
207 | "accounts",
208 | "14964e48-5a7d-4393-ad55-7636704233d1",
209 | "transactions"
210 | ]
211 | }
212 | },
213 | "response": []
214 | },
215 | {
216 | "name": "Starting payment",
217 | "request": {
218 | "method": "POST",
219 | "header": [
220 | {
221 | "key": "Authorization",
222 | "value": "Bearer {{jwt_signed}}",
223 | "type": "text"
224 | }
225 | ],
226 | "body": {
227 | "mode": "raw",
228 | "raw": "{\n \"aspsp\": {\n \"name\": \"Nordea\",\n \"country\": \"FI\"\n },\n \"state\": \"b463a960-9616-4df6-909f-f80884190c22\",\n \"redirect_url\": \"https://google.com/\",\n \"psu_type\": \"personal\",\n \"payment_type\": \"SEPA\",\n \"payment_request\": {\n \"credit_transfer_transaction\": [\n {\n \"instructed_amount\": {\n \"amount\": \"1.00\",\n \"currency\": \"EUR\"\n },\n \"beneficiary\": {\n \"creditor\": {\n \"name\": \"Tester\"\n },\n \"creditor_account\": {\n \"scheme_name\": \"IBAN\",\n \"identification\": \"FI2112345600000785\"\n }\n },\n \"remittance_information\": [\n \"testing\"\n ]\n }\n ]\n }\n}",
229 | "options": {
230 | "raw": {
231 | "language": "json"
232 | }
233 | }
234 | },
235 | "url": {
236 | "raw": "https://api.enablebanking.com/payments",
237 | "protocol": "https",
238 | "host": [
239 | "api",
240 | "enablebanking",
241 | "com"
242 | ],
243 | "path": [
244 | "payments"
245 | ]
246 | }
247 | },
248 | "response": [
249 | {
250 | "name": "SE Handelsbanken personal DOMESTIC (BANKID auth)",
251 | "originalRequest": {
252 | "method": "POST",
253 | "header": [
254 | {
255 | "key": "Authorization",
256 | "value": "Bearer {{jwt_signed}}",
257 | "type": "text"
258 | },
259 | {
260 | "key": "Content-Type",
261 | "value": "application/json",
262 | "type": "text"
263 | }
264 | ],
265 | "body": {
266 | "mode": "raw",
267 | "raw": "{\n \"aspsp\": {\n \"name\": \"Handelsbanken\",\n \"country\": \"SE\"\n },\n \"state\": \"b463a960-9616-4df6-909f-f80884190c22\",\n \"redirect_url\": \"http://localhost:8080/auth_callback\",\n \"psu_type\": \"personal\",\n \"auth_method\": \"BANKID\",\n \"payment_type\": \"DOMESTIC\",\n \"payment_request\": {\n // \"debtor_account\": {\n // \"scheme_name\": \"BBAN\",\n // \"identification\": \"889881138\"\n // },\n \"credit_transfer_transaction\": [\n {\n \"instructed_amount\": {\n \"amount\": \"1.00\",\n \"currency\": \"SEK\"\n },\n \"beneficiary\": {\n \"creditor\": {\n \"name\": \"Tester\"\n },\n \"creditor_account\": {\n \"scheme_name\": \"BBAN\",\n \"identification\": \"0139562\"\n },\n \"creditor_agent\": {\n \"clearing_system_member_id\": {\n \"member_id\": \"1371\"\n }\n }\n },\n \"remittance_information\": [\n \"testing\"\n ]\n }\n ]\n }\n}",
268 | "options": {
269 | "raw": {
270 | "language": "json"
271 | }
272 | }
273 | },
274 | "url": {
275 | "raw": "https://api.enablebanking.com/payments",
276 | "protocol": "https",
277 | "host": [
278 | "api",
279 | "enablebanking",
280 | "com"
281 | ],
282 | "path": [
283 | "payments"
284 | ]
285 | }
286 | },
287 | "_postman_previewlanguage": "Text",
288 | "header": [],
289 | "cookie": [],
290 | "body": "{\n \"payment_id\": \"075d85d2-1683-4b77-9582-1a99f1708146\",\n \"status\": \"RCVD\",\n \"url\": \"https://tilisy-sandbox.enablebanking.com/pis/start?payment_id=075d85d2-1683-4b77-9582-1a99f1708146\"\n}"
291 | },
292 | {
293 | "name": "SE Swedbank personal SEPA",
294 | "originalRequest": {
295 | "method": "POST",
296 | "header": [
297 | {
298 | "key": "Authorization",
299 | "value": "Bearer {{jwt_signed}}",
300 | "type": "text"
301 | }
302 | ],
303 | "body": {
304 | "mode": "raw",
305 | "raw": "{\n \"aspsp\": {\n \"name\": \"Swedbank\",\n \"country\": \"SE\"\n },\n \"state\": \"b463a960-9616-4df6-909f-f80884190c22\",\n \"redirect_url\": \"http://localhost:8080/auth_callback\",\n \"psu_type\": \"personal\",\n \"payment_type\": \"SEPA\",\n \"payment_request\": {\n \"charge_bearer\": \"SHAR\",\n \"payment_type_information\": {\n \"service_level\": \"SEPA\",\n \"instruction_priority\": \"NORM\"\n },\n // \"debtor_account\": {\n // \"scheme_name\": \"IBAN\",\n // \"identification\": \"SE4880000123459876543219\"\n // },\n \"credit_transfer_transaction\": [\n {\n \"instructed_amount\": {\n \"currency\": \"EUR\",\n \"amount\": \"1.00\"\n },\n \"beneficiary\": {\n \"creditor\": {\n \"name\": \"Tester\",\n \"postal_address\": {\n \"country\": \"FI\",\n \"department\": \"\",\n \"sub_department\": \"\",\n \"street_name\": \"\",\n \"building_number\": \"\",\n \"post_code\": \"\",\n \"town_name\": \"\",\n \"country_sub_division\": \"\",\n \"address_line\": [\n \"\"\n ]\n }\n },\n \"creditor_account\": {\n \"scheme_name\": \"IBAN\",\n \"identification\": \"FI2112345600000785\"\n }\n },\n \"remittance_information\": [\n \"testing\"\n ]\n }\n ]\n }\n}",
306 | "options": {
307 | "raw": {
308 | "language": "json"
309 | }
310 | }
311 | },
312 | "url": {
313 | "raw": "https://api.enablebanking.com/payments",
314 | "protocol": "https",
315 | "host": [
316 | "api",
317 | "enablebanking",
318 | "com"
319 | ],
320 | "path": [
321 | "payments"
322 | ]
323 | }
324 | },
325 | "_postman_previewlanguage": "Text",
326 | "header": [],
327 | "cookie": [],
328 | "body": "{\n \"payment_id\": \"16e568c4-a5b4-406e-a024-7c90515031ee\",\n \"status\": \"RCVD\",\n \"url\": \"https://tilisy-sandbox.enablebanking.com/pis/start?payment_id=16e568c4-a5b4-406e-a024-7c90515031ee\"\n}"
329 | }
330 | ]
331 | },
332 | {
333 | "name": "Fetching payment",
334 | "request": {
335 | "method": "GET",
336 | "header": [
337 | {
338 | "key": "Authorization",
339 | "value": "Bearer {{jwt_signed}}",
340 | "type": "text"
341 | }
342 | ],
343 | "url": {
344 | "raw": "https://api.enablebanking.com/payments/:paymentId",
345 | "protocol": "https",
346 | "host": [
347 | "api",
348 | "enablebanking",
349 | "com"
350 | ],
351 | "path": [
352 | "payments",
353 | ":paymentId"
354 | ],
355 | "variable": [
356 | {
357 | "key": "paymentId",
358 | "value": ""
359 | }
360 | ]
361 | }
362 | },
363 | "response": [
364 | {
365 | "name": "SE Handelsbanken personal DOMESTIC",
366 | "originalRequest": {
367 | "method": "GET",
368 | "header": [
369 | {
370 | "key": "Authorization",
371 | "value": "Bearer {{jwt_signed}}",
372 | "type": "text"
373 | }
374 | ],
375 | "url": {
376 | "raw": "https://api.enablebanking.com/payments/075d85d2-1683-4b77-9582-1a99f1708146",
377 | "protocol": "https",
378 | "host": [
379 | "api",
380 | "enablebanking",
381 | "com"
382 | ],
383 | "path": [
384 | "payments",
385 | "075d85d2-1683-4b77-9582-1a99f1708146"
386 | ]
387 | }
388 | },
389 | "_postman_previewlanguage": "Text",
390 | "header": [],
391 | "cookie": [],
392 | "body": "{\n \"payment_id\": \"075d85d2-1683-4b77-9582-1a99f1708146\",\n \"status\": \"ACCC\",\n \"payment_details\": {\n \"debtor\": {\n \"name\": \"SANDBOX-INDIVIDUAL-SE-1\"\n },\n \"debtor_account\": {\n \"identification\": \"SE3460000000000923450017\",\n \"scheme_name\": \"IBAN\"\n },\n \"debtor_currency\": \"SEK\",\n \"credit_transfer_transaction\": [\n {\n \"instructed_amount\": {\n \"currency\": \"SEK\",\n \"amount\": \"1.00\"\n },\n \"beneficiary\": {\n \"creditor_agent\": {\n \"clearing_system_member_id\": {\n \"member_id\": \"1371\"\n }\n },\n \"creditor\": {\n \"name\": \"Tester\"\n },\n \"creditor_account\": {\n \"identification\": \"0139562\",\n \"scheme_name\": \"BBAN\"\n }\n },\n \"remittance_information\": [\n \"testing\"\n ]\n }\n ]\n },\n \"final_status\": true\n}"
393 | },
394 | {
395 | "name": "SE Swedbank personal SEPA",
396 | "originalRequest": {
397 | "method": "GET",
398 | "header": [
399 | {
400 | "key": "Authorization",
401 | "value": "Bearer {{jwt_signed}}",
402 | "type": "text"
403 | }
404 | ],
405 | "url": {
406 | "raw": "https://api.enablebanking.com/payments/16e568c4-a5b4-406e-a024-7c90515031ee",
407 | "protocol": "https",
408 | "host": [
409 | "api",
410 | "enablebanking",
411 | "com"
412 | ],
413 | "path": [
414 | "payments",
415 | "16e568c4-a5b4-406e-a024-7c90515031ee"
416 | ]
417 | }
418 | },
419 | "_postman_previewlanguage": "Text",
420 | "header": [],
421 | "cookie": [],
422 | "body": "{\n \"payment_id\": \"16e568c4-a5b4-406e-a024-7c90515031ee\",\n \"status\": \"ACTC\",\n \"payment_details\": {\n \"payment_type_information\": {\n \"instruction_priority\": \"NORM\",\n \"service_level\": \"SEPA\"\n },\n \"debtor\": {\n \"name\": \"John Snow\"\n },\n \"debtor_account\": {\n \"identification\": \"SE4880000123459876543219\",\n \"scheme_name\": \"IBAN\"\n },\n \"debtor_currency\": \"SEK\",\n \"charge_bearer\": \"SHAR\",\n \"credit_transfer_transaction\": [\n {\n \"instructed_amount\": {\n \"currency\": \"EUR\",\n \"amount\": \"1.00\"\n },\n \"beneficiary\": {\n \"creditor\": {\n \"name\": \"Tester\",\n \"postal_address\": {\n \"department\": \"\",\n \"sub_department\": \"\",\n \"street_name\": \"\",\n \"building_number\": \"\",\n \"post_code\": \"\",\n \"town_name\": \"\",\n \"country_sub_division\": \"\",\n \"country\": \"SE\",\n \"address_line\": [\n \"\"\n ]\n }\n },\n \"creditor_account\": {\n \"identification\": \"FI2112345600000785\",\n \"scheme_name\": \"IBAN\"\n }\n },\n \"remittance_information\": [\n \"testing\"\n ]\n }\n ]\n },\n \"final_status\": false\n}"
423 | }
424 | ]
425 | }
426 | ],
427 | "event": [
428 | {
429 | "listen": "prerequest",
430 | "script": {
431 | "type": "text/javascript",
432 | "exec": [
433 | "(() => {",
434 | " const kid = pm.collectionVariables.get(\"kid\") || '';",
435 | " const privateKey = pm.collectionVariables.get(\"private_key\") || '';",
436 | "",
437 | " if (!kid) {",
438 | " throw new Error(\"Collection variable 'kid' is not set. Please set it in the current collection variables.\");",
439 | " }",
440 | "",
441 | " if (!privateKey) {",
442 | " throw new Error(\"Collection variable 'private_key' is not set. Please set it in the current collection variables.\");",
443 | " }",
444 | "",
445 | " const header = {",
446 | " typ: 'JWT',",
447 | " alg: 'RS256',",
448 | " kid: kid",
449 | " };",
450 | "",
451 | " const currentTimestamp = Math.floor(Date.now() / 1000);",
452 | "",
453 | " const payload = {",
454 | " iss: 'enablebanking.com',",
455 | " aud: 'api.enablebanking.com',",
456 | " iat: currentTimestamp,",
457 | " exp: currentTimestamp + 300",
458 | " };",
459 | "",
460 | " function generateJwt() {",
461 | " try {",
462 | " this.navigator = {};",
463 | " this.window = {};",
464 | "",
465 | " const jsrsasignCode = pm.globals.get('jsrsasign-js');",
466 | " if (!jsrsasignCode) {",
467 | " throw new Error(\"jsrsasign-js global variable is empty or missing.\");",
468 | " }",
469 | " eval(jsrsasignCode);",
470 | "",
471 | " const token = KJUR.jws.JWS.sign(header.alg, JSON.stringify(header), JSON.stringify(payload), privateKey);",
472 | " pm.collectionVariables.set('jwt_signed', token);",
473 | " } catch (e) {",
474 | " throw new Error(\"Error while generating JWT: \" + e.message);",
475 | " }",
476 | " }",
477 | "",
478 | " if (pm.globals.has('jsrsasign-js')) {",
479 | " generateJwt();",
480 | " } else {",
481 | " pm.sendRequest('https://cdn.jsdelivr.net/npm/jsrsasign@10.5.23/lib/jsrsasign-all-min.js', (err, res) => {",
482 | " if (err) {",
483 | " throw new Error(\"Failed to load jsrsasign library: \" + err.message);",
484 | " }",
485 | " pm.globals.set('jsrsasign-js', res.text());",
486 | " generateJwt();",
487 | " });",
488 | " }",
489 | "})();",
490 | ""
491 | ]
492 | }
493 | },
494 | {
495 | "listen": "test",
496 | "script": {
497 | "type": "text/javascript",
498 | "exec": [
499 | ""
500 | ]
501 | }
502 | }
503 | ],
504 | "variable": [
505 | {
506 | "key": "private_key",
507 | "value": "",
508 | "type": "default"
509 | },
510 | {
511 | "key": "kid",
512 | "value": "",
513 | "type": "default"
514 | }
515 | ]
516 | }
517 |
--------------------------------------------------------------------------------