← Back to blog
·9 min read

RBL Bank Statement Parser: Extract Structured Data with AI

Parse any RBL Bank statement into structured JSON with AI. Handles all PDF formats, credit card exports, and multi-page statements automatically.

rbl bank statementbank statement parserfinancial document extractionai document parsingrbl bankindian bankingfintech apiai agent

RBL Bank (formerly Ratnakar Bank) has grown into one of India's prominent private sector banks, with over 1.5 crore customers across savings, salary, NRI, current, and credit card accounts. If you're building a lending platform, personal finance app, or credit assessment workflow, you'll regularly encounter RBL Bank statements that need parsing.

This guide walks through exactly how to parse RBL Bank statements into structured JSON using AI — covering every format variation, the quirks that break conventional tools, and the TypeScript code to wire Lekha into your agent.

Why RBL Bank Statements Are Hard to Parse

RBL Bank statements appear straightforward at first glance. In practice, they have several format inconsistencies that trip up rule-based parsers and OCR tools:

  • Dual PDF generators: RBL's net banking portal and mobile app produce different PDF layouts — different column widths, font sizes, and narration truncation thresholds
  • Credit card statements: RBL is a major credit card issuer through partnerships (Bajaj Finserv, Shoprite). These co-branded card statements use completely different templates from savings account statements
  • UPI and NEFT interleaving: RBL encodes NEFT UTR numbers and UPI transaction IDs in the same narration column, often wrapping across two lines when IDs are long
  • Balance sign ambiguity: Older PDF exports use "Cr" and "Dr" suffixes on the balance column rather than signed numbers, which confuses column-detection algorithms
  • Password-protected exports: Many RBL PDF statements are password-protected by default. Standard OCR tools fail silently on these; vision AI models return an error you can act on
  • Traditional OCR (Tesseract, AWS Textract) typically achieves 65–75% accuracy on RBL statements. Vision AI that reads layout holistically — the way a human analyst would — consistently reaches 95%+.

    What Structured Extraction Produces

    A raw RBL Bank PDF becomes typed JSON your application can query immediately:

    {
      "bank": "RBL Bank",
      "account_number": "XXXXXXXX7832",
      "account_type": "Savings",
      "account_holder": "Ankit Desai",
      "ifsc": "RATN0000047",
      "branch": "Vashi, Navi Mumbai",
      "statement_period": {
        "from": "2026-04-01",
        "to": "2026-04-30"
      },
      "currency": "INR",
      "opening_balance": 48250.0,
      "closing_balance": 62140.75,
      "total_credits": 95000.0,
      "total_debits": 81109.25,
      "transactions": [
        {
          "date": "2026-04-03",
          "description": "NEFT/CITIBANK/SALARY APRIL/UTR8843921",
          "type": "credit",
          "amount": 75000.0,
          "balance": 123250.0,
          "category": "salary",
          "reference": "UTR8843921"
        },
        {
          "date": "2026-04-05",
          "description": "UPI/GPAY/9876543210/Rent April",
          "type": "debit",
          "amount": 22000.0,
          "balance": 101250.0,
          "category": "housing",
          "reference": "UPI9283746512"
        }
      ]
    }
    

    Every amount is a number (never a string), every date is ISO 8601, and every transaction is categorised automatically.

    Parsing RBL Statements with Lekha

    Installation

    npm install @lekha/client
    

    or

    bun add @lekha/client

    Basic Extraction

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

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

    async function parseRBLStatement(pdfPath: string) { const pdfBuffer = readFileSync(pdfPath);

    const result = await client.extract({ document: pdfBuffer, documentType: "bank_statement", hint: "RBL Bank", });

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

    const { data } = result;

    console.log(Account: ${data.account_holder}); console.log( Period: ${data.statement_period.from} → ${data.statement_period.to}, ); console.log( Closing balance: ₹${data.closing_balance.toLocaleString("en-IN")}, ); console.log(Transactions: ${data.transactions.length});

    return data; }

    Handling Credit Card Statements

    RBL is a large credit card issuer, and its card statements require a different document type:

    async function parseRBLCreditCard(pdfPath: string) {
      const pdfBuffer = readFileSync(pdfPath);
    

    const result = await client.extract({ document: pdfBuffer, documentType: "credit_card_statement", hint: "RBL Bank", });

    if (!result.success) { throw new Error(result.error.message); }

    const { data } = result;

    return { cardHolder: data.card_holder, cardNumber: data.card_number, statementDate: data.statement_date, totalDue: data.total_amount_due, minimumDue: data.minimum_amount_due, dueDate: data.payment_due_date, creditLimit: data.credit_limit, availableCredit: data.available_credit, transactions: data.transactions, }; }

    Income and EMI Analysis

    The most common use case after extraction is computing net income, identifying EMIs, and flagging salary consistency for lending decisions:

    interface IncomeProfile {
      monthlySalaryCredits: number[];
      averageSalary: number;
      salaryConsistent: boolean;
      activeEmis: { description: string; amount: number; frequency: string }[];
      totalEmiCommitment: number;
      emiToIncomeRatio: number;
    }
    

    function analyseRBLStatement(data: BankStatement): IncomeProfile { const salaryTransactions = data.transactions.filter( (tx) => tx.category === "salary" && tx.type === "credit", );

    const monthlySalaryCredits = salaryTransactions.map((tx) => tx.amount); const averageSalary = monthlySalaryCredits.length > 0 ? monthlySalaryCredits.reduce((a, b) => a + b, 0) / monthlySalaryCredits.length : 0;

    // Salary is "consistent" if no month varies more than 10% from the average const salaryConsistent = monthlySalaryCredits.every( (amount) => Math.abs(amount - averageSalary) / averageSalary < 0.1, );

    // Detect recurring debits (EMIs, subscriptions) const debitGroups = new Map(); for (const tx of data.transactions.filter((tx) => tx.type === "debit")) { const key = tx.description .replace(/\d{4,}/g, "") .trim() .slice(0, 40); const existing = debitGroups.get(key) ?? []; debitGroups.set(key, [...existing, tx.amount]); }

    const activeEmis = Array.from(debitGroups.entries()) .filter(([, amounts]) => amounts.length >= 2) .map(([description, amounts]) => ({ description, amount: Math.round(amounts.reduce((a, b) => a + b, 0) / amounts.length), frequency: "monthly", }));

    const totalEmiCommitment = activeEmis.reduce( (sum, emi) => sum + emi.amount, 0, ); const emiToIncomeRatio = averageSalary > 0 ? totalEmiCommitment / averageSalary : 0;

    return { monthlySalaryCredits, averageSalary, salaryConsistent, activeEmis, totalEmiCommitment, emiToIncomeRatio, }; }

    Handling Multi-Month Statements

    RBL's net banking export supports up to 12 months in a single PDF. For income verification in lending, you'll often need 3–6 months of history. Lekha handles multi-page statements natively — all transactions are returned in a single transactions array, sorted chronologically.

    async function analyseMultiMonthStatement(pdfPath: string) {
      const data = await parseRBLStatement(pdfPath);
    

    // Group transactions by month const byMonth = new Map(); for (const tx of data.transactions) { const month = tx.date.slice(0, 7); // "YYYY-MM" const existing = byMonth.get(month) ?? []; byMonth.set(month, [...existing, tx]); }

    // Compute net income per month const monthlyNetIncome = Array.from(byMonth.entries()).map(([month, txs]) => { const credits = txs .filter((t) => t.type === "credit") .reduce((s, t) => s + t.amount, 0); const debits = txs .filter((t) => t.type === "debit") .reduce((s, t) => s + t.amount, 0); return { month, credits, debits, net: credits - debits }; });

    return { statementPeriod: data.statement_period, monthlyNetIncome, averageMonthlyNet: monthlyNetIncome.reduce((s, m) => s + m.net, 0) / monthlyNetIncome.length, }; }

    RBL-Specific Format Quirks

    Credit Card Co-Brand Statements

    RBL issues co-branded cards with Bajaj Finserv, ShopRite, and other partners. These statements carry the partner's branding on the first page but still follow RBL's underlying template. Lekha detects this from the IFSC and card prefix, so you don't need to specify the co-brand variant.

    Salary Account vs. Regular Savings

    RBL salary accounts (offered in partnership with many corporates) include an employer code in the account metadata. Lekha surfaces this in data.account_variant so you can distinguish salary accounts from regular savings without parsing narrations.

    Password-Protected PDFs

    RBL frequently password-protects downloaded statements. Pass the password via the documentPassword field:

    const result = await client.extract({
      document: pdfBuffer,
      documentType: "bank_statement",
      documentPassword: "01011990", // typically DOB in DDMMYYYY format
    });
    

    If the password is wrong, result.success will be false with error.code === "DOCUMENT_ENCRYPTED" — prompt the user to re-enter their statement password.

    Building a Loan Eligibility Check

    Here's a complete example: take an RBL Bank statement, extract it, run the income analysis, and return a simple eligibility verdict:

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

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

    interface EligibilityResult { eligible: boolean; reason: string; maxLoanAmount: number; suggestedEmi: number; }

    async function checkLoanEligibility( pdfBuffer: Buffer, requestedAmount: number, tenureMonths: number, ): Promise { const result = await client.extract({ document: pdfBuffer, documentType: "bank_statement", });

    if (!result.success) { return { eligible: false, reason: Document extraction failed: ${result.error.message}, maxLoanAmount: 0, suggestedEmi: 0, }; }

    const profile = analyseRBLStatement(result.data);

    // Standard underwriting rules const MAX_FOIR = 0.5; // Fixed Obligation to Income Ratio cap const RATE = 0.14 / 12; // 14% p.a. monthly rate const remainingFOIR = MAX_FOIR - profile.emiToIncomeRatio;

    if (remainingFOIR <= 0) { return { eligible: false, reason: "Existing EMI obligations exceed 50% of income", maxLoanAmount: 0, suggestedEmi: 0, }; }

    if (!profile.salaryConsistent) { return { eligible: false, reason: "Irregular income detected — salary varies by more than 10% across months", maxLoanAmount: 0, suggestedEmi: 0, }; }

    const maxEmi = profile.averageSalary * remainingFOIR; const maxLoan = maxEmi * ((1 - Math.pow(1 + RATE, -tenureMonths)) / RATE); const requestedEmi = (requestedAmount * RATE) / (1 - Math.pow(1 + RATE, -tenureMonths));

    if (requestedAmount > maxLoan) { return { eligible: false, reason: Requested amount ₹${requestedAmount.toLocaleString("en-IN")} exceeds maximum eligible ₹${Math.round(maxLoan).toLocaleString("en-IN")}, maxLoanAmount: Math.round(maxLoan), suggestedEmi: Math.round(maxEmi), }; }

    return { eligible: true, reason: "Applicant meets all eligibility criteria", maxLoanAmount: Math.round(maxLoan), suggestedEmi: Math.round(requestedEmi), }; }

    Accuracy Benchmarks

    | Document Type | OCR Accuracy | Lekha (Vision AI) | | ----------------------------- | ------------ | ----------------- | | RBL Savings — net banking PDF | 72% | 97% | | RBL Salary Account | 68% | 96% | | RBL Credit Card Statement | 61% | 95% | | RBL NRI Account (NRE/NRO) | 55% | 94% | | Password-protected PDF | 0% | 94% |

    Accuracy is measured against manually verified ground-truth datasets of 500+ statements per type.

    FAQ

    Does Lekha support all RBL Bank account types? Yes — savings, salary, current, NRI (NRE/NRO), and credit card statements are all supported. Pass documentType: "bank_statement" for account statements and documentType: "credit_card_statement" for card statements. How does Lekha handle RBL's password-protected PDFs? Pass the documentPassword field in your extract call. RBL typically uses the account holder's date of birth in DDMMYYYY format as the default password. If the password fails, the API returns error.code === "DOCUMENT_ENCRYPTED" so you can prompt for the correct one. Can I extract 6 months of RBL statements in one call? Yes. Upload the multi-month PDF directly — Lekha processes all pages and returns a single sorted transactions array covering the full period. For separate monthly PDFs, call extract for each and merge the arrays client-side. What does Lekha return for RBL co-branded credit card statements (Bajaj Finserv, etc.)? The extracted data matches the standard credit card schema. Lekha identifies the issuer as RBL Bank from the card BIN and IFSC metadata, regardless of which partner brand appears on the statement cover page.

    Ready to parse RBL Bank statements in your application? Get your API key at lekhadev.com and test any document live at lekhadev.com/playground. Full API reference is at lekhadev.com/docs.