Indian Bank Statement Parser: Extract JSON with AI
Parse Indian Bank and legacy Allahabad Bank statements into structured JSON. Handles both PDF formats, Tamil narrations, pension credits, and ECS patterns.
Indian Bank is one of India's oldest public sector banks, established in 1907 in Chennai with a legacy spanning South India and a pan-India network of over 6,000 branches. In 2020, Allahabad Bank — founded in 1865 — merged into Indian Bank, creating the seventh-largest PSU bank in the country with more than 40 million active accounts.
If you are building a loan origination platform, an NBFC underwriting engine, or a personal finance tool in India, a significant portion of your users will upload Indian Bank statements. Government employees, pensioners, farmers, and small business owners in Tamil Nadu, Andhra Pradesh, and Telangana are heavy Indian Bank customers. Parsing their statements reliably is not optional — it is a prerequisite for serving this segment.
The challenge: the 2020 Allahabad Bank merger means your pipeline will encounter two entirely different PDF layouts under what users think of as one bank. Add Tamil and Telugu narration text, pension credit patterns, and password-protected net banking exports, and you have a parsing problem that breaks most generic tools.
This guide shows how to handle all of it using Lekha — a financial document intelligence API built specifically for Indian bank formats.
What Makes Indian Bank Statements Hard to Parse
Dual PDF Layouts from the Allahabad Bank Merger
Before the April 2020 merger, Allahabad Bank customers received statements with Allahabad Bank's layout: header logo, account details block, and a transaction table with columns in a specific order. After the merger, the branding changed to "Indian Bank (erstwhile Allahabad Bank)" — but the underlying PDF template continued to be used in many branches for months.
A parser trained only on native Indian Bank statements will fail on Allahabad Bank legacy PDFs. Lekha handles both layouts and normalizes them to the same structured output.
Tamil, Telugu, and Hindi Narration Text
Indian Bank has deep roots in Tamil Nadu and Andhra Pradesh. Rural and semi-urban account statements frequently contain narration text in Tamil script (கா.எ.க.க.) or Telugu (ఎన్ఇఎఫ్టి) alongside English. OCR tools trained on Latin characters either skip these narrations entirely or corrupt the surrounding text, causing transaction boundary detection to fail.
Pension and Government Salary Patterns
Indian Bank processes a large volume of pension credits for retired central and state government employees. These appear as structured narrations:
PENSION PPO/2024/XXXXXXXX TNPSC
DCRG/GRATUITY CREDIT XXXXXXXX
TREASURY SALARY CREDIT TN GOV
Extracting the scheme type, beneficiary reference, and paying authority from these narrations requires semantic understanding rather than pattern matching.
Password-Protected Net Banking Exports
Net banking PDFs downloaded from indianbank.net.in are typically protected. Common passwords follow the format: PAN (uppercase) + date of birth in DDMMYYYY, or just the account number. Any pipeline that does not handle decryption at the ingestion layer will return empty results.
ECS and NACH Debit Codes
Indian Bank uses a dense narration format for recurring mandates:
ECS DR LICI001 2024XX LIC PREMIUM
NACH/DEBIT/HDFCBANK/EMI/LNXXXXXXXX
NACH DR 000000XXXXXXXXXX BAJAJ FIN
Parsing the creditor name, mandate reference, and purpose from these requires training on Indian NACH/ECS narration conventions specifically.
Quickstart: Parse an Indian Bank Statement in 30 Seconds
Install the Lekha SDK:
bun add lekha-sdk
or: npm install lekha-sdk
Then extract a statement in three lines:
import { LekhaClient } from "lekha-sdk";
import { readFileSync } from "fs";
const lekha = new LekhaClient({ apiKey: process.env.LEKHA_API_KEY });
const file = readFileSync("./indian-bank-statement.pdf");
const result = await lekha.extract({
document: file,
filename: "indian-bank-statement.pdf",
documentType: "bank_statement",
});
console.log(result.data);
No document type hinting required — Lekha's classifier detects Indian Bank and Allahabad Bank formats automatically. Try it live at lekhadev.com/playground.
The Structured JSON Output
Lekha returns a normalized JSON object regardless of whether the statement is a native Indian Bank PDF or a legacy Allahabad Bank layout:
{
"bank": "Indian Bank",
"account_number": "XXXXXXXXXX",
"account_type": "Savings",
"account_holder": "RAMESH KUMAR",
"ifsc": "IDIB000XXXX",
"branch": "T. Nagar, Chennai",
"statement_period": {
"from": "2026-04-01",
"to": "2026-09-30"
},
"opening_balance": 42500.0,
"closing_balance": 68300.25,
"currency": "INR",
"transactions": [
{
"date": "2026-04-01",
"description": "PENSION PPO/2024/00123456 TNPSC",
"credit": 38500.0,
"debit": null,
"balance": 81000.0,
"reference": "INB2604010001234",
"transaction_type": "pension_credit",
"mode": "NEFT"
},
{
"date": "2026-04-05",
"description": "NACH DR 000000987654321 LIC PREMIUM",
"credit": null,
"debit": 4200.0,
"balance": 76800.0,
"reference": "NACH20260405001",
"transaction_type": "nach_debit",
"mode": "NACH"
}
],
"summary": {
"total_credits": 231000.0,
"total_debits": 162699.75,
"average_monthly_balance": 58400.5,
"monthly_inflows": [
{ "month": "2026-04", "amount": 38500.0 },
{ "month": "2026-05", "amount": 38500.0 }
]
}
}
All amounts are numbers, never strings. All dates are ISO 8601. This is ready to feed into a credit engine, a personal finance dashboard, or an AI agent without any transformation.
Full TypeScript Integration
Here is a production-ready function that handles file ingestion, extraction, and lightweight error handling:
import { LekhaClient, LekhaError } from "lekha-sdk";
const lekha = new LekhaClient({
apiKey: process.env.LEKHA_API_KEY!,
});
interface BankStatementResult {
accountHolder: string;
bank: string;
averageMonthlyBalance: number;
totalCredits: number;
totalDebits: number;
transactions: Array<{
date: string;
description: string;
credit: number | null;
debit: number | null;
balance: number;
transactionType: string;
}>;
}
export async function parseIndianBankStatement(
fileBuffer: Buffer,
filename: string,
): Promise {
const result = await lekha.extract({
document: fileBuffer,
filename,
documentType: "bank_statement",
});
if (!result.success) {
throw new Error(Extraction failed: ${result.error.message});
}
const data = result.data;
return {
accountHolder: data.account_holder,
bank: data.bank,
averageMonthlyBalance: data.summary.average_monthly_balance,
totalCredits: data.summary.total_credits,
totalDebits: data.summary.total_debits,
transactions: data.transactions.map((tx) => ({
date: tx.date,
description: tx.description,
credit: tx.credit,
debit: tx.debit,
balance: tx.balance,
transactionType: tx.transaction_type,
})),
};
}
Handling the Allahabad Bank Legacy Format
Accounts originally held at Allahabad Bank were migrated to Indian Bank after the 2020 merger. Many customers in Uttar Pradesh, Bihar, and West Bengal still think of their bank as "Allahabad Bank" and upload PDFs that carry the old branding.
You do not need to handle this at the application layer. Lekha's document classifier detects both Indian Bank and Allahabad Bank legacy PDFs and normalizes them to the same output schema. The bank field in the response will read "Indian Bank" regardless of the original template.
If you need to preserve provenance (e.g., for audit trails), check the raw metadata.detected_format field:
const result = await lekha.extract({
document: fileBuffer,
filename: "statement.pdf",
documentType: "bank_statement",
includeMetadata: true,
});
// "indian_bank_v2" or "allahabad_bank_legacy_v1"
console.log(result.metadata.detected_format);
Filtering Pension Credits for Lending Use Cases
Pension credit is a stable, government-backed recurring inflow — exactly what a lender wants to see. Indian Bank accounts belonging to retired government employees often show highly consistent monthly credits, making them excellent lending candidates.
Here is a utility to isolate pension income from the extracted transactions:
function extractPensionIncome(
transactions: BankStatementResult["transactions"],
): { monthlyPension: number; regularityScore: number } {
const pensionCredits = transactions.filter(
(tx) =>
tx.transactionType === "pension_credit" ||
tx.description.match(/PENSION|PPO|DCRG|GRATUITY|TREASURY SALARY/i),
);
if (pensionCredits.length === 0) {
return { monthlyPension: 0, regularityScore: 0 };
}
const totalPension = pensionCredits.reduce(
(sum, tx) => sum + (tx.credit ?? 0),
0,
);
// Months covered by at least one pension credit
const uniqueMonths = new Set(pensionCredits.map((tx) => tx.date.slice(0, 7)))
.size;
// Regularity: what fraction of calendar months had a pension credit
const statementMonths =
Math.ceil(pensionCredits.length / pensionCredits.length) || 1;
const regularityScore = Math.min(uniqueMonths / 6, 1.0); // normalize to 6-month window
return {
monthlyPension: totalPension / Math.max(uniqueMonths, 1),
regularityScore,
};
}
Detecting NACH and ECS Obligations
Before sanctioning a loan, a credit engine must know what recurring obligations already exist on the account. Indian Bank account holders frequently have LIC premium mandates, home loan EMIs, and SIP debits running as NACH mandates.
function detectRecurringObligations(
transactions: BankStatementResult["transactions"],
): Array<{ creditor: string; estimatedMonthly: number; type: string }> {
const nachdebits = transactions.filter(
(tx) =>
tx.debit !== null &&
(tx.transactionType === "nach_debit" ||
tx.description.match(/^(ECS DR|NACH\/DEBIT|NACH DR)/i)),
);
// Group by normalized creditor name
const creditorMap = new Map<
string,
{ total: number; count: number; type: string }
>();
for (const tx of nachdebits) {
// Extract creditor from narration (last meaningful token after mandate ref)
const parts = tx.description.split(/\s+/);
const creditor = parts[parts.length - 1] ?? "UNKNOWN";
const type = tx.description.includes("LIC")
? "insurance"
: tx.description.includes("EMI") || tx.description.includes("LOAN")
? "emi"
: "subscription";
const existing = creditorMap.get(creditor) ?? {
total: 0,
count: 0,
type,
};
creditorMap.set(creditor, {
total: existing.total + (tx.debit ?? 0),
count: existing.count + 1,
type: existing.type,
});
}
return Array.from(creditorMap.entries()).map(([creditor, data]) => ({
creditor,
estimatedMonthly: data.total / Math.max(data.count, 1),
type: data.type,
}));
}
Building an API Route
Here is how you might expose this in a Next.js API route for a loan origination frontend:
// app/api/extract/indian-bank/route.ts
import { NextRequest, NextResponse } from "next/server";
import { LekhaClient } from "lekha-sdk";
const lekha = new LekhaClient({ apiKey: process.env.LEKHA_API_KEY! });
export async function POST(req: NextRequest) {
const formData = await req.formData();
const file = formData.get("statement") as File;
if (!file) {
return NextResponse.json(
{ success: false, error: "No file uploaded" },
{ status: 400 },
);
}
const buffer = Buffer.from(await file.arrayBuffer());
const result = await lekha.extract({
document: buffer,
filename: file.name,
documentType: "bank_statement",
});
if (!result.success) {
return NextResponse.json(
{ success: false, error: result.error.message },
{ status: 422 },
);
}
return NextResponse.json({ success: true, data: result.data });
}
See more patterns in the Lekha docs.
FAQ
Does Lekha support Allahabad Bank PDFs after the merger? Yes. Lekha's classifier detects both native Indian Bank PDFs and legacy Allahabad Bank templates. Both resolve to the same structured JSON output. You do not need to route them separately. How does Lekha handle Tamil and Telugu text in narrations? Lekha uses a vision language model rather than traditional OCR, which means it reads narration context visually rather than character by character. Tamil and Telugu text in narrations is used as context for semantic classification (e.g., identifying government salary credits) even where direct transliteration is not required. Can Lekha extract pension scheme details like PPO number? Yes. For pension credit transactions, Lekha extracts the PPO reference, paying authority, and scheme type from the narration field when present. These appear in thereference and description fields of the transaction object.
What should I do if a statement is password-protected?
Pass the password as a parameter in the Lekha API call. If you do not know the password, Lekha can attempt common Indian bank password patterns (PAN + DOB combinations). See the docs for the password and tryCommonPasswords options.
Does Lekha work with six-month and twelve-month statements?
Yes. Lekha handles multi-page PDFs of any length without truncation. Average monthly balance, monthly inflow summaries, and trend data are all computed across the full statement period. See our multi-page PDF guide for more.
Indian Bank's hybrid customer base — pensioners, government employees, and SME borrowers across southern and eastern India — represents a segment that many fintechs underserve because their document pipeline can not reliably handle the format. With Lekha, that stops being a technical constraint.
Sign up for a free Lekha API key and parse your first Indian Bank or Allahabad Bank statement in under a minute. No credit card required.