├── .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 | ![Postman screenshot](./postman-screenshot.png) 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 | --------------------------------------------------------------------------------