API Integration Guide for Bank Statement Processing: Developer's Complete Reference 2025
Complete developer guide for integrating bank statement processing APIs. Code examples, authentication, error handling, and best practices for financial data automation.
API Integration Guide for Bank Statement Processing: Developer's Complete Reference 2025
Modern financial applications require seamless bank statement processing capabilities. Whether you're building accounting software, expense management tools, or financial analytics platforms, integrating a bank statement processing API can dramatically enhance your application's value proposition.
This comprehensive guide covers everything developers need to know about implementing bank statement processing APIs, from basic integration to enterprise-scale automation.
Quick Developer Reference
StatementConverter API Overview:
- Base URL:
https://api.statementconverter.xyz/v1
- Authentication: API Key + Bearer Token
- Rate Limits: 1000 requests/hour (Enterprise: unlimited)
- Processing Speed: 387ms average
- Accuracy: 99.3%
- Supported Formats: PDF input, JSON/CSV/Excel output
Table of Contents
- API Architecture Overview
- Authentication and Security
- Core Endpoints and Methods
- Integration Patterns
- Code Examples and SDKs
- Error Handling and Retry Logic
- Webhook Integration
- Batch Processing Implementation
- Performance Optimization
- Enterprise Considerations
- Testing and Monitoring
- Best Practices
API Architecture Overview {#api-architecture}
RESTful Design Principles
The StatementConverter API follows REST conventions with predictable resource-based URLs:
GET /v1/documents # List processing jobs
POST /v1/documents # Submit new document
GET /v1/documents/{id} # Get processing status
GET /v1/documents/{id}/result # Download processed data
DELETE /v1/documents/{id} # Cancel processing job
Processing Flow Architecture
Client Application → API Gateway → Document Processor → AI Engine → Result Storage
↓ ↑
Webhook ←←←←←←←←←←←←←← Status Updates ←←←←←←←←←←←←←←←←←←←←←←
Processing Stages:
- Upload: Document received and validated
- Queue: Job queued for processing
- Processing: AI extraction in progress
- Complete: Data extracted and available
- Error: Processing failed with details
Data Format Standards
Input Requirements:
- File format: PDF
- Maximum size: 50MB
- Text-based documents (OCR available for scanned)
- No password protection
Output Options:
- JSON: Structured transaction data
- CSV: Spreadsheet-compatible format
- Excel: Native .xlsx format with formatting
- QBO: QuickBooks-compatible export
Authentication and Security {#authentication-security}
API Key Authentication
Obtaining API Keys:
- Register at StatementConverter Developer Portal
- Create new application
- Generate API key and secret
- Configure rate limits and permissions
Authentication Header:
Authorization: Bearer {your-api-key}
Content-Type: application/json
Security Best Practices
API Key Management:
// Environment variable usage (recommended)
const API_KEY = process.env.STATEMENT_CONVERTER_API_KEY;
// Never hardcode keys in source code
// ❌ Bad
const API_KEY = "sk_live_abc123...";
// ✅ Good
const API_KEY = process.env.STATEMENT_CONVERTER_API_KEY;
Request Signing (Enterprise):
const crypto = require('crypto');
function signRequest(payload, timestamp, secret) {
const signingString = timestamp + '.' + JSON.stringify(payload);
return crypto
.createHmac('sha256', secret)
.update(signingString)
.digest('hex');
}
Rate Limiting
Standard Limits:
- Free tier: 10 requests/hour
- Professional: 1000 requests/hour
- Enterprise: Unlimited with SLA
Rate Limit Headers:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1642608000
Core Endpoints and Methods {#core-endpoints}
Document Submission
POST /v1/documents
Submit a bank statement for processing:
const formData = new FormData();
formData.append('file', fileBuffer, 'statement.pdf');
formData.append('format', 'json');
formData.append('webhook_url', 'https://your-app.com/webhook');
const response = await fetch('https://api.statementconverter.xyz/v1/documents', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
},
body: formData
});
const result = await response.json();
Response:
{
"id": "doc_1234567890",
"status": "processing",
"created_at": "2025-01-17T10:30:00Z",
"estimated_completion": "2025-01-17T10:30:30Z",
"pages": 5,
"format": "json"
}
Status Monitoring
GET /v1/documents/{id}
Check processing status:
async function checkStatus(documentId) {
const response = await fetch(
`https://api.statementconverter.xyz/v1/documents/${documentId}`,
{
headers: {
'Authorization': `Bearer ${API_KEY}`
}
}
);
return await response.json();
}
Response:
{
"id": "doc_1234567890",
"status": "completed",
"progress": 100,
"pages_processed": 5,
"transactions_found": 247,
"confidence_score": 99.3,
"processing_time_ms": 387,
"result_url": "/v1/documents/doc_1234567890/result"
}
Result Retrieval
GET /v1/documents/{id}/result
Download processed data:
async function downloadResult(documentId, format = 'json') {
const response = await fetch(
`https://api.statementconverter.xyz/v1/documents/${documentId}/result?format=${format}`,
{
headers: {
'Authorization': `Bearer ${API_KEY}`
}
}
);
if (format === 'json') {
return await response.json();
}
return await response.blob(); // For Excel/CSV downloads
}
Integration Patterns {#integration-patterns}
Synchronous Processing
Real-time Processing Pattern:
async function processStatementSync(fileBuffer) {
try {
// Submit document
const submission = await submitDocument(fileBuffer);
// Poll for completion
let status = await checkStatus(submission.id);
while (status.status === 'processing') {
await new Promise(resolve => setTimeout(resolve, 1000));
status = await checkStatus(submission.id);
}
// Download result
if (status.status === 'completed') {
return await downloadResult(submission.id);
}
throw new Error(`Processing failed: ${status.error}`);
} catch (error) {
console.error('Processing error:', error);
throw error;
}
}
Asynchronous Processing
Webhook-based Pattern:
// Submit with webhook
async function processStatementAsync(fileBuffer, webhookUrl) {
const formData = new FormData();
formData.append('file', fileBuffer, 'statement.pdf');
formData.append('webhook_url', webhookUrl);
const response = await fetch('https://api.statementconverter.xyz/v1/documents', {
method: 'POST',
headers: { 'Authorization': `Bearer ${API_KEY}` },
body: formData
});
return await response.json();
}
// Webhook handler
app.post('/webhook/statement-processed', (req, res) => {
const { document_id, status, result_url } = req.body;
if (status === 'completed') {
// Process the completed document
handleCompletedDocument(document_id, result_url);
} else if (status === 'error') {
// Handle processing error
handleProcessingError(document_id, req.body.error);
}
res.status(200).json({ received: true });
});
Batch Processing
Multiple Document Processing:
async function processBatchStatements(files) {
const results = await Promise.allSettled(
files.map(file => processStatementSync(file))
);
const successful = results
.filter(result => result.status === 'fulfilled')
.map(result => result.value);
const failed = results
.filter(result => result.status === 'rejected')
.map(result => result.reason);
return { successful, failed };
}
Code Examples and SDKs {#code-examples}
JavaScript/Node.js SDK
Installation:
npm install @statementconverter/nodejs-sdk
Basic Usage:
const StatementConverter = require('@statementconverter/nodejs-sdk');
const client = new StatementConverter({
apiKey: process.env.STATEMENT_CONVERTER_API_KEY,
baseUrl: 'https://api.statementconverter.xyz/v1'
});
// Process document
async function processStatement(filePath) {
try {
const result = await client.documents.create({
file: fs.createReadStream(filePath),
format: 'json',
webhook: 'https://your-app.com/webhook'
});
console.log('Document submitted:', result.id);
return result;
} catch (error) {
console.error('Error:', error.message);
}
}
Python SDK
Installation:
pip install statementconverter-python
Basic Usage:
import statementconverter
from statementconverter import StatementConverter
client = StatementConverter(api_key=os.getenv('STATEMENT_CONVERTER_API_KEY'))
# Process document
def process_statement(file_path):
try:
with open(file_path, 'rb') as file:
result = client.documents.create(
file=file,
format='json',
webhook='https://your-app.com/webhook'
)
print(f"Document submitted: {result.id}")
return result
except statementconverter.APIError as e:
print(f"API Error: {e.message}")
PHP SDK
Installation:
composer require statementconverter/php-sdk
Basic Usage:
<?php
require_once 'vendor/autoload.php';
use StatementConverter\Client;
$client = new Client([
'api_key' => $_ENV['STATEMENT_CONVERTER_API_KEY'],
'base_url' => 'https://api.statementconverter.xyz/v1'
]);
// Process document
function processStatement($filePath) {
global $client;
try {
$result = $client->documents->create([
'file' => fopen($filePath, 'r'),
'format' => 'json',
'webhook' => 'https://your-app.com/webhook'
]);
echo "Document submitted: " . $result->id;
return $result;
} catch (StatementConverter\APIException $e) {
echo "Error: " . $e->getMessage();
}
}
?>
cURL Examples
Submit Document:
curl -X POST \
https://api.statementconverter.xyz/v1/documents \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "file=@statement.pdf" \
-F "format=json" \
-F "webhook_url=https://your-app.com/webhook"
Check Status:
curl -X GET \
https://api.statementconverter.xyz/v1/documents/doc_1234567890 \
-H "Authorization: Bearer YOUR_API_KEY"
Error Handling and Retry Logic {#error-handling}
HTTP Status Codes
Success Codes:
200 OK
: Request successful201 Created
: Document submitted successfully202 Accepted
: Document queued for processing
Error Codes:
400 Bad Request
: Invalid request parameters401 Unauthorized
: Invalid API key403 Forbidden
: Insufficient permissions413 Payload Too Large
: File exceeds size limit429 Too Many Requests
: Rate limit exceeded500 Internal Server Error
: Server error
Error Response Format
{
"error": {
"code": "invalid_file_format",
"message": "Unsupported file format. Please upload a PDF file.",
"details": {
"file_type": "image/jpeg",
"supported_formats": ["application/pdf"]
},
"request_id": "req_1234567890"
}
}
Retry Logic Implementation
class APIClient {
async makeRequest(url, options, maxRetries = 3) {
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await fetch(url, options);
if (response.ok) {
return await response.json();
}
// Don't retry client errors (4xx)
if (response.status >= 400 && response.status < 500) {
throw new Error(`Client error: ${response.status}`);
}
// Retry server errors (5xx) and rate limits
lastError = new Error(`Server error: ${response.status}`);
} catch (error) {
lastError = error;
}
// Exponential backoff
if (attempt < maxRetries) {
const delay = Math.pow(2, attempt) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw lastError;
}
}
Webhook Integration {#webhook-integration}
Webhook Configuration
Setting Webhook URL:
// During document submission
const response = await fetch('https://api.statementconverter.xyz/v1/documents', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
file_url: 'https://your-storage.com/statement.pdf',
format: 'json',
webhook_url: 'https://your-app.com/webhooks/statements',
webhook_secret: 'your-webhook-secret'
})
});
Webhook Payload Structure
Processing Complete:
{
"event": "document.completed",
"document_id": "doc_1234567890",
"status": "completed",
"timestamp": "2025-01-17T10:30:30Z",
"data": {
"pages_processed": 5,
"transactions_found": 247,
"confidence_score": 99.3,
"processing_time_ms": 387,
"result_url": "/v1/documents/doc_1234567890/result"
}
}
Processing Failed:
{
"event": "document.failed",
"document_id": "doc_1234567890",
"status": "error",
"timestamp": "2025-01-17T10:30:15Z",
"error": {
"code": "processing_failed",
"message": "Unable to extract tables from document",
"details": {
"reason": "Document appears to be scanned with poor quality"
}
}
}
Webhook Security
Signature Verification:
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload, 'utf8')
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expectedSignature, 'hex')
);
}
// Express.js webhook handler
app.post('/webhooks/statements', express.raw({type: 'application/json'}), (req, res) => {
const signature = req.headers['x-signature-sha256'];
const payload = req.body;
if (!verifyWebhookSignature(payload, signature, WEBHOOK_SECRET)) {
return res.status(401).send('Unauthorized');
}
const event = JSON.parse(payload);
handleWebhookEvent(event);
res.status(200).json({ received: true });
});
Batch Processing Implementation {#batch-processing}
Concurrent Processing
Optimal Concurrency Management:
class BatchProcessor {
constructor(concurrencyLimit = 5) {
this.concurrencyLimit = concurrencyLimit;
this.queue = [];
this.running = 0;
}
async processBatch(files) {
return new Promise((resolve, reject) => {
const results = [];
let completed = 0;
files.forEach((file, index) => {
this.queue.push(async () => {
try {
const result = await this.processFile(file);
results[index] = { success: true, data: result };
} catch (error) {
results[index] = { success: false, error: error.message };
} finally {
completed++;
this.running--;
this.processNext();
if (completed === files.length) {
resolve(results);
}
}
});
});
this.processNext();
});
}
processNext() {
if (this.running < this.concurrencyLimit && this.queue.length > 0) {
this.running++;
const task = this.queue.shift();
task();
}
}
async processFile(file) {
// Implementation for single file processing
return await statementConverter.process(file);
}
}
Progress Tracking
Batch Progress Monitoring:
class ProgressTracker {
constructor() {
this.jobs = new Map();
}
trackBatch(batchId, files) {
this.jobs.set(batchId, {
total: files.length,
completed: 0,
failed: 0,
results: [],
startTime: Date.now()
});
}
updateProgress(batchId, result) {
const job = this.jobs.get(batchId);
if (job) {
job.results.push(result);
if (result.success) {
job.completed++;
} else {
job.failed++;
}
}
}
getProgress(batchId) {
const job = this.jobs.get(batchId);
if (!job) return null;
const processed = job.completed + job.failed;
const progress = (processed / job.total) * 100;
const elapsed = Date.now() - job.startTime;
const eta = processed > 0 ? (elapsed / processed) * (job.total - processed) : 0;
return {
total: job.total,
completed: job.completed,
failed: job.failed,
progress: Math.round(progress),
eta: Math.round(eta / 1000), // seconds
results: job.results
};
}
}
Performance Optimization {#performance-optimization}
Connection Pooling
HTTP Agent Configuration:
const https = require('https');
const httpsAgent = new https.Agent({
keepAlive: true,
maxSockets: 10,
maxFreeSockets: 2,
timeout: 60000
});
const client = axios.create({
httpsAgent,
timeout: 30000,
headers: {
'Connection': 'keep-alive'
}
});
Caching Strategies
Result Caching:
const Redis = require('redis');
const redis = Redis.createClient();
class CachedProcessor {
async processWithCache(fileBuffer) {
// Generate file hash for cache key
const fileHash = crypto
.createHash('sha256')
.update(fileBuffer)
.digest('hex');
const cacheKey = `processed:${fileHash}`;
// Check cache first
const cached = await redis.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
// Process if not cached
const result = await this.processStatement(fileBuffer);
// Cache result (expire in 7 days)
await redis.setex(cacheKey, 604800, JSON.stringify(result));
return result;
}
}
Memory Management
Stream Processing for Large Files:
const fs = require('fs');
const FormData = require('form-data');
async function processLargeFile(filePath) {
const form = new FormData();
// Use streams to avoid loading entire file into memory
form.append('file', fs.createReadStream(filePath));
form.append('format', 'json');
const response = await fetch('https://api.statementconverter.xyz/v1/documents', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
...form.getHeaders()
},
body: form
});
return await response.json();
}
Enterprise Considerations {#enterprise-considerations}
High Availability Setup
Load Balancing Configuration:
const endpoints = [
'https://api-1.statementconverter.xyz/v1',
'https://api-2.statementconverter.xyz/v1',
'https://api-3.statementconverter.xyz/v1'
];
class LoadBalancedClient {
constructor() {
this.currentEndpoint = 0;
}
getEndpoint() {
const endpoint = endpoints[this.currentEndpoint];
this.currentEndpoint = (this.currentEndpoint + 1) % endpoints.length;
return endpoint;
}
async makeRequest(path, options) {
let lastError;
for (let attempt = 0; attempt < endpoints.length; attempt++) {
try {
const baseUrl = this.getEndpoint();
const response = await fetch(`${baseUrl}${path}`, options);
if (response.ok) {
return await response.json();
}
lastError = new Error(`HTTP ${response.status}: ${response.statusText}`);
} catch (error) {
lastError = error;
}
}
throw lastError;
}
}
Monitoring and Observability
Metrics Collection:
const prometheus = require('prom-client');
// Define metrics
const requestCounter = new prometheus.Counter({
name: 'api_requests_total',
help: 'Total number of API requests',
labelNames: ['method', 'endpoint', 'status']
});
const processingDuration = new prometheus.Histogram({
name: 'processing_duration_seconds',
help: 'Time spent processing documents',
buckets: [0.1, 0.5, 1, 2, 5, 10]
});
// Instrumented API client
class MonitoredClient {
async processDocument(file) {
const timer = processingDuration.startTimer();
try {
const result = await this.apiCall('/documents', {
method: 'POST',
body: file
});
requestCounter.inc({ method: 'POST', endpoint: '/documents', status: 'success' });
return result;
} catch (error) {
requestCounter.inc({ method: 'POST', endpoint: '/documents', status: 'error' });
throw error;
} finally {
timer();
}
}
}
Data Governance
Audit Logging:
class AuditLogger {
constructor(options = {}) {
this.logLevel = options.logLevel || 'info';
this.destination = options.destination || console;
}
logAPICall(userId, action, documentId, metadata = {}) {
const logEntry = {
timestamp: new Date().toISOString(),
userId,
action,
documentId,
metadata,
requestId: generateRequestId()
};
this.destination.log(JSON.stringify(logEntry));
}
logProcessingComplete(documentId, stats) {
this.logAPICall(null, 'PROCESSING_COMPLETE', documentId, {
processingTimeMs: stats.processingTime,
transactionsFound: stats.transactionCount,
confidenceScore: stats.confidence
});
}
}
Testing and Monitoring {#testing-monitoring}
Unit Testing
Jest Test Example:
const StatementProcessor = require('../src/statement-processor');
describe('StatementProcessor', () => {
let processor;
beforeEach(() => {
processor = new StatementProcessor({
apiKey: 'test-key',
baseUrl: 'https://api.test.statementconverter.xyz/v1'
});
});
test('should process valid PDF statement', async () => {
const mockFile = Buffer.from('mock-pdf-content');
// Mock API response
fetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve({
id: 'doc_test123',
status: 'completed',
transactions: 50
})
});
const result = await processor.process(mockFile);
expect(result.status).toBe('completed');
expect(result.transactions).toBe(50);
});
test('should handle API errors gracefully', async () => {
const mockFile = Buffer.from('mock-pdf-content');
fetch.mockRejectedValueOnce(new Error('Network error'));
await expect(processor.process(mockFile))
.rejects
.toThrow('Network error');
});
});
Integration Testing
End-to-End Test:
describe('Bank Statement Processing Integration', () => {
test('complete processing workflow', async () => {
// Upload test statement
const testFile = fs.readFileSync('./test-data/sample-statement.pdf');
const submission = await client.documents.create({
file: testFile,
format: 'json'
});
expect(submission.id).toBeDefined();
expect(submission.status).toBe('processing');
// Wait for processing to complete
let status = await client.documents.get(submission.id);
while (status.status === 'processing') {
await new Promise(resolve => setTimeout(resolve, 1000));
status = await client.documents.get(submission.id);
}
expect(status.status).toBe('completed');
expect(status.transactions_found).toBeGreaterThan(0);
// Download and validate results
const result = await client.documents.getResult(submission.id);
expect(result.transactions).toBeDefined();
expect(Array.isArray(result.transactions)).toBe(true);
});
});
Monitoring Dashboard
Health Check Endpoint:
app.get('/health', async (req, res) => {
const checks = {
api: await checkAPIHealth(),
database: await checkDatabaseHealth(),
redis: await checkRedisHealth(),
processing_queue: await checkQueueHealth()
};
const healthy = Object.values(checks).every(check => check.status === 'ok');
const status = healthy ? 200 : 503;
res.status(status).json({
status: healthy ? 'healthy' : 'unhealthy',
timestamp: new Date().toISOString(),
checks
});
});
async function checkAPIHealth() {
try {
const response = await fetch('https://api.statementconverter.xyz/v1/health', {
headers: { 'Authorization': `Bearer ${API_KEY}` }
});
return {
status: response.ok ? 'ok' : 'error',
response_time: response.headers.get('x-response-time'),
rate_limit_remaining: response.headers.get('x-ratelimit-remaining')
};
} catch (error) {
return { status: 'error', error: error.message };
}
}
Best Practices {#best-practices}
Security Best Practices
- Never expose API keys in client-side code
- Use environment variables for configuration
- Implement proper error handling without exposing sensitive data
- Validate webhook signatures
- Use HTTPS for all communications
- Implement rate limiting in your application
Performance Best Practices
- Use connection pooling for high-volume applications
- Implement proper retry logic with exponential backoff
- Cache results when appropriate
- Use async processing for large batches
- Monitor API usage and performance metrics
Development Best Practices
- Use TypeScript for better type safety
- Implement comprehensive error handling
- Write unit and integration tests
- Document your integration thoroughly
- Use semantic versioning for your application
Example TypeScript Interface
interface ProcessingResult {
id: string;
status: 'processing' | 'completed' | 'error';
transactions?: Transaction[];
metadata: {
pages_processed: number;
confidence_score: number;
processing_time_ms: number;
};
error?: {
code: string;
message: string;
details?: any;
};
}
interface Transaction {
date: string;
description: string;
amount: number;
type: 'debit' | 'credit';
balance?: number;
category?: string;
}
Conclusion
Integrating bank statement processing APIs can significantly enhance your application's capabilities, providing users with powerful financial data extraction and analysis tools. By following the patterns and best practices outlined in this guide, you can build robust, scalable, and secure integrations that deliver exceptional value to your users.
Key Takeaways:
- Start Simple: Begin with basic integration and add complexity as needed
- Handle Errors Gracefully: Implement comprehensive error handling and retry logic
- Monitor Performance: Track API usage, response times, and success rates
- Secure by Design: Follow security best practices from the beginning
- Test Thoroughly: Implement both unit and integration tests
Ready to Start Building?
Get started with the StatementConverter API today:
Get API Keys - Start with free tier for development
Explore our SDK documentation and code examples for faster implementation.
For enterprise integrations, contact our developer support team for dedicated assistance and custom solutions.
This guide reflects current API capabilities as of January 2025. API features and endpoints may evolve. Always refer to the latest API documentation for current specifications.