API Request Examples
Complete implementation examples for Open Banking API integration.
🔐 Authentication Examples
- OAuth Flow
- Token Refresh
- mTLS Setup
Complete OAuth 2.0 Implementation
Step 1: Generate PKCE Parameters
const crypto = require('crypto');
class PKCEChallenge {
constructor() {
this.verifier = this.generateVerifier();
this.challenge = this.generateChallenge(this.verifier);
}
generateVerifier() {
return crypto.randomBytes(32).toString('base64url');
}
generateChallenge(verifier) {
return crypto
.createHash('sha256')
.update(verifier)
.digest('base64url');
}
}
// Usage
const pkce = new PKCEChallenge();
console.log('Verifier:', pkce.verifier);
console.log('Challenge:', pkce.challenge);
Step 2: Build Authorization URL
function buildAuthorizationUrl(clientId, redirectUri, scope, pkceChallenge) {
const authUrl = new URL('https://open-banking-api.paysera.com/xs2a/berlin/1.3/oauth/authorize');
const params = {
response_type: 'code',
client_id: clientId,
redirect_uri: redirectUri,
scope: scope,
state: crypto.randomBytes(16).toString('hex'),
code_challenge: pkceChallenge,
code_challenge_method: 'S256'
};
Object.entries(params).forEach(([key, value]) => {
authUrl.searchParams.append(key, value);
});
return authUrl.toString();
}
// Example usage
const authUrl = buildAuthorizationUrl(
'your_client_id',
'https://yourapp.com/callback',
'accounts transactions payments',
pkce.challenge
);
// Redirect user to authUrl
window.location.href = authUrl;
Step 3: Token Exchange
const axios = require('axios');
async function exchangeCodeForToken(code, verifier, clientId, redirectUri) {
try {
const response = await axios.post(
'https://open-banking-api.paysera.com/xs2a/berlin/1.3/oauth/token',
new URLSearchParams({
grant_type: 'authorization_code',
code: code,
redirect_uri: redirectUri,
client_id: clientId,
code_verifier: verifier
}),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}
);
return {
accessToken: response.data.access_token,
refreshToken: response.data.refresh_token,
expiresIn: response.data.expires_in,
scope: response.data.scope
};
} catch (error) {
console.error('Token exchange failed:', error.response?.data);
throw error;
}
}
Refreshing Access Tokens
async function refreshAccessToken(refreshToken, clientId) {
const TokenManager = {
async refresh(refreshToken, clientId) {
const response = await axios.post(
'https://open-banking-api.paysera.com/xs2a/berlin/1.3/oauth/token',
new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_id: clientId
}),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}
);
return response.data;
},
isTokenExpired(expiryTime) {
return Date.now() >= expiryTime - 60000; // Refresh 1 minute before expiry
},
async ensureValidToken(tokenData, clientId) {
if (this.isTokenExpired(tokenData.expiryTime)) {
const newTokens = await this.refresh(tokenData.refreshToken, clientId);
return {
...newTokens,
expiryTime: Date.now() + (newTokens.expires_in * 1000)
};
}
return tokenData;
}
};
return TokenManager.refresh(refreshToken, clientId);
}
Certificate Configuration
const https = require('https');
const fs = require('fs');
class SecureApiClient {
constructor(certPath, keyPath, caPath) {
this.httpsAgent = new https.Agent({
cert: fs.readFileSync(certPath),
key: fs.readFileSync(keyPath),
ca: fs.readFileSync(caPath),
rejectUnauthorized: true,
keepAlive: true
});
}
async makeRequest(url, accessToken, method = 'GET', data = null) {
const options = {
method,
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
'X-Request-ID': crypto.randomUUID()
},
agent: this.httpsAgent
};
if (data) {
options.body = JSON.stringify(data);
}
const response = await fetch(url, options);
return response.json();
}
}
// Usage
const client = new SecureApiClient(
'./certificates/qwac-cert.pem',
'./certificates/qwac-key.pem',
'./certificates/ca-bundle.pem'
);
📊 Account Information (AIS)
- Get Accounts
- Get Balances
- Get Transactions
Retrieve Account List
// Define certificate paths (from previous examples):
// const certPath = './certs/cert.pem';
// const keyPath = './certs/key.pem';
// const caPath = './certs/ca.pem';
async function getAccounts(accessToken) {
const client = new SecureApiClient(certPath, keyPath, caPath);
try {
const accounts = await client.makeRequest(
'https://open-banking-api.paysera.com/xs2a/berlin/1.3/v1/accounts',
accessToken
);
// Process accounts
accounts.accounts.forEach(account => {
console.log(`Account: ${account.iban}`);
console.log(`Currency: ${account.currency}`);
console.log(`Product: ${account.product}`);
console.log(`Status: ${account.status}`);
console.log('---');
});
return accounts;
} catch (error) {
console.error('Failed to fetch accounts:', error);
throw error;
}
}
// Response example
{
"accounts": [
{
"resourceId": "acc_123",
"iban": "LT123456789012345678",
"currency": "EUR",
"product": "Current Account",
"status": "enabled",
"usage": "PRIV",
"_links": {
"balances": "/v1/accounts/acc_123/balances",
"transactions": "/v1/accounts/acc_123/transactions"
}
}
]
}
Retrieve Account Balances
// Define certificate paths (from previous examples):
// const certPath = './certs/cert.pem';
// const keyPath = './certs/key.pem';
// const caPath = './certs/ca.pem';
async function getAccountBalances(accountId, accessToken) {
const client = new SecureApiClient(certPath, keyPath, caPath);
const balances = await client.makeRequest(
`https://open-banking-api.paysera.com/xs2a/berlin/1.3/v1/accounts/${accountId}/balances`,
accessToken
);
// Format and display balances
const formatBalance = (balance) => {
const types = {
'closingBooked': '📘 Booked Balance',
'expected': '📊 Expected Balance',
'authorised': '✅ Authorised Balance',
'interimAvailable': '💰 Available Balance'
};
return {
type: types[balance.balanceType] || balance.balanceType,
amount: `${balance.balanceAmount.amount} ${balance.balanceAmount.currency}`,
date: balance.referenceDate || balance.lastChangeDateTime
};
};
balances.balances.forEach(balance => {
const formatted = formatBalance(balance);
console.log(`${formatted.type}: ${formatted.amount}`);
if (formatted.date) {
console.log(`As of: ${formatted.date}`);
}
});
return balances;
}
Retrieve Transaction History
import requests
from datetime import datetime, timedelta
import json
class TransactionFetcher:
def __init__(self, cert_path, key_path, base_url):
self.cert = (cert_path, key_path)
self.base_url = base_url
def get_transactions(self, account_id, access_token, days_back=90):
"""Fetch transactions for the specified period"""
date_from = (datetime.now() - timedelta(days=days_back)).strftime('%Y-%m-%d')
date_to = datetime.now().strftime('%Y-%m-%d')
params = {
'dateFrom': date_from,
'dateTo': date_to,
'bookingStatus': 'both' # both, booked, or pending
}
headers = {
'Authorization': f'Bearer {access_token}',
'Accept': 'application/json'
}
response = requests.get(
f'{self.base_url}/v1/accounts/{account_id}/transactions',
params=params,
headers=headers,
cert=self.cert
)
if response.status_code == 200:
return self.format_transactions(response.json())
else:
raise Exception(f'Error {response.status_code}: {response.text}')
def format_transactions(self, data):
"""Format transactions for display"""
transactions = []
for txn in data.get('transactions', {}).get('booked', []):
transactions.append({
'date': txn['bookingDate'],
'amount': f"{txn['transactionAmount']['amount']} {txn['transactionAmount']['currency']}",
'description': txn.get('remittanceInformationUnstructured', 'N/A'),
'creditor': txn.get('creditorName', 'N/A'),
'debtor': txn.get('debtorName', 'N/A'),
'status': 'Booked'
})
for txn in data.get('transactions', {}).get('pending', []):
transactions.append({
'date': txn.get('valueDate', 'Pending'),
'amount': f"{txn['transactionAmount']['amount']} {txn['transactionAmount']['currency']}",
'description': txn.get('remittanceInformationUnstructured', 'N/A'),
'status': 'Pending'
})
return transactions
# Usage
fetcher = TransactionFetcher(
'qwac-cert.pem',
'qwac-key.pem',
'https://open-banking-api.paysera.com'
)
transactions = fetcher.get_transactions('acc_123', access_token, days_back=30)
for txn in transactions:
print(f"{txn['date']}: {txn['amount']} - {txn['description']}")
💸 Payment Initiation (PIS)
- Single Payment
- Payment Status
- Cancel Payment
Initiate Single Payment
async function initiateSinglePayment(accessToken, paymentData) {
const crypto = require('crypto');
const payment = {
instructedAmount: {
currency: 'EUR',
amount: '100.00'
},
debtorAccount: {
iban: 'LT123456789012345678'
},
creditorAccount: {
iban: 'LT987654321098765432'
},
creditorName: 'John Doe',
remittanceInformationUnstructured: 'Invoice #12345'
};
// Generate request signature
const requestId = crypto.randomUUID();
const bodyString = JSON.stringify(payment);
const digest = crypto
.createHash('sha256')
.update(bodyString)
.digest('base64');
const response = await axios.post(
'https://open-banking-api.paysera.com/xs2a/berlin/1.3/v1/payments/sepa-credit-transfers',
payment,
{
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
'X-Request-ID': requestId,
'Digest': `SHA-256=${digest}`,
'PSU-IP-Address': '192.168.1.1' // User's IP
},
httpsAgent: new https.Agent({
cert: fs.readFileSync('./qwac-cert.pem'),
key: fs.readFileSync('./qwac-key.pem')
})
}
);
console.log('Payment initiated:', {
paymentId: response.data.paymentId,
status: response.data.transactionStatus,
scaRedirect: response.data._links.scaRedirect
});
return response.data;
}
Check Payment Status
async function checkPaymentStatus(paymentId, accessToken) {
const statuses = {
'RCVD': '📥 Received',
'PDNG': '⏳ Pending',
'ACCP': '✅ Accepted',
'ACSC': '✅ Accepted Settlement Completed',
'ACSP': '✅ Accepted Settlement in Process',
'ACTC': '✅ Accepted Technical Validation',
'ACWC': '✅ Accepted With Change',
'ACWP': '⏸️ Accepted Without Posting',
'PART': '⚠️ Partially Accepted',
'RJCT': '❌ Rejected',
'CANC': '🚫 Cancelled'
};
const response = await fetch(
`https://open-banking-api.paysera.com/xs2a/berlin/1.3/v1/payments/sepa-credit-transfers/${paymentId}`,
{
headers: {
'Authorization': `Bearer ${accessToken}`,
'Accept': 'application/json'
}
}
);
const payment = await response.json();
const statusInfo = {
id: payment.paymentId,
status: payment.transactionStatus,
statusDescription: statuses[payment.transactionStatus] || payment.transactionStatus,
amount: payment.instructedAmount,
creditor: payment.creditorName,
executionDate: payment.expectedExecutionDate || 'N/A'
};
console.log(`Payment ${statusInfo.id}:`);
console.log(`Status: ${statusInfo.statusDescription}`);
console.log(`Amount: ${statusInfo.amount.amount} ${statusInfo.amount.currency}`);
console.log(`To: ${statusInfo.creditor}`);
console.log(`Execution: ${statusInfo.executionDate}`);
return statusInfo;
}
Cancel Payment
// Requires: const axios = require('axios'); const crypto = require('crypto');
async function cancelPayment(paymentId, accessToken) {
try {
const response = await axios.delete(
`https://open-banking-api.paysera.com/xs2a/berlin/1.3/v1/payments/sepa-credit-transfers/${paymentId}`,
{
headers: {
'Authorization': `Bearer ${accessToken}`,
'X-Request-ID': crypto.randomUUID()
}
}
);
if (response.status === 204) {
console.log(`✅ Payment ${paymentId} cancelled successfully`);
return { success: true, paymentId };
}
} catch (error) {
if (error.response?.status === 405) {
console.error('❌ Payment cannot be cancelled (already processed)');
} else if (error.response?.status === 404) {
console.error('❌ Payment not found');
} else {
console.error('❌ Cancellation failed:', error.response?.data);
}
throw error;
}
}
🛠️ Utility Functions
Error Handling & Retry Logic
class ApiErrorHandler {
constructor(maxRetries = 3, backoffMs = 1000) {
this.maxRetries = maxRetries;
this.backoffMs = backoffMs;
}
async executeWithRetry(apiCall) {
let lastError;
for (let attempt = 0; attempt < this.maxRetries; attempt++) {
try {
return await apiCall();
} catch (error) {
lastError = error;
if (!this.isRetryable(error)) {
throw error;
}
if (attempt < this.maxRetries - 1) {
const delay = this.backoffMs * Math.pow(2, attempt);
console.log(`Retry attempt ${attempt + 1} after ${delay}ms`);
await this.sleep(delay);
}
}
}
throw lastError;
}
isRetryable(error) {
const retryableStatuses = [429, 500, 502, 503, 504];
return error.response && retryableStatuses.includes(error.response.status);
}
handleError(error) {
const errorMap = {
400: 'Bad Request - Check your request parameters',
401: 'Unauthorized - Token expired or invalid',
403: 'Forbidden - Insufficient permissions',
404: 'Not Found - Resource does not exist',
405: 'Method Not Allowed',
409: 'Conflict - Resource already exists',
429: 'Too Many Requests - Rate limit exceeded',
500: 'Internal Server Error',
503: 'Service Unavailable'
};
const status = error.response?.status;
const message = errorMap[status] || 'Unknown error occurred';
return {
status,
message,
details: error.response?.data,
timestamp: new Date().toISOString()
};
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// Usage
const errorHandler = new ApiErrorHandler();
try {
const result = await errorHandler.executeWithRetry(async () => {
return await getAccounts(accessToken);
});
} catch (error) {
const errorInfo = errorHandler.handleError(error);
console.error('API Error:', errorInfo);
}
Rate Limit Management
class RateLimitManager {
constructor() {
this.limits = new Map();
}
updateFromHeaders(headers) {
const limit = parseInt(headers['x-ratelimit-limit'] || 10);
const remaining = parseInt(headers['x-ratelimit-remaining'] || 10);
const reset = parseInt(headers['x-ratelimit-reset'] || Date.now() / 1000);
this.limits.set('current', {
limit,
remaining,
reset: new Date(reset * 1000),
percentage: (remaining / limit) * 100
});
return this.limits.get('current');
}
shouldThrottle() {
const current = this.limits.get('current');
if (!current) return false;
// Throttle if less than 20% remaining
return current.percentage < 20;
}
async waitForReset() {
const current = this.limits.get('current');
if (!current) return;
const now = new Date();
const resetTime = new Date(current.reset);
const waitMs = Math.max(0, resetTime - now);
if (waitMs > 0) {
console.log(`Rate limit reached. Waiting ${waitMs / 1000} seconds...`);
await new Promise(resolve => setTimeout(resolve, waitMs));
}
}
getStatus() {
const current = this.limits.get('current');
if (!current) return 'No rate limit info';
return `Rate Limit: ${current.remaining}/${current.limit} ` +
`(${current.percentage.toFixed(1)}%) - ` +
`Resets at ${current.reset.toLocaleTimeString()}`;
}
}
// Usage with API calls
const rateLimiter = new RateLimitManager();
async function makeRateLimitedRequest(url, accessToken) {
if (rateLimiter.shouldThrottle()) {
await rateLimiter.waitForReset();
}
const response = await fetch(url, {
headers: { 'Authorization': `Bearer ${accessToken}` }
});
rateLimiter.updateFromHeaders(response.headers);
console.log(rateLimiter.getStatus());
return response.json();
}
📝 Testing
cURL Test Commands
Test Certificate Connection
# Test mTLS connection
openssl s_client -connect open-banking-api.paysera.com:443 \
-cert qwac-cert.pem \
-key qwac-key.pem \
-CAfile ca-bundle.pem
# Get accounts with cURL
curl -X GET 'https://open-banking-api.paysera.com/xs2a/berlin/1.3/v1/accounts' \
--cert ./qwac-cert.pem \
--key ./qwac-key.pem \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'Accept: application/json'
# Initiate payment with cURL
curl -X POST 'https://open-banking-api.paysera.com/xs2a/berlin/1.3/v1/payments/sepa-credit-transfers' \
--cert ./qwac-cert.pem \
--key ./qwac-key.pem \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'Content-Type: application/json' \
-H 'X-Request-ID: 123e4567-e89b-12d3-a456-426614174000' \
-d '{
"instructedAmount": {"currency": "EUR", "amount": "10.00"},
"creditorAccount": {"iban": "LT123456789012345678"},
"creditorName": "Test Recipient"
}'
Resources
Pro Tips
- Always implement proper error handling and retry logic
- Cache OAuth tokens and refresh before expiry
- Monitor rate limits to avoid throttling
- Use connection pooling for better performance
- Implement comprehensive logging for debugging