Axis Bank Statement Parser: Extract Structured JSON with AI
Parse any Axis Bank statement—savings, salary, or credit card—into structured JSON with AI. Handles all PDF formats, multi-page exports, and edge cases.
Axis Bank is India's third-largest private sector bank, with over 30 million customers generating statements in at least five distinct PDF formats. If you're building a lending platform, personal finance app, or AI agent that needs to read Axis Bank statements, you've likely hit the same wall: no two statements look quite the same, and traditional OCR fails on scanned PDFs.
This guide shows you how to extract structured JSON from any Axis Bank statement using Lekha — a financial document intelligence API built for Indian fintech developers.
Why Axis Bank Statements Are Hard to Parse
Axis Bank issues statements across several product lines and delivery channels. Each has its own layout, encoding, and quirks.
| Statement type | Delivery channel | Common issues | | ------------------- | -------------------- | --------------------------------------- | | Savings / Current | Net banking PDF | Variable column widths, multi-page | | Salary account | Corporate portal | Employer header overlaid on bank header | | Credit card | Email attachment | Reward points table breaks parsers | | NRI account | Branch / courier | Scanned, rotated pages | | Burgundy / Priority | Relationship manager | Merged cells, custom branding |
Beyond layout variation, Axis statements routinely include:
A regex or template-based parser breaks the moment Axis updates its layout. Vision AI reads the document the same way a human does — structure-agnostic, format-resilient.
What You Get After Extraction
Lekha returns a consistent JSON schema regardless of which Axis Bank format you send:
{
"document_type": "bank_statement",
"bank": "Axis Bank",
"account": {
"holder_name": "Priya Mehta",
"account_number": "XXXXXXXX3421",
"account_type": "Savings",
"ifsc": "UTIB0001234",
"branch": "Andheri West, Mumbai"
},
"period": {
"from": "2026-01-01",
"to": "2026-03-31"
},
"summary": {
"opening_balance": 42500.0,
"closing_balance": 87320.5,
"total_credits": 215000.0,
"total_debits": 170179.5
},
"transactions": [
{
"date": "2026-01-03",
"narration": "UPI/CR/PHONEPE/9876543210/Salary",
"reference": "426301234567",
"debit": null,
"credit": 85000.0,
"balance": 127500.0,
"category": "salary",
"channel": "UPI"
}
]
}
Dates are always ISO 8601. Amounts are always numbers — never strings like "₹85,000.00". The category field is inferred by Lekha automatically.
Quickstart: Parse an Axis Bank Statement in TypeScript
Install the Lekha SDK and send your first document in under two minutes.
bun add @lekha/sdk
or: npm install @lekha/sdk
import Lekha from "@lekha/sdk";
import { readFileSync } from "fs";
const lekha = new Lekha({ apiKey: process.env.LEKHA_API_KEY });
const pdf = readFileSync("axis-statement-jan-mar-2026.pdf");
const result = await lekha.extract({
document: pdf,
type: "bank_statement",
});
console.log(result.data.summary.closing_balance); // 87320.5
console.log(result.data.transactions.length); // 47
That's it. Lekha auto-detects the bank, the statement type, and the period. You don't need to specify Axis Bank explicitly — the classifier handles it.
Try it live at lekhadev.com/playground without writing any code.
Handling Password-Protected PDFs
Axis Bank's mobile app exports password-protected statements by default. The password is usually the customer's date of birth (DDMMYYYY) or a custom PIN. Pass it with the password option:
const result = await lekha.extract({
document: pdf,
type: "bank_statement",
password: "01051990", // DOB: 01 May 1990
});
Lekha decrypts the document server-side, processes it in memory, and never persists it to disk — a requirement for DPDP compliance.
Filtering and Aggregating Transactions
Once you have the JSON, standard TypeScript array methods give you everything a financial app needs:
const { transactions } = result.data;
// Total UPI credits in the period
const upiIncome = transactions
.filter((t) => t.channel === "UPI" && t.credit !== null)
.reduce((sum, t) => sum + (t.credit ?? 0), 0);
// Monthly spend breakdown
const monthlyDebits = transactions
.filter((t) => t.debit !== null)
.reduce>((acc, t) => {
const month = t.date.slice(0, 7); // "2026-01"
acc[month] = (acc[month] ?? 0) + (t.debit ?? 0);
return acc;
}, {});
// Salary detection (useful for lending underwriting)
const salaryCredits = transactions.filter((t) => t.category === "salary");
const avgMonthlySalary =
salaryCredits.reduce((sum, t) => sum + (t.credit ?? 0), 0) /
Math.max(salaryCredits.length, 1);
console.log({ upiIncome, monthlyDebits, avgMonthlySalary });
Building a Loan Eligibility Check with Axis Statements
A common use case is automated loan underwriting. Here's a minimal agent that reads an Axis Bank statement and returns a go/no-go decision:
import Lekha from "@lekha/sdk"; import Anthropic from "@anthropic-ai/sdk";, }, ], });const lekha = new Lekha({ apiKey: process.env.LEKHA_API_KEY }); const anthropic = new Anthropic();
async function checkLoanEligibility(statementPdf: Buffer, loanAmount: number) { // Step 1: Extract structured data const { data } = await lekha.extract({ document: statementPdf, type: "bank_statement", });
const { summary, transactions } = data;
// Step 2: Compute financial signals const salaryCredits = transactions.filter((t) => t.category === "salary"); const avgSalary = salaryCredits.reduce((s, t) => s + (t.credit ?? 0), 0) / Math.max(salaryCredits.length, 1);
const avgMonthlyBalance = transactions.reduce((s, t) => s + t.balance, 0) / transactions.length;
const emiPayments = transactions.filter((t) => /emi|loan|equated/i.test(t.narration), ); const monthlyEmiOutflow = emiPayments.reduce((s, t) => s + (t.debit ?? 0), 0) / Math.max(new Set(emiPayments.map((t) => t.date.slice(0, 7))).size, 1);
// Step 3: Ask Claude to reason over the signals const message = await anthropic.messages.create({ model: "claude-sonnet-4-6", max_tokens: 512, messages: [ { role: "user", content:
Evaluate loan eligibility. Loan requested: ₹${loanAmount.toLocaleString("en-IN")} Average monthly salary: ₹${avgSalary.toFixed(0)} Average monthly balance: ₹${avgMonthlyBalance.toFixed(0)} Existing EMI outflow/month: ₹${monthlyEmiOutflow.toFixed(0)} Closing balance: ₹${summary.closing_balance}Return JSON: { eligible: boolean, reason: string, max_emi: number }
return JSON.parse((message.content[0] as { text: string }).text); }
This pattern — extract with Lekha, reason with Claude — keeps each layer doing what it does best. Lekha handles the messy PDF parsing; Claude handles the financial judgment.
Multi-Statement Analysis (6-Month View)
Many NBFC credit policies require a 6-month bank statement. Lekha processes each month in parallel and returns a unified view:
import { readFileSync } from "fs";
import Lekha from "@lekha/sdk";
const lekha = new Lekha({ apiKey: process.env.LEKHA_API_KEY });
const statementFiles = [
"axis-oct-2025.pdf",
"axis-nov-2025.pdf",
"axis-dec-2025.pdf",
"axis-jan-2026.pdf",
"axis-feb-2026.pdf",
"axis-mar-2026.pdf",
];
// Extract all statements in parallel
const results = await Promise.all(
statementFiles.map((file) =>
lekha.extract({
document: readFileSync(file),
type: "bank_statement",
}),
),
);
// Merge transactions across all months
const allTransactions = results.flatMap((r) => r.data.transactions);
// De-duplicate (in case statements overlap by a day at month boundaries)
const seen = new Set();
const uniqueTransactions = allTransactions.filter((t) => {
const key = ${t.date}-${t.reference}-${t.debit ?? t.credit};
if (seen.has(key)) return false;
seen.add(key);
return true;
});
console.log(Total transactions over 6 months: ${uniqueTransactions.length});
Common Axis Bank Statement Edge Cases
Reversed transactions: Axis sometimes reverses a debit on the same day with a narration likeREV/UPI/.... Lekha flags these with "type": "reversal" so you can exclude them from spend analysis.
Interim statements: If your user downloads a mid-month statement, the opening balance won't match the previous month's closing balance. Always use summary.opening_balance from the document rather than computing it from transactions.
Salary credit timing: Axis salary accounts often credit at 11:59 PM on the last working day. If you're computing months, use t.date (ISO date) not the narration date, which may say the employer's payroll run date.
NRI forex rows: NRI statements include a "currency" field on transactions. Filter to currency === "INR" before summing rupee amounts.
What Lekha Does Not Store
Axis Bank statements contain sensitive personal data — account numbers, PAN-linked transactions, salary information. Lekha processes every document in-memory and returns the structured JSON. No PDF is written to disk, no raw document is retained after the API call completes.
This zero-persistence architecture is required for compliance with India's Digital Personal Data Protection (DPDP) Act. See the Lekha docs for the full data handling policy.
API Reference Cheatsheet
| Parameter | Type | Description |
| ------------- | -------------------------- | --------------------------------------------- |
| document | Buffer \| Blob \| base64 | The PDF or image file |
| type | "bank_statement" | Document type hint (optional — auto-detected) |
| password | string | PDF password if protected |
| pages | number[] | Extract specific pages only |
| webhook_url | string | Callback URL for async processing |
Full reference at lekhadev.com/docs.
FAQ
Does Lekha support all Axis Bank statement formats? Yes. Lekha's vision AI model reads Axis Bank savings, current, salary, credit card, NRI, and Priority/Burgundy statements. It handles both digitally-generated PDFs and scanned images. How accurate is the transaction extraction? Lekha achieves 98.2% field-level accuracy on Axis Bank statements in our benchmark suite. The main failure mode is handwritten annotations added by branch staff, which are rare in digital statements. Can I extract credit card statements separately from bank statements? Axis Bank credit card statements are a distinct format. Settype: "credit_card_statement" in the API call, or omit it and let auto-detection handle it. The returned schema includes reward_points, minimum_due, and payment_due_date fields specific to credit cards.
What happens if the statement is corrupted or unreadable?
Lekha returns a structured error with success: false and an error.code like DOCUMENT_UNREADABLE or INSUFFICIENT_QUALITY. Your code should handle these gracefully rather than treating a null result as a successful extraction.
Axis Bank is one of India's fastest-growing private banks and a core data source for credit underwriting, personal finance, and KYC workflows. With Lekha handling the extraction layer, your agent gets clean, typed JSON in milliseconds — regardless of which Axis statement variant lands in your queue.
Sign up for a free Lekha API key → and extract your first Axis Bank statement in under five minutes.