API Documentation
Complete reference for integrating with the FLVXtract API for automated bank statement extraction and analysis.
Introduction
Everything you need to integrate with the FLVXtract API for automated bank statement extraction and analysis.
Upload bank statement PDFs and receive structured transaction data with AI-powered extraction.
Get lending evaluation metrics including salary regularity, debt-to-income ratio, and cash flow scores.
Receive instant notifications when extraction completes via secure webhook delivery.
Base URL
All API requests are made to the following base URL. All endpoints require HTTPS.
https://api.flvxtract.com
Content Type
All request and response bodies use JSON unless otherwise specified (e.g., file uploads use multipart/form-data).
Content-Type: application/json
Accept: application/json
Quick Start
- Create an account or log in to get your JWT token
- Or generate an API Key pair from the API Keys section
- Upload a bank statement PDF via
POST /api/statements/upload - For consent flows, create a request via
POST /api/consent/requestsand share the join link - Poll or configure a webhook to be notified on completion
- Retrieve results via
GET /api/statements/{'{id}'}/analysis
Authentication
FLVXtract supports two authentication methods: JWT Bearer tokens and API Keys.
JWT Bearer Token
JWT authentication is ideal for user-facing applications. Tokens expire after 60 minutes and can be refreshed using the refresh token (valid for 7 days).
1. Obtain a token
curl -X POST https://api.flvxtract.com/api/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"password": "your-password"
}'
2. Use the token
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
3. Refresh when expired
curl -X POST https://api.flvxtract.com/api/auth/refresh \
-H "Content-Type: application/json" \
-d '{ "refreshToken": "your-refresh-token" }'
API Key Authentication
API keys are ideal for server-to-server integrations. Each key consists of a Consumer Key and Consumer Secret pair.
Include keys in headers
X-Consumer-Key: flvx_prod_a1b2c3d4e5f6...
X-Consumer-Secret: sk_live_x9y8z7w6v5u4...
curl https://api.flvxtract.com/api/statements \
-H "X-Consumer-Key: flvx_prod_a1b2c3d4e5f6..." \
-H "X-Consumer-Secret: sk_live_x9y8z7w6v5u4..."
Security Best Practices
- Never expose API keys in client-side code or version control
- Use environment variables to store keys
- Rotate keys periodically using the regenerate endpoint
- Use separate keys for development and production
Endpoints
All endpoints require authentication unless marked none.
Webhooks
Receive real-time notifications when extraction jobs complete, fail, or require attention.
Configure your webhook URL in Settings → Webhooks inside the dashboard. You can also test delivery from the settings page.
Events
| Event Type | Description | Trigger |
|---|---|---|
| extraction.completed | Extraction finished successfully | Statement processing complete |
| extraction.failed | Extraction job failed | Processing error occurred |
| extraction.partial | Extraction completed with warnings | Low confidence results |
| webhook.test | Test webhook event | Manual test from dashboard |
Webhook Headers
X-Webhook-Signature: sha256=a1b2c3d4e5f6...
X-Webhook-Signature-Algorithm: sha256
X-Webhook-Delivery-Id: 550e8400-e29b-41d4-a716-446655440000
X-Webhook-Event-Type: extraction.completed
X-Webhook-Attempt: 1
X-Webhook-Timestamp: 1704067200
User-Agent: FLVXtract-Webhook/1.0
extraction.completed payload
{
"eventType": "extraction.completed",
"eventId": "550e8400-e29b-41d4-a716-446655440000",
"timestamp": "2024-01-26T12:00:00Z",
"organizationId": "123e4567-e89b-12d3-a456-426614174000",
"data": {
"statementId": "789e0123-e45b-67c8-d901-234567890abc",
"fileName": "statement_january_2024.pdf",
"status": "completed",
"transactionCount": 45,
"processingTimeMs": 3245,
"confidence": { "overall": 0.95, "ocr": 0.97, "extraction": 0.93 },
"detectedBank": "GTBank"
}
}
Signature Verification
Every webhook includes an HMAC-SHA256 signature in the X-Webhook-Signature header. Always verify this to ensure requests are from FLVXtract.
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expected = 'sha256=' +
crypto.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature), Buffer.from(expected)
);
}
// Express.js
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-webhook-signature'];
if (!verifyWebhookSignature(req.body, sig, process.env.FLVXTRACT_WEBHOOK_SECRET))
return res.status(401).send('Invalid signature');
res.status(200).send('OK');
});
Retry Behavior
| Attempt | Delay | Retries On |
|---|---|---|
| 1st | Immediate | N/A |
| 2nd | 2 seconds | 5xx, 429, timeout, connection error |
| 3rd | 4 seconds | 5xx, 429, timeout, connection error |
| 4th | 8 seconds | 5xx, 429, timeout, connection error |
Best Practices
- Respond with
200 OKwithin 30 seconds - Process payloads asynchronously: queue for background processing
- Use
eventIdfor idempotent processing, as events may be delivered multiple times - 4xx responses (except 408, 429) are treated as permanent failures and will not be retried
Rate Limiting
API requests are rate-limited to ensure fair usage and platform stability.
| Tier | Limit | Applies To |
|---|---|---|
| Global | 1,000 req/min | All authenticated endpoints |
| Upload | 10 req/min | Statement upload endpoint |
| Auth | 5 req/min | Login, register, forgot password |
| Strict | 3 req/min | Sensitive operations |
Rate Limit Headers
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 987
X-RateLimit-Reset: 1706270460
Requests that exceed the limit receive 429 Too Many Requests. Wait until the X-RateLimit-Reset timestamp before retrying.
Error Handling
All API errors follow a consistent JSON format.
{
"message": "Validation failed",
"errors": {
"email": ["The email field is required."],
"password": ["Password must be at least 8 characters."]
},
"details": null,
"traceId": "00-cf3d4c9f2353d95f7fbe4f4b2e04f8a6-..."
}
{
"message": "Monthly upload quota exceeded for your current plan.",
"errors": null,
"details": { "quotaType": "MonthlyUploads", "limit": 50, "used": 50 }
}
HTTP Status Codes
| Code | Meaning | Common Cause |
|---|---|---|
| 200 | OK | Request succeeded |
| 201 | Created | Resource created successfully |
| 400 | Bad Request | Validation error or malformed payload |
| 401 | Unauthorized | Missing or invalid authentication |
| 402 | Payment Required | Plan quota exceeded |
| 403 | Forbidden | Insufficient permissions |
| 404 | Not Found | Resource does not exist or is inaccessible |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Server Error | Unexpected server error |
Code Examples
Complete integration examples for common workflows.
Upload a Statement
Upload a bank statement PDF for extraction. The file must be sent as multipart/form-data.
curl -X POST https://api.flvxtract.com/api/statements/upload \
-H "X-Consumer-Key: ${CONSUMER_KEY}" \
-H "X-Consumer-Secret: ${CONSUMER_SECRET}" \
-F "file=@/path/to/statement.pdf"
# Response: { "id": "...", "status": "Processing", ... }
Poll for Results
After uploading, poll until status === 'Completed'. Or use webhooks to be notified automatically.
async function waitForCompletion(statementId, headers) {
while (true) {
const stmt = await (
await fetch(`https://api.flvxtract.com/api/statements/${statementId}`, { headers })
).json();
if (stmt.status === 'Completed' || stmt.status === 'Failed') return stmt;
await new Promise(r => setTimeout(r, 5000));
}
}
const result = await waitForCompletion(statementId, headers);
if (result.status === 'Completed') {
const analysis = await (
await fetch(`https://api.flvxtract.com/api/statements/${statementId}/analysis`, { headers })
).json();
console.log('Transactions:', analysis.header.totalTransactions);
}
Consent Request Flow
Create a consumer data request, wait for approval, then fetch approved statements.
# 1) Create data request
REQ=$(curl -s -X POST https://api.flvxtract.com/api/consent/requests \
-H "X-Consumer-Key: ${CONSUMER_KEY}" \
-H "X-Consumer-Secret: ${CONSUMER_SECRET}" \
-H "Content-Type: application/json" \
-d '{"consumerEmail":"consumer@example.com","scope":90,"expiryDays":14}')
echo "Share: $(echo $REQ | jq -r '.joinLink')"
# 2) After consumer approves — list statements
REQUEST_ID=$(echo $REQ | jq -r '.requestId')
curl https://api.flvxtract.com/api/consent/$REQUEST_ID/statements \
-H "X-Consumer-Key: ${CONSUMER_KEY}" \
-H "X-Consumer-Secret: ${CONSUMER_SECRET}"
Full Integration Flow
Complete end-to-end: upload, wait for processing, retrieve results.
const fs = require('fs');
const HEADERS = {
'X-Consumer-Key': process.env.FLVXTRACT_CONSUMER_KEY,
'X-Consumer-Secret': process.env.FLVXTRACT_CONSUMER_SECRET,
};
async function processStatement(filePath) {
// 1. Upload
const form = new FormData();
form.append('file', fs.createReadStream(filePath));
const { id } = await (await fetch('https://api.flvxtract.com/api/statements/upload', {
method: 'POST', headers: { ...HEADERS, ...form.getHeaders() }, body: form,
})).json();
// 2. Wait
let status = 'Processing';
while (status === 'Processing') {
await new Promise(r => setTimeout(r, 5000));
({ status } = await (await fetch(`https://api.flvxtract.com/api/statements/${id}`, { headers: HEADERS })).json());
}
if (status === 'Failed') throw new Error('Processing failed');
// 3. Get analysis
const analysis = await (await fetch(`https://api.flvxtract.com/api/statements/${id}/analysis`, { headers: HEADERS })).json();
return {
id,
bankName: analysis.header?.bankName,
transactions: analysis.header?.totalTransactions,
cashFlowScore: analysis.lendingMetrics?.cashFlowSufficiencyScore,
debtToIncome: analysis.lendingMetrics?.debtToIncomeRatio,
};
}
processStatement('./statement.pdf').then(console.log);