# @apiclient.xyz/bunq

an unofficial TypeScript api client for the bunq API

# readme.md for @apiclient.xyz/bunq

[![npm version](https://img.shields.io/npm/v/@apiclient.xyz/bunq.svg)](https://www.npmjs.com/package/@apiclient.xyz/bunq)
[![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

A powerful, type-safe TypeScript/JavaScript client for the bunq API with full feature coverage

## Table of Contents
- [Features](#features)
- [Stateless Architecture](#stateless-architecture-v400)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Core Examples](#core-examples)
  - [Account Management](#account-management)
  - [Making Payments](#making-payments)
  - [Payment Requests](#payment-requests)
  - [Draft Payments](#draft-payments-requires-approval)
  - [Card Management](#card-management)
  - [Webhooks](#webhooks)
  - [File Attachments](#file-attachments)
  - [Export Statements](#export-statements)
  - [Session Management](#stateless-session-management)
  - [User Management](#user-management)
- [Advanced Usage](#advanced-usage)
- [Security Best Practices](#security-best-practices)
- [Migration Guides](#migration-guide)
- [Testing](#testing)
- [Requirements](#requirements)
- [License](#license-and-legal-information)

## Features

### Core Banking Operations
- 💳 **Complete Account Management** - Access all account types (personal, business, joint)
- 💸 **Advanced Payment Processing** - Single payments, batch payments, scheduled payments
- 📊 **Transaction History** - Full transaction access with filtering and pagination
- 💰 **Payment Requests** - Send and manage payment requests with bunq.me integration
- 📝 **Draft Payments** - Create payments requiring approval

### Advanced Features
- 🔄 **Automatic Session Management** - Handles token refresh and session renewal
- 🔐 **Full Security Implementation** - Request signing and response verification
- 🎯 **Webhook Support** - Real-time notifications with signature verification
- 💳 **Card Management** - Full card control (activation, limits, blocking)
- 📎 **File Attachments** - Upload and attach files to payments
- 📑 **Statement Exports** - Export statements in multiple formats (PDF, CSV, MT940)
- 🔗 **OAuth Support** - Third-party app integration
- 🧪 **Sandbox Environment** - Full testing support

### Developer Experience
- 📘 **Full TypeScript Support** - Complete type definitions for all API responses
- 🏗️ **Builder Pattern APIs** - Intuitive payment and request builders
- ⚡ **Promise-based** - Modern async/await support throughout
- 🛡️ **Type Safety** - Compile-time type checking for all operations
- 📚 **Comprehensive Documentation** - Detailed examples for every feature
- 🔄 **Robust HTTP Client** - Built-in retry logic and rate limit handling

## Stateless Architecture (v4.0.0+)

Starting from version 4.0.0, this library is completely stateless. Session management is now entirely controlled by the consumer:

### Key Changes
- **No File Persistence** - The library no longer saves any state to disk
- **Session Data Export** - Full session data is returned for you to persist
- **Session Data Import** - Initialize with previously saved session data
- **Explicit Session Management** - You control when and how sessions are stored

### Benefits
- **Full Control** - Store sessions in your preferred storage (database, Redis, etc.)
- **Better for Microservices** - No shared state between instances
- **Improved Testing** - Predictable behavior with no hidden state
- **Enhanced Security** - You control where sensitive data is stored

### Migration from v3.x
If you're upgrading from v3.x, you'll need to handle session persistence yourself. See the [Stateless Session Management](#stateless-session-management) section for examples.

## Installation

```bash
npm install @apiclient.xyz/bunq
```

```bash
yarn add @apiclient.xyz/bunq
```

```bash
pnpm add @apiclient.xyz/bunq
```

## Quick Start

```typescript
import { BunqAccount } from '@apiclient.xyz/bunq';

// Initialize the client
const bunq = new BunqAccount({
  apiKey: 'your-api-key',
  deviceName: 'My App',
  environment: 'PRODUCTION' // or 'SANDBOX' for testing
});

// Initialize connection and get session data
const sessionData = await bunq.init();

// IMPORTANT: Save the session data for reuse
await saveSessionToDatabase(sessionData);

// Get your accounts
const { accounts, sessionData: updatedSession } = await bunq.getAccounts();
console.log(`Found ${accounts.length} accounts`);

// If session was refreshed, save the updated data
if (updatedSession) {
  await saveSessionToDatabase(updatedSession);
}

// Get recent transactions
const transactions = await accounts[0].getTransactions();
transactions.forEach(tx => {
  console.log(`${tx.created}: ${tx.amount.value} ${tx.amount.currency} - ${tx.description}`);
});

// Always cleanup when done
await bunq.stop();
```

## Core Examples

### Account Management

```typescript
// Get all accounts with details
const accounts = await bunq.getAccounts();

for (const account of accounts) {
  console.log(`Account: ${account.description}`);
  console.log(`Balance: ${account.balance.value} ${account.balance.currency}`);
  console.log(`IBAN: ${account.iban}`);
  
  // Get account-specific transactions
  const transactions = await account.getTransactions({
    count: 50,  // Last 50 transactions
    newer_id: false,
    older_id: false
  });
}

// Create a new monetary account (business accounts only)
const newAccount = await BunqMonetaryAccount.create(bunq, {
  currency: 'EUR',
  description: 'Savings Account',
  dailyLimit: '1000.00',
  overdraftLimit: '0.00'
});
```

### Making Payments

#### Simple Payment
```typescript
// Using the payment builder pattern
const payment = await BunqPayment.builder(bunq, account)
  .amount('25.00', 'EUR')
  .toIban('NL91ABNA0417164300', 'John Doe')
  .description('Birthday gift')
  .create();

console.log(`Payment created with ID: ${payment.id}`);
```

#### Payment with Custom Request ID (Idempotency)
```typescript
// Prevent duplicate payments with custom request IDs
const payment = await BunqPayment.builder(bunq, account)
  .amount('100.00', 'EUR')
  .toIban('NL91ABNA0417164300', 'Supplier B.V.')
  .description('Invoice #12345')
  .customRequestId('invoice-12345-payment')  // Prevents duplicate payments
  .create();
```

#### Batch Payments
```typescript
const batch = new BunqPaymentBatch(bunq);

// Create multiple payments in one API call
const batchId = await batch.create(account, [
  {
    amount: { value: '10.00', currency: 'EUR' },
    counterparty_alias: { 
      type: 'IBAN', 
      value: 'NL91ABNA0417164300', 
      name: 'Employee 1' 
    },
    description: 'Salary payment'
  },
  {
    amount: { value: '20.00', currency: 'EUR' },
    counterparty_alias: { 
      type: 'EMAIL', 
      value: 'freelancer@example.com', 
      name: 'Freelancer' 
    },
    description: 'Project payment'
  }
]);

// Check batch status
const batchDetails = await batch.get(account, batchId);
console.log(`Batch status: ${batchDetails.status}`);
console.log(`Total amount: ${batchDetails.total_amount.value}`);
```

#### Scheduled & Recurring Payments
```typescript
const scheduler = new BunqSchedulePayment(bunq);

// One-time scheduled payment
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);

const scheduledId = await BunqSchedulePayment.builder(bunq, account)
  .amount('50.00', 'EUR')
  .toIban('NL91ABNA0417164300', 'Landlord')
  .description('Rent payment')
  .scheduleOnce(tomorrow.toISOString())
  .create();

// Recurring monthly payment
const recurringId = await BunqSchedulePayment.builder(bunq, account)
  .amount('9.99', 'EUR')
  .toIban('NL91ABNA0417164300', 'Netflix B.V.')
  .description('Monthly subscription')
  .scheduleMonthly('2024-01-01T10:00:00Z', '2024-12-31T10:00:00Z')
  .create();

// List all scheduled payments
const schedules = await scheduler.list(account);

// Cancel a scheduled payment
await scheduler.delete(account, scheduledId);
```

### Payment Requests

```typescript
// Create a payment request
const request = await BunqRequestInquiry.builder(bunq, account)
  .amount('25.00', 'EUR')
  .fromEmail('friend@example.com', 'My Friend')
  .description('Lunch money')
  .allowBunqme()  // Generate bunq.me link
  .minimumAge(18)
  .create();

console.log(`Share this link: ${request.bunqmeShareUrl}`);

// List pending requests
const requests = await BunqRequestInquiry.list(bunq, account.id);
const pending = requests.filter(r => r.status === 'PENDING');

// Cancel a request
await request.update(requestId, { status: 'CANCELLED' });
```

### Draft Payments (Requires Approval)

```typescript
const draft = new BunqDraftPayment(bunq, account);

// Create a draft with multiple payments
const draftId = await draft.create({
  numberOfRequiredAccepts: 2,  // Requires 2 approvals
  entries: [
    {
      amount: { value: '1000.00', currency: 'EUR' },
      counterparty_alias: { 
        type: 'IBAN', 
        value: 'NL91ABNA0417164300', 
        name: 'Supplier A' 
      },
      description: 'Invoice payment'
    },
    {
      amount: { value: '2000.00', currency: 'EUR' },
      counterparty_alias: { 
        type: 'IBAN', 
        value: 'NL91ABNA0417164300', 
        name: 'Supplier B' 
      },
      description: 'Equipment purchase'
    }
  ]
});

// Approve the draft
await draft.accept();

// Or reject it
await draft.reject('Budget exceeded');
```

### Card Management

```typescript
// List all cards
const cards = await BunqCard.list(bunq);

// Get card details
for (const card of cards) {
  console.log(`Card: ${card.name_on_card}`);
  console.log(`Status: ${card.status}`);
  console.log(`Type: ${card.type}`)
  console.log(`Expiry: ${card.expiry_date}`);
  
  // Get card limits
  const limits = card.limit;
  console.log(`Daily limit: ${limits.daily_spent}`);
}

// Note: Card management methods like activation, PIN updates, and ordering
// new cards should be performed through the bunq app or API directly.
```

### Webhooks

```typescript
// Setup webhook server
const webhookServer = new BunqWebhookServer(bunq, {
  port: 3000,
  publicUrl: 'https://myapp.com/webhooks'
});

// Register event handlers
webhookServer.getHandler().onPayment((payment) => {
  console.log(`New payment: ${payment.amount.value} ${payment.amount.currency}`);
  console.log(`From: ${payment.counterparty_alias.display_name}`);
  console.log(`Description: ${payment.description}`);
  
  // Your business logic here
  updateDatabase(payment);
  sendNotification(payment);
});

webhookServer.getHandler().onRequest((request) => {
  console.log(`New payment request: ${request.amount_inquired.value}`);
  console.log(`From: ${request.user_alias_created.display_name}`);
});

webhookServer.getHandler().onCard((card) => {
  if (card.status === 'BLOCKED') {
    console.log(`Card blocked: ${card.name_on_card}`);
    alertSecurityTeam(card);
  }
});

// Start server and register with bunq
await webhookServer.start();
await webhookServer.register();

// Manual webhook management
const webhook = new BunqWebhook(bunq, account);

// Create webhook for specific URL
const webhookId = await webhook.create(account, 'https://myapp.com/bunq-webhook');

// List all webhooks
const webhooks = await webhook.list(account);

// Delete webhook
await webhook.delete(account, webhookId);
```

### File Attachments

```typescript
const attachment = new BunqAttachment(bunq);

// Upload a file
const attachmentUuid = await attachment.uploadFile(
  '/path/to/invoice.pdf',
  'Invoice #12345'
);

// Attach to payment
const payment = await BunqPayment.builder(bunq, account)
  .amount('150.00', 'EUR')
  .toIban('NL91ABNA0417164300', 'Accountant')
  .description('Services rendered')
  .attachments([attachmentUuid])
  .create();

// Upload from buffer
const buffer = await generateReport();
const uuid = await attachment.uploadBuffer(
  buffer,
  'report.pdf',
  'application/pdf',
  'Monthly Report'
);

// Get attachment content
const content = await attachment.getContent(attachmentUuid);
await fs.writeFile('downloaded.pdf', content);
```

### Export Statements

```typescript
// Export last month as PDF
await new ExportBuilder(bunq, account)
  .asPdf()
  .lastMonth()
  .downloadTo('/path/to/statement.pdf');

// Export date range as CSV
await new ExportBuilder(bunq, account)
  .asCsv()
  .dateRange('2024-01-01', '2024-03-31')
  .regionalFormat('EUROPEAN')
  .downloadTo('/path/to/transactions.csv');

// Export as MT940 for accounting software
await new ExportBuilder(bunq, account)
  .asMt940()
  .lastDays(90)  // Last 90 days
  .downloadTo('/path/to/statement.sta');

// Export last 30 days with attachments
await new ExportBuilder(bunq, account)
  .asPdf()
  .lastDays(30)
  .includeAttachments(true)
  .downloadTo('/path/to/statement-with-attachments.pdf');

// Get statement as Buffer (no file saving)
const buffer = await new ExportBuilder(bunq, account)
  .asPdf()
  .lastMonth()
  .download();
// Use the buffer directly, e.g., send as email attachment
await emailService.sendWithAttachment(buffer, 'statement.pdf');

// Get statement as ArrayBuffer for web APIs
const arrayBuffer = await new ExportBuilder(bunq, account)
  .asCsv()
  .lastDays(30)
  .downloadAsArrayBuffer();
// Use with web APIs like Blob
const blob = new Blob([arrayBuffer], { type: 'text/csv' });

// Using account's getAccountStatement method for easy month selection
const statement1 = account.getAccountStatement({
  monthlyIndexedFrom1: 1,  // Last month (1 = last month, 2 = two months ago, etc.)
  includeTransactionAttachments: true
});
await statement1.asPdf().downloadTo('/path/to/last-month.pdf');

// Or using 0-based indexing
const statement2 = account.getAccountStatement({
  monthlyIndexedFrom0: 0,  // Current month (0 = current, 1 = last month, etc.)
  includeTransactionAttachments: false
});
await statement2.asCsv().downloadTo('/path/to/current-month.csv');

// Or specify exact date range
const statement3 = account.getAccountStatement({
  from: new Date('2024-01-01'),
  to: new Date('2024-03-31'),
  includeTransactionAttachments: true
});
await statement3.asMt940().downloadTo('/path/to/q1-statement.sta');
```

### Stateless Session Management

```typescript
// Initial session creation
const bunq = new BunqAccount({
  apiKey: 'your-api-key',
  deviceName: 'My App',
  environment: 'PRODUCTION'
});

// Initialize and receive session data
const sessionData = await bunq.init();

// Save to your preferred storage
await saveToDatabase({
  userId: 'user123',
  sessionData: sessionData,
  createdAt: new Date()
});

// Reusing existing session
const savedData = await loadFromDatabase('user123');
const bunq2 = new BunqAccount({
  apiKey: 'your-api-key',
  deviceName: 'My App',
  environment: 'PRODUCTION'
});

// Initialize with saved session
await bunq2.initWithSession(savedData.sessionData);

// Check if session is valid
if (!bunq2.isSessionValid()) {
  // Session expired, create new one
  const newSession = await bunq2.init();
  await saveToDatabase({ userId: 'user123', sessionData: newSession });
}

// Making API calls with automatic refresh
const { accounts, sessionData: refreshedSession } = await bunq2.getAccounts();

// Always save refreshed session data
if (refreshedSession) {
  await saveToDatabase({
    userId: 'user123',
    sessionData: refreshedSession,
    updatedAt: new Date()
  });
}

// Get current session data at any time
const currentSession = bunq2.getSessionData();
```

### User Management

```typescript
// Get user information
const user = await bunq.getUser();
const userInfo = await user.getInfo();

// Determine user type
if (userInfo.UserPerson) {
  console.log(`Personal account: ${userInfo.UserPerson.display_name}`);
} else if (userInfo.UserCompany) {
  console.log(`Business account: ${userInfo.UserCompany.name}`);
}

// Update user settings
await user.update({
  dailyLimitWithoutConfirmationLogin: '100.00',
  notificationFilters: [
    { category: 'PAYMENT', notificationDeliveryMethod: 'PUSH' }
  ]
});
```

## Advanced Usage

### Custom Request Headers

```typescript
// Use custom request IDs for idempotency
const payment = await BunqPayment.builder(bunq, account)
  .amount('100.00', 'EUR')
  .toIban('NL91ABNA0417164300', 'Recipient')
  .description('Invoice payment')
  .customRequestId('unique-request-id-123')  // Prevents duplicate payments
  .create();

// The same request ID will return the original payment without creating a duplicate
```

### OAuth Token Support

```typescript
// Using OAuth access token instead of API key
const bunq = new BunqAccount({
  apiKey: 'your-oauth-access-token',  // OAuth token from bunq OAuth flow
  deviceName: 'OAuth App',
  environment: 'PRODUCTION',
  isOAuthToken: true  // Important for OAuth-specific handling
});

try {
  // Try normal initialization
  const sessionData = await bunq.init();
  await saveOAuthSession(sessionData);
} catch (error) {
  // OAuth token may already have installation/device
  if (error.message.includes('already has a user session')) {
    // Load existing installation data if available
    const existingInstallation = await loadOAuthInstallation();
    
    // Initialize with existing installation
    const sessionData = await bunq.initOAuthWithExistingInstallation(existingInstallation);
    await saveOAuthSession(sessionData);
  } else {
    throw error;
  }
}

// Use the OAuth-initialized account normally
const { accounts, sessionData } = await bunq.getAccounts();

// OAuth tokens work like regular API keys:
// 1. They go through installation → device → session creation
// 2. The OAuth token is used as the 'secret' during authentication
// 3. A session token is created and used for all API calls
```

### Error Handling

```typescript
import { BunqApiError } from '@apiclient.xyz/bunq';

try {
  await payment.create();
} catch (error) {
  if (error instanceof BunqApiError) {
    // Handle API errors
    console.error('API Error:', error.errors);
    error.errors.forEach(e => {
      console.error(`- ${e.error_description}`);
    });
  } else if (error.response?.status === 401) {
    // Handle authentication errors
    console.error('Authentication failed:', error.message);
    await bunq.init();  // Re-initialize session
  } else {
    // Handle other errors
    console.error('Unexpected error:', error);
  }
}
```

### Automatic Rate Limit Handling

The library automatically handles rate limiting (HTTP 429 responses) with intelligent exponential backoff:

```typescript
// No special handling needed - rate limits are handled automatically
const payments = await Promise.all([
  payment1.create(),
  payment2.create(),
  payment3.create(),
  // ... many more payments
]);

// The HTTP client will automatically:
// 1. Detect 429 responses
// 2. Wait for the time specified in Retry-After header
// 3. Use exponential backoff if no Retry-After is provided
// 4. Retry the request automatically (up to 3 times)
```

### Pagination

```typescript
// Paginate through all transactions
async function* getAllTransactions(account: BunqMonetaryAccount) {
  let olderId: number | false = false;
  
  while (true) {
    const transactions = await account.getTransactions({
      count: 200,
      older_id: olderId
    });
    
    if (transactions.length === 0) break;
    
    yield* transactions;
    olderId = transactions[transactions.length - 1].id;
  }
}

// Usage
for await (const transaction of getAllTransactions(account)) {
  console.log(`${transaction.created}: ${transaction.description}`);
}
```

### Sandbox Testing

```typescript
// Create sandbox environment
const sandboxBunq = new BunqAccount({
  apiKey: '',  // Will be generated
  deviceName: 'My Test App',
  environment: 'SANDBOX'
});

// Create sandbox user with €1000 balance
const apiKey = await sandboxBunq.createSandboxUser();
console.log('Sandbox API key:', apiKey);

// Re-initialize with the generated key
const bunq = new BunqAccount({
  apiKey: apiKey,
  deviceName: 'My Test App',
  environment: 'SANDBOX'
});
await bunq.init();

// The sandbox environment provides €1000 initial balance for testing
// Additional sandbox-specific features can be accessed through the bunq API directly
```

## Security Best Practices

1. **API Key Storage**: Never commit API keys to version control
   ```typescript
   const bunq = new BunqAccount({
     apiKey: process.env.BUNQ_API_KEY,
     deviceName: 'Production App',
     environment: 'PRODUCTION'
   });
   ```

2. **IP Whitelisting**: Restrict API access to specific IPs
   ```typescript
   const bunq = new BunqAccount({
     apiKey: process.env.BUNQ_API_KEY,
     permittedIps: ['1.2.3.4', '5.6.7.8']
   });
   ```

3. **Webhook Verification**: Always verify webhook signatures
   ```typescript
   app.post('/webhook', (req, res) => {
     const signature = req.headers['x-bunq-client-signature'];
     const isValid = bunq.verifyWebhookSignature(req.body, signature);
     
     if (!isValid) {
       return res.status(401).send('Invalid signature');
     }
     
     // Process webhook...
   });
   ```

## Migration Guide

### From @bunq-community/bunq-js-client

```typescript
// Old
import BunqJSClient from '@bunq-community/bunq-js-client';
const bunqJSClient = new BunqJSClient();

// New
import { BunqAccount } from '@apiclient.xyz/bunq';
const bunq = new BunqAccount({
  apiKey: 'your-api-key',
  deviceName: 'My App'
});

// Old
await bunqJSClient.install();
await bunqJSClient.registerDevice();
await bunqJSClient.registerSession();

// New - all handled in one call
await bunq.init();
```

## Migration Guide from v3.x to v4.0.0

Version 4.0.0 introduces a breaking change: the library is now completely stateless. Here's how to migrate:

### Before (v3.x)
```typescript
// Session was automatically saved to .nogit/bunqproduction.json
const bunq = new BunqAccount({ apiKey, deviceName, environment });
await bunq.init(); // Session saved to disk automatically
const accounts = await bunq.getAccounts(); // Returns accounts directly
```

### After (v4.0.0)
```typescript
// You must handle session persistence yourself
const bunq = new BunqAccount({ apiKey, deviceName, environment });
const sessionData = await bunq.init(); // Returns session data
await myDatabase.save('session', sessionData); // You save it

// API calls now return both data and potentially refreshed session
const { accounts, sessionData: newSession } = await bunq.getAccounts();
if (newSession) {
  await myDatabase.save('session', newSession); // Save refreshed session
}
```

### Key Changes
1. **No automatic file persistence** - Remove any dependency on `.nogit/` files
2. **`init()` returns session data** - You must save this data yourself
3. **API methods return objects** - Methods like `getAccounts()` now return `{ accounts, sessionData? }`
4. **Session reuse requires explicit loading** - Use `initWithSession(savedData)`
5. **OAuth handling is explicit** - Use `initOAuthWithExistingInstallation()` for OAuth tokens with existing installations

### Session Storage Example
```typescript
// Simple file-based storage (similar to v3.x behavior)
import { promises as fs } from 'fs';

async function saveSession(data: ISessionData) {
  await fs.writeFile('./my-session.json', JSON.stringify(data));
}

async function loadSession(): Promise<ISessionData | null> {
  try {
    const data = await fs.readFile('./my-session.json', 'utf-8');
    return JSON.parse(data);
  } catch {
    return null;
  }
}

// Database storage example
async function saveSessionToDB(userId: string, data: ISessionData) {
  await db.collection('bunq_sessions').updateOne(
    { userId },
    { $set: { sessionData: data, updatedAt: new Date() } },
    { upsert: true }
  );
}
```

## Testing

The library includes comprehensive test coverage:

```bash
# Run all tests
npm test

# Run specific test suites
npm run test:basic      # Core functionality
npm run test:payments   # Payment features
npm run test:webhooks   # Webhook functionality
npm run test:session    # Session management
npm run test:errors     # Error handling
npm run test:advanced   # Advanced features
```

## Requirements

- Node.js 14.x or higher
- TypeScript 4.5 or higher (for TypeScript users)

## License and Legal Information

This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. 

**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.

### Trademarks

This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.

### Company Information

Task Venture Capital GmbH  
Registered at District court Bremen HRB 35230 HB, Germany

For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.

By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.

# changelog.md for @apiclient.xyz/bunq

## 2025-07-29 - 4.4.0 - feat(export)
Added buffer download methods to ExportBuilder for in-memory statement handling

- Added `download()` method to get statements as Buffer without saving to disk
- Added `downloadAsArrayBuffer()` method for web API compatibility
- Enhanced documentation for account's `getAccountStatement()` method with month-based selection
- Updated README with comprehensive examples for all statement export options

## 2025-07-29 - 4.3.0 - feat(http)
Enhanced HTTP client with automatic rate limit handling

- Added @push.rocks/smartrequest dependency for robust HTTP handling
- Implemented automatic retry with exponential backoff for rate-limited requests
- Built-in handling of HTTP 429 responses with intelligent waiting
- Respects Retry-After headers when provided by the server
- Maximum of 3 retry attempts with configurable backoff (1s, 2s, 4s)
- Improved error handling and network resilience
- Updated readme documentation with automatic rate limit handling examples

## 2025-07-27 - 4.2.1 - fix(tests)
Fix test compatibility with breaking changes from v4.0.0

- Updated all tests to handle new API structure where methods return objects
- Fixed destructuring for getAccounts() which now returns { accounts, sessionData? }
- Ensures all 83 tests pass successfully with the stateless architecture

## 2025-07-27 - 4.2.0 - feat(core)
Switch to native fetch API for all HTTP requests

- Replaced @push.rocks/smartrequest with native fetch API throughout the codebase
- Updated HTTP client to use fetch with proper error handling
- Updated attachment upload/download methods to use fetch
- Updated export download method to use fetch  
- Updated sandbox user creation to use fetch
- Removed smartrequest dependency from package.json and plugins
- Improved error messages with HTTP status codes

## 2025-07-26 - 4.1.3 - fix(export)
Fix PDF statement download to use direct content endpoint

- Changed `downloadContent()` method to use the `/content` endpoint directly for PDF statements
- Removed unnecessary attachment lookup step that was causing issues
- Simplified the download process for customer statement exports

## 2025-07-25 - 4.1.1 - fix(httpclient)
Fix query parameter handling for smartrequest compatibility

- Changed query parameter handling to pass objects directly instead of URLSearchParams
- Removed URLSearchParams usage and string conversion
- Now passes queryParams as an object to smartrequest which handles URL encoding internally

## 2025-07-25 - 4.1.0 - feat(transactions)
Enhanced transaction pagination support with full control over historical data retrieval

- Added full `IBunqPaginationOptions` support to `getTransactions()` method
- Now supports `older_id` for paginating backwards through historical transactions
- Supports custom `count` parameter (defaults to 200)
- Maintains backward compatibility - passing a number is still treated as `newer_id`
- Only includes pagination parameters that are explicitly set (not false/undefined)
- Added `example.pagination.ts` demonstrating various pagination patterns

This enhancement allows banking applications to properly fetch and paginate through historical transaction data using both `newer_id` and `older_id` parameters.

## 2025-07-25 - 4.0.0 - BREAKING CHANGE(core)
Complete stateless architecture - consumers now have full control over session persistence

- **BREAKING**: Removed all file-based persistence - no more automatic saving to .nogit/ directory
- **BREAKING**: `init()` now returns `ISessionData` that must be persisted by the consumer
- **BREAKING**: API methods like `getAccounts()` now return `{ data, sessionData? }` objects
- Added `ISessionData` interface exposing complete session state including sessionId
- Added `initWithSession(sessionData)` to initialize with previously saved sessions
- Added `exportSession()` and `getSessionData()` methods for session access
- Added `isSessionValid()` to check session validity
- Fixed session destruction to use actual session ID instead of hardcoded '0'
- Added `initOAuthWithExistingInstallation()` for explicit OAuth session handling
- Session refresh now returns updated session data for consumer persistence
- Added `example.stateless.ts` showing session management patterns

This change gives consumers full control over session persistence strategy (database, Redis, files, etc.) and makes the library suitable for serverless/microservices architectures.

## 2025-07-22 - 3.1.2 - fix(oauth)
Remove OAuth session caching to prevent authentication issues

- Removed static OAuth session cache that was causing incomplete session issues
- Each OAuth token now creates a fresh session without caching
- Removed cache management methods (clearOAuthCache, clearOAuthCacheForToken, getOAuthCacheSize)
- Simplified init() method to treat OAuth tokens the same as regular API keys
- OAuth tokens still handle "Superfluous authentication" errors with initWithExistingInstallation

## 2025-07-22 - 3.1.1 - fix(oauth)
Fix OAuth token authentication flow for existing installations

- Fixed initWithExistingInstallation to properly create new sessions with existing installation/device
- OAuth tokens now correctly skip installation/device steps when they already exist
- Session creation still uses OAuth token as the secret parameter
- Properly handles "Superfluous authentication" errors by reusing existing installation
- Renamed initWithExistingSession to initWithExistingInstallation for clarity

## 2025-07-22 - 3.1.0 - feat(oauth)
Add OAuth session caching to prevent multiple authentication attempts

- Implemented static OAuth session cache in BunqAccount class
- Added automatic session reuse for OAuth tokens across multiple instances
- Added handling for "Superfluous authentication" and "Authentication token already has a user session" errors
- Added initWithExistingSession() method to reuse OAuth tokens as session tokens
- Added cache management methods: clearOAuthCache(), clearOAuthCacheForToken(), getOAuthCacheSize()
- Added hasValidSession() method to check session validity
- OAuth tokens now properly cache and reuse sessions to prevent authentication conflicts

## 2025-07-22 - 3.0.8 - fix(oauth)
Correct OAuth implementation to match bunq documentation

- Removed OAuth mode from HTTP client - OAuth tokens use normal request signing
- OAuth tokens now work exactly like regular API keys (per bunq docs)
- Fixed test comments to reflect correct OAuth behavior
- Simplified OAuth handling by removing unnecessary special cases
- OAuth tokens properly go through full auth flow with request signing

## 2025-07-22 - 3.0.7 - fix(oauth)
Fix OAuth token authentication flow

- OAuth tokens now go through full initialization (installation → device → session)
- Fixed "Insufficient authentication" errors by treating OAuth tokens as API keys
- OAuth tokens are used as the 'secret' parameter, not as session tokens
- Follows bunq documentation: "Just use the OAuth Token as a normal bunq API key"
- Removed incorrect session skip logic for OAuth tokens

## 2025-07-22 - 3.0.6 - fix(oauth)
Fix OAuth token private key error

- Fixed "Private key not generated yet" error for OAuth tokens
- Added OAuth mode to HTTP client to skip request signing
- Skip response signature verification for OAuth tokens
- Properly handle missing private keys in OAuth mode

## 2025-07-22 - 3.0.5 - feat(oauth)
Add OAuth token support

- Added support for OAuth access tokens with isOAuthToken flag
- OAuth tokens skip session creation since they already have an associated session
- Fixed "Authentication token already has a user session" error for OAuth tokens
- Added OAuth documentation to readme with usage examples
- Created test cases for OAuth token flow

## 2025-07-22 - 3.0.4 - fix(tests,security)
Improve test reliability and remove sensitive file

- Added error handling for "Superfluous authentication" errors in session tests
- Improved retry mechanism with rate limiting delays in error tests  
- Skipped tests that require access to private properties
- Removed qenv.yml from repository for security reasons

## 2025-07-22 - 3.0.3 - fix(tests)
Fix test failures and draft payment API compatibility

- Fixed draft payment test by removing unsupported cancel operation in sandbox
- Added error handling for "Insufficient authentication" errors in transaction tests  
- Fixed draft payment API payload formatting to use snake_case properly
- Removed problematic draft update operations that are limited in sandbox

## 2025-07-22 - 3.0.2 - fix(tests,webhooks)
Fix test assertions and webhook API structure

- Updated test assertions from .toBe() to .toEqual() for better compatibility
- Made error message assertions more flexible to handle varying error messages
- Fixed webhook API payload structure by removing unnecessary wrapper object
- Added --logfile flag to test script for better debugging

## 2025-07-18 - 3.0.1 - fix(docs)
docs: update readme examples for card management, export statements and error handling; add local settings for CLI permissions

- Replaced outdated card management examples with a note emphasizing that activation, PIN updates, and ordering should be handled via the bunq app or API.
- Updated export examples to use methods like .lastDays(90) and .includeAttachments for clearer instructions.
- Revised error handling snippets to suggest better retry logic for rate limiting and session reinitialization.
- Added a new .claude/settings.local.json file to configure allowed CLI commands and permissions.

## 2025-07-18 - 3.0.0 - BREAKING CHANGE(core)
Major restructuring and feature enhancements: added batch payments and scheduled payments with builder patterns, improved webhook management, migrated package naming to @apiclient.xyz/bunq, and updated documentation and tests.

- Introduced BunqPaymentBatch for creating multiple payments in a single API call.
- Implemented BunqSchedulePayment builder for scheduled and recurring payments.
- Enhanced webhook support with integrated webhook server and improved signature verification.
- Migrated package from @bunq-community/bunq to @apiclient.xyz/bunq with complete module restructure.
- Updated README and changelog to reflect breaking changes and provide a migration guide.
- Improved ESM compatibility and full TypeScript support.

## 2025-07-18 - 3.0.0 - BREAKING CHANGE(core)
Major update: Introduced batch payments, scheduled payment builder, and comprehensive webhook improvements with a complete migration from bunq-js-client to the new package structure. This release brings breaking changes in API signatures, module exports, and session management for enhanced ESM and TypeScript support.

- Added BunqPaymentBatch for creating multiple payments in a single API call
- Introduced BunqSchedulePayment with builder pattern for scheduled and recurring payments
- Enhanced webhook management with BunqWebhook and integrated webhook server support
- Migrated package naming from @bunq-community/bunq to @apiclient.xyz/bunq with a complete module restructure
- Improved ESM compatibility with proper .js extensions and TypeScript verbatimModuleSyntax support
- Updated documentation, changelog, and tests to reflect breaking changes and migration updates

## 2025-07-18 - 3.0.0 - BREAKING CHANGE(core)
Release 2.0.0: Major updates including batch payment support, scheduled payments with a builder pattern, comprehensive webhook enhancements, migration from bunq-js-client to the new package structure, and improved ESM/TypeScript compatibility.

- Added BunqPaymentBatch for creating multiple payments in a single API call.
- Introduced BunqSchedulePayment with builder pattern for scheduled and recurring payments.
- Implemented comprehensive webhook management with BunqWebhook and built-in webhook server.
- Migrated package naming from @bunq-community/bunq to @apiclient.xyz/bunq and restructured module exports.
- Improved ESM compatibility with proper .js extension usage and TypeScript verbatimModuleSyntax support.
- Updated documentation, changelog, and tests to reflect the new API and migration changes.

## 2019-10-02 to 2025-07-18 - Various - Minor updates
These releases did not include any feature or bug‐fix changes beyond routine updates. The following versions are summarized here: 2.0.0, 1.0.22, 1.0.7, and 1.0.6.

## 2020-08-25 - 1.0.21 - core
Main change: fix(core): update

- Fixed issues in the core module

## 2020-08-21 - 1.0.20 - core
Main change: fix(core): update

- Fixed issues in the core module

## 2020-08-21 - 1.0.19 - core
Main change: fix(core): update

- Fixed issues in the core module

## 2020-08-21 - 1.0.18 - core
Main change: fix(core): update

- Fixed issues in the core module

## 2020-08-20 - 1.0.17 - core
Main change: fix(core): update

- Fixed issues in the core module

## 2020-08-20 - 1.0.16 - core
Main change: fix(core): update

- Fixed issues in the core module

## 2020-06-20 - 1.0.15 - core
Main change: fix(core): update

- Fixed issues in the core module

## 2019-12-15 - 1.0.14 - transactions
Main change: fix(transactions): enter a starting transaction

- Entered a starting transaction in the transactions module

## 2019-12-15 - 1.0.13 - core
Main change: fix(core): update

- Fixed issues in the core module

## 2019-12-15 - 1.0.12 - core
Main change: fix(core): update

- Fixed issues in the core module

## 2019-10-03 - 1.0.11 - core
Main change: fix(core): update

- Fixed issues in the core module

## 2019-10-03 - 1.0.10 - core
Main change: fix(core): update

- Fixed issues in the core module

## 2019-10-02 - 1.0.9 - core
Main change: fix(core): update

- Fixed issues in the core module

## 2019-10-02 - 1.0.8 - core
Main change: fix(core): update

- Fixed issues in the core module

## 2019-10-02 - 1.0.5 - core
Main change: fix(core): update

- Fixed issues in the core module

## 2019-10-02 - 1.0.4 - core
Main change: fix(core): update

- Fixed issues in the core module

## 2019-09-26 - 1.0.3 - core
Main change: fix(core): update

- Fixed issues in the core module

## 2019-09-26 - 1.0.2 - core
Main change: fix(core): update

- Fixed issues in the core module

## 2019-09-26 - 1.0.1 - core
Main change: fix(core): update

- Fixed issues in the core module