← Back to blog
·9 min read

Bandhan Bank Statement Parser: Extract JSON with AI

Parse Bandhan Bank PDF statements from BFILite or internet banking into structured JSON with AI. Handles NACH codes, microfinance transactions, and East India formats.

bandhan bank statementbank statement parserdocument extractionai agentindian fintechmicrofinancetypescriptlekha api

Bandhan Bank is one of India's fastest-growing private sector banks — over 35 million customers, 6,000+ banking outlets, and a customer base that skews heavily toward East India, rural borrowers, and microfinance clients. If you are building a lending, KYC, or income verification product that serves this segment, parsing Bandhan Bank statements is non-negotiable.

The problem: Bandhan's statements come in at least three distinct formats depending on whether the customer used BFILite (their mobile app), internet banking, or a branch-printed passbook. Each format has quirks that break standard OCR — NACH mandate codes, microfinance loan codes, Bengali-inflected branch names, and 16-digit account numbers that overlap with transaction reference codes.

This guide shows how to reliably extract structured JSON from any Bandhan Bank statement using Lekha's API, with TypeScript examples you can drop straight into a production workflow.

Why Bandhan Bank Statements Are Hard to Parse

Bandhan Bank's statement complexity comes from its origin as a microfinance institution (MFI). Before becoming a scheduled commercial bank in 2015, Bandhan was one of India's largest MFIs. That heritage shows up in the statements:

Multiple product lines in a single statement. A typical Bandhan customer might have a savings account, a micro-business loan, and a NACH mandate for insurance premiums — all reflected in the same PDF. Transaction descriptions mix savings activity with repayment codes like LOAN-EMI/BBANKBFL/0000123456 and NACH debits formatted as NACH-DEBIT/NACH00000123456. Regional branch codes. Bandhan's stronghold is West Bengal, Assam, Odisha, and the Northeast. Branch codes follow a regional prefix system (KOL for Kolkata, GWH for Guwahati) that appears in transaction references and can confuse parsers expecting a uniform IFSC-only format. BFILite vs internet banking layout. The BFILite mobile app generates compact, narrow-column statements. Internet banking produces a wider format with an additional "Cheque No" column. Branch-printed statements often have watermarks and non-standard fonts. A regex-based parser tuned for one breaks on the others. Microfinance transaction codes. Bandhan uses internal codes for group lending (JLG/), individual loans (IL/), and doorstep collections (DSC/). These appear in the narration column and carry information (loan ID, collection cycle) that matters for credit assessment but requires semantic understanding to extract correctly.

Getting Started with Lekha

Install the Lekha SDK and set your API key:

npm install @lekha/client
import { LekhaClient } from "@lekha/client";

const lekha = new LekhaClient({ apiKey: process.env.LEKHA_API_KEY, });

Get your API key at lekhadev.com. The free tier covers 50 documents per month — enough to prototype a complete Bandhan Bank parsing workflow.

Extract Your First Bandhan Bank Statement

Lekha's classifier automatically detects the bank and statement format. You pass the PDF as a Buffer and Lekha returns structured JSON:

import { readFileSync } from "fs";

async function parseBandhanStatement(pdfPath: string) { const pdf = readFileSync(pdfPath);

const result = await lekha.extract({ document: pdf, type: "bank_statement", });

if (!result.success) { throw new Error(Extraction failed: ${result.error.message}); }

return result.data; }

const statement = await parseBandhanStatement("./bandhan_statement.pdf");

console.log({ bank: statement.bank_name, // "Bandhan Bank" account: statement.account_number, // "30010012345678" (16-digit) period: { from: statement.period_from, // "2026-04-01" to: statement.period_to, // "2026-06-30" }, opening_balance: statement.opening_balance, // 12450.00 closing_balance: statement.closing_balance, // 28310.50 transactions: statement.transactions.length, // 47 });

The response normalises account numbers, ISO 8601 dates, and numeric amounts regardless of which Bandhan format you send.

Handle NACH and Loan Repayment Transactions

NACH debits and microfinance loan repayments are the transactions that matter most in a lending or credit assessment workflow. Lekha extracts and labels them so you can filter without regex:

interface Transaction {
  date: string;
  description: string;
  amount: number;
  type: "credit" | "debit";
  category: string;
  reference?: string;
}

function analyzeRepayments(transactions: Transaction[]) { // NACH debits for EMI or insurance const nachDebits = transactions.filter( (t) => t.type === "debit" && t.category === "nach_emi", );

// Microfinance loan repayments (JLG or IL collections) const loanRepayments = transactions.filter( (t) => t.type === "debit" && t.category === "loan_repayment", );

// Salary or business income credits const incomeCredits = transactions.filter( (t) => t.type === "credit" && t.category === "salary", );

const totalNachLoad = nachDebits.reduce((sum, t) => sum + t.amount, 0); const totalRepayments = loanRepayments.reduce((sum, t) => sum + t.amount, 0); const totalIncome = incomeCredits.reduce((sum, t) => sum + t.amount, 0);

return { nach_monthly_obligation: totalNachLoad / 3, // quarterly window loan_repayments_total: totalRepayments, income_total: totalIncome, fixed_obligation_ratio: (totalNachLoad + totalRepayments) / totalIncome, }; }

const { transactions } = statement; const repaymentSummary = analyzeRepayments(transactions);

console.log(repaymentSummary); // { // nach_monthly_obligation: 2340.00, // loan_repayments_total: 7020.00, // income_total: 32000.00, // fixed_obligation_ratio: 0.285 // }

A fixed obligation ratio above 0.50 is a common underwriting red flag for MSME and microfinance lenders. This logic runs in milliseconds rather than the minutes it takes a human analyst to tally NACH lines.

Build a Bandhan Bank Income Verification Workflow

For a lending or BNPL use case, you typically need to verify three things: income regularity, existing loan burden, and account stability. Here is a complete workflow:

import { LekhaClient } from "@lekha/client";

const lekha = new LekhaClient({ apiKey: process.env.LEKHA_API_KEY });

interface IncomeVerificationResult { verified: boolean; monthly_income_estimate: number; existing_obligations: number; available_income: number; risk_flags: string[]; }

async function verifyBandhanAccountHolder( statementBuffer: Buffer, ): Promise { const result = await lekha.extract({ document: statementBuffer, type: "bank_statement", });

if (!result.success) { return { verified: false, monthly_income_estimate: 0, existing_obligations: 0, available_income: 0, risk_flags: [Extraction failed: ${result.error.code}], }; }

const { transactions, period_from, period_to } = result.data;

// Calculate period length in months const start = new Date(period_from); const end = new Date(period_to); const months = (end.getFullYear() - start.getFullYear()) * 12 + (end.getMonth() - start.getMonth()) + 1;

// Sum income credits const incomeCategories = ["salary", "business_income", "transfer_in"]; const totalIncome = transactions .filter((t) => t.type === "credit" && incomeCategories.includes(t.category)) .reduce((sum, t) => sum + t.amount, 0);

// Sum fixed obligations const obligationCategories = ["nach_emi", "loan_repayment", "emi"]; const totalObligations = transactions .filter( (t) => t.type === "debit" && obligationCategories.includes(t.category), ) .reduce((sum, t) => sum + t.amount, 0);

const monthlyIncome = totalIncome / months; const monthlyObligations = totalObligations / months;

// Risk flags const flags: string[] = []; if (monthlyObligations / monthlyIncome > 0.5) flags.push("high_obligation_ratio"); if (result.data.closing_balance < monthlyIncome * 0.1) flags.push("low_balance_buffer"); if (months < 3) flags.push("insufficient_history");

// Check for bounced NACH (returned debits) const bouncedNach = transactions.filter( (t) => t.category === "nach_return" || t.category === "ecs_return", ); if (bouncedNach.length > 0) flags.push(nach_bounced_${bouncedNach.length}x);

return { verified: flags.length === 0, monthly_income_estimate: Math.round(monthlyIncome), existing_obligations: Math.round(monthlyObligations), available_income: Math.round(monthlyIncome - monthlyObligations), risk_flags: flags, }; }

This handles the edge cases that matter for Bandhan's customer segment: NACH bounces (common among customers with irregular income), thin balance buffers, and high pre-existing loan burdens from group lending cycles.

Handling Password-Protected and Scanned Statements

Bandhan Bank's internet banking portal allows customers to download password-protected PDFs. BFILite-generated statements are generally unprotected. Branch-printed passbooks are sometimes scanned by the customer and submitted as image PDFs.

Lekha handles all three:

async function parseWithFallback(buffer: Buffer, password?: string) {
  const result = await lekha.extract({
    document: buffer,
    type: "bank_statement",
    options: {
      password: password, // passed through for protected PDFs
    },
  });

if (!result.success && result.error.code === "PASSWORD_REQUIRED") { throw new Error( "Statement is password-protected. Request password from user.", ); }

if (!result.success && result.error.code === "LOW_QUALITY_SCAN") { // Lekha still attempts extraction but flags confidence console.warn("Low-quality scan detected. Review extracted data manually."); }

return result; }

For scanned documents, Lekha applies vision AI rather than text extraction — the same approach that makes it accurate on branch-printed statements where Tesseract struggles with non-standard fonts and watermarks.

Production Error Handling and Retries

import { LekhaError } from "@lekha/client";

async function extractWithRetry( buffer: Buffer, maxRetries = 3, ): Promise { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { const result = await lekha.extract({ document: buffer, type: "bank_statement", });

if (result.success) return result.data;

// Non-retryable errors if ( ["UNSUPPORTED_FORMAT", "PASSWORD_REQUIRED", "CORRUPT_FILE"].includes( result.error.code, ) ) { throw new LekhaError(result.error); }

// Retryable: rate limit, timeout if (attempt < maxRetries) { await new Promise((r) => setTimeout(r, 1000 * attempt)); continue; }

throw new LekhaError(result.error); } catch (err) { if (attempt === maxRetries) throw err; await new Promise((r) => setTimeout(r, 1000 * attempt)); } }

throw new Error("Max retries exceeded"); }

Testing Your Integration

Lekha's playground lets you upload a real Bandhan Bank PDF and inspect the JSON output before writing a single line of code. It's the fastest way to verify that Lekha handles your specific statement format — BFILite, internet banking, or branch-printed.

For automated testing, use fixture-based tests:

import { describe, it, expect } from "vitest";
import { readFileSync } from "fs";

describe("Bandhan Bank extraction", () => { it("extracts account number correctly", async () => { const pdf = readFileSync("tests/fixtures/bandhan_bflite_sample.pdf"); const result = await lekha.extract({ document: pdf, type: "bank_statement", });

expect(result.success).toBe(true); expect(result.data.bank_name).toBe("Bandhan Bank"); expect(result.data.account_number).toMatch(/^\d{16}$/); });

it("labels NACH debits correctly", async () => { const pdf = readFileSync("tests/fixtures/bandhan_internet_sample.pdf"); const result = await lekha.extract({ document: pdf, type: "bank_statement", });

const nachDebits = result.data.transactions.filter( (t) => t.category === "nach_emi", ); expect(nachDebits.length).toBeGreaterThan(0); nachDebits.forEach((t) => { expect(t.type).toBe("debit"); expect(t.amount).toBeGreaterThan(0); }); }); });

Frequently Asked Questions

Does Lekha support Bandhan Bank's BFILite app statement format? Yes. Lekha's classifier recognises BFILite-generated PDFs and applies the correct extraction logic. BFILite statements use a narrower column layout than internet banking statements, but both are supported without any configuration on your end. How does Lekha handle Bengali text in Bandhan Bank statements? Bandhan's East India branches sometimes generate statements with Bengali branch names or bilingual headers. Lekha uses vision AI rather than pure text extraction, so it reads the document visually and is not confused by Devanagari or Bengali script the way a text-based parser would be. What is the accuracy on microfinance transaction codes like JLG and NACH? Lekha achieves over 95% field-level accuracy on Bandhan Bank statements, including NACH debits, JLG repayments, and DSC (doorstep collection) credits. The category labels — nach_emi, loan_repayment, nach_return — are consistent across all statement formats. Can Lekha extract Bandhan Bank credit card statements? Bandhan Bank offers credit cards through select partnerships, but these are less common in their core microfinance segment. Lekha supports credit card statement extraction; use type: "credit_card_statement" for those documents.

Next Steps

Bandhan Bank's customer base — small business owners, rural households, first-time banking customers — represents a segment that AI-powered lending products can serve better than traditional underwriting. Automated statement parsing removes the manual review bottleneck that slows down exactly the kind of high-volume, small-ticket lending Bandhan's segment needs.

Get started:
  • Sign up for a free API key at lekhadev.com
  • Try the interactive playground with your own Bandhan Bank PDFs
  • Read the full API documentation for all supported document types and response schemas
  • Lekha supports 20+ Indian banks out of the box. If you are processing statements from multiple banks in your workflow, a single API call handles classification and extraction automatically — no bank-specific configuration required.