Skip to main content

API Request Examples

Complete implementation examples for Open Banking API integration.

🔐 Authentication Examples

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;
}
}

📊 Account Information (AIS)

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"
}
}
]
}

💸 Payment Initiation (PIS)

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;
}

🛠️ 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