← Back to blog
·10 min read

Bank of Baroda Statement Parser: AI Extraction Guide

Parse Bank of Baroda PDF statements into structured JSON with AI. Covers BOB World, internet banking, business, and NRI account formats in TypeScript.

bank of barodabank statement parserdocument extractionai agentindian fintechtypescriptstructured data

Bank of Baroda is one of India's largest public sector banks — over 170 million customers, operations in 17 countries, and a document landscape that has given developers headaches for years. BOB statements come in at least five distinct PDF layouts depending on whether they originate from BOB World (mobile), the Baroda Connect internet banking portal, a branch counter, the business banking portal, or the NRI account module. Each layout has its own column arrangement, header language, and transaction description format.

This guide shows how to parse any Bank of Baroda statement into clean, structured JSON using Lekha's API — with TypeScript examples ready to copy into your project.

Why BOB Statements Are Difficult to Parse with Traditional Tools

Rule-based parsers and vanilla OCR tools fail on BOB for the same reasons they fail on other PSU bank statements, plus a few BOB-specific ones:

  • Five distinct PDF templates: BOB World generates narrow-column statements with a different header than the Baroda Connect internet banking PDF. Branch-printed statements are often bilingual. Business statements include GSTIN and CIN fields. NRI statements carry multi-currency amounts.
  • 15–16 digit account numbers: BOB account numbers vary in length by account type. Parsers that expect fixed-width fields truncate or misalign them.
  • Cryptic NEFT/RTGS references: A typical BOB transaction description — NEFT/BARB0SURAT1/CR/HDFC0001234/00012345678 — encodes the originating branch IFSC, counterparty IFSC, and reference number in a single opaque string. Extracting semantic meaning requires understanding context, not just splitting on /.
  • Bilingual content in branch statements: BOB branch-printed statements mix Hindi and English headers. Legacy OCR pipelines trained on Latin scripts produce garbled output for Devanagari columns.
  • Overdraft accounts: BOB overdraft (OD) accounts show balances as negative numbers or with a Dr suffix. Simple numeric extractors get the sign wrong.
  • Vision AI reads each page as an image, understands cross-column relationships, and handles all of these without format-specific rules.

    Quick Start: Parse a BOB Statement in Under a Minute

    import Lekha from "@lekhadev/sdk";
    import { readFileSync } from "fs";
    

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

    const result = await lekha.extract({ document: { content: readFileSync("bob-statement.pdf").toString("base64"), mimeType: "application/pdf", }, documentType: "bank_statement", });

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

    console.log(result.data);

    Lekha auto-detects that the document is a Bank of Baroda statement, identifies the format variant, and returns the same structured schema regardless of which BOB PDF template generated it.

    The Structured JSON You Get Back

    {
      "bank": "Bank of Baroda",
      "account": {
        "number": "XXXX XXXX 1234",
        "type": "savings",
        "holder": "MEENA RAMAKRISHNAN",
        "branch": "Baroda Main Branch, Vadodara",
        "ifsc": "BARB0VADODA",
        "currency": "INR"
      },
      "period": {
        "from": "2026-04-01",
        "to": "2026-06-30"
      },
      "summary": {
        "opening_balance": 28450.0,
        "closing_balance": 54820.75,
        "total_credits": 215000.0,
        "total_debits": 188629.25,
        "transaction_count": 62
      },
      "transactions": [
        {
          "date": "2026-04-02",
          "description": "NEFT from HDFC Bank — Salary April",
          "reference": "BARB0SURAT1CR00001234",
          "type": "credit",
          "amount": 72000.0,
          "balance": 100450.0,
          "category": "salary"
        },
        {
          "date": "2026-04-07",
          "description": "UPI/SWIGGY/9876543210",
          "reference": "426789001234",
          "type": "debit",
          "amount": 640.0,
          "balance": 99810.0,
          "category": "food"
        }
      ]
    }
    

    Every amount is a number. Every date is ISO 8601. Opaque NEFT reference codes are resolved to readable descriptions where the data exists in the PDF. The schema is identical whether you sent a BOB World PDF or a branch-printed statement.

    Handling BOB Account Types

    Bank of Baroda serves retail, MSME, and corporate customers — and each generates a different statement format.

    Savings and Current Accounts

    Standard retail statements from BOB World or Baroda Connect are the most common. Pass them directly — no special options needed.

    const retail = await lekha.extract({
      document: {
        content: readFileSync("bob-savings.pdf").toString("base64"),
        mimeType: "application/pdf",
      },
      documentType: "bank_statement",
    });
    

    Business / Current Account Statements

    BOB business statements include a GSTIN header, sometimes a CIN, and transaction descriptions that reference vendor/buyer GSTINs. Lekha surfaces these in the account object when present:

    const business = await lekha.extract({
      document: {
        content: readFileSync("bob-current-account.pdf").toString("base64"),
        mimeType: "application/pdf",
      },
      documentType: "bank_statement",
    });
    

    const { gstin, cin } = business.data.account as any; console.log(gstin); // "24AABCR1234B1ZQ" console.log(cin); // "U65990GJ2019PTC108312" (if present)

    Overdraft Accounts

    BOB overdraft statements print balances with a Dr suffix to indicate negative balances. Lekha returns these as negative numbers in the balance field — no suffix, no ambiguity:

    // tx.balance will be -45000.00 for a ₹45,000 overdraft, not "45000.00 Dr"
    const overdrawnTxns = result.data.transactions.filter((tx) => tx.balance < 0);
    console.log(${overdrawnTxns.length} transactions in overdraft);
    

    Parsing BOB NRI Account Statements (NRE / NRO)

    NRI statements are where most parsers break entirely. BOB NRE and NRO statements include:

  • Remittance references alongside domestic transaction references
  • FEMA compliance notes
  • Multi-currency credits (USD, GBP, AED remittances shown in INR equivalent)
  • Slightly different branch formats for overseas branches
  • const nriStatement = await lekha.extract({
      document: {
        content: readFileSync("bob-nre-statement.pdf").toString("base64"),
        mimeType: "application/pdf",
      },
      documentType: "bank_statement",
    });
    

    const { data } = nriStatement;

    // account.type will be "nre_savings" or "nro_savings" console.log(data.account.type); // "nre_savings"

    // Remittance transactions carry an fx_amount field for (const tx of data.transactions) { if ((tx as any).fx_amount) { const { fx_amount, fx_currency } = tx as any; console.log( Remittance: ${fx_currency} ${fx_amount} → ₹${tx.amount.toLocaleString("en-IN")}, ); } }

    Building a 6-Month Aggregator for Income Verification

    Lending and credit-underwriting workflows typically need 3–6 months of bank statement data. Here's a pattern for aggregating multiple BOB statements into a single income view:

    import Lekha from "@lekhadev/sdk";
    import { readFileSync, readdirSync } from "fs";
    import path from "path";
    

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

    interface MonthlyIncome { month: string; totalCredits: number; salaryCredits: number; otherCredits: number; }

    async function aggregateBobStatements( statementDir: string, ): Promise { const files = readdirSync(statementDir) .filter((f) => f.endsWith(".pdf")) .map((f) => path.join(statementDir, f));

    // Extract all statements in parallel const extractions = await Promise.all( files.map((file) => lekha.extract({ document: { content: readFileSync(file).toString("base64"), mimeType: "application/pdf", }, documentType: "bank_statement", }), ), );

    // Deduplicate transactions across overlapping statement periods const seen = new Set(); const allTransactions = extractions .flatMap((e) => e.data.transactions) .filter((tx) => { const key = ${tx.date}:${tx.amount}:${tx.reference}; if (seen.has(key)) return false; seen.add(key); return true; }) .filter((tx) => tx.type === "credit");

    // Group by month const byMonth = new Map(); for (const tx of allTransactions) { const month = tx.date.slice(0, 7); // "YYYY-MM" if (!byMonth.has(month)) byMonth.set(month, []); byMonth.get(month)!.push(tx); }

    return Array.from(byMonth.entries()) .sort(([a], [b]) => a.localeCompare(b)) .map(([month, txns]) => ({ month, totalCredits: txns.reduce((sum, tx) => sum + tx.amount, 0), salaryCredits: txns .filter((tx) => tx.category === "salary") .reduce((sum, tx) => sum + tx.amount, 0), otherCredits: txns .filter((tx) => tx.category !== "salary") .reduce((sum, tx) => sum + tx.amount, 0), })); }

    // Usage const income = await aggregateBobStatements("./bob-statements/"); const avgMonthlyIncome = income.reduce((sum, m) => sum + m.salaryCredits, 0) / income.length;

    console.log( Average monthly salary income: ₹${avgMonthlyIncome.toLocaleString("en-IN")}, );

    This is the core of an income verification flow for loan origination. Pair it with an ITR parser for a cross-validated income picture — see Parse Form 16 and ITR for Tax Verification Agents.

    Error Handling

    import Lekha from "@lekhadev/sdk";
    

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

    async function extractBobStatement(pdfBuffer: Buffer) { const result = await lekha.extract({ document: { content: pdfBuffer.toString("base64"), mimeType: "application/pdf", }, documentType: "bank_statement", });

    if (!result.success) { const { code, message } = result.error;

    switch (code) { case "NOT_A_BANK_STATEMENT": // User uploaded a passbook image, a notice letter, etc. throw new Error( "Document is not a bank statement — ask user to re-upload", );

    case "LOW_QUALITY_SCAN": // Branch-printed statements scanned below 150 DPI fail here throw new Error( "Scan quality too low — request a digital PDF from Baroda Connect", );

    case "UNSUPPORTED_FORMAT": // Rare: a BOB subsidiary or overseas branch with an unusual template throw new Error(Format not recognised: ${message});

    default: throw new Error(Extraction failed [${code}]: ${message}); } }

    return result.data; }

    LOW_QUALITY_SCAN is the most common error on BOB statements — branch-printed statements scanned at convenience-store kiosks often come in under 150 DPI. Direct users to download a digital PDF from Baroda Connect or BOB World instead.

    BOB Statement Variants Lekha Handles

    | Format | Source | Notable quirks | | ------------------------------- | ------------------ | --------------------------------- | | BOB World savings | Mobile app | Narrow columns, compressed layout | | Baroda Connect internet banking | baroda.in | Standard layout, machine-readable | | Branch counter / passbook | Printed in-branch | Bilingual headers, often scanned | | Business / current account | Corporate portal | GSTIN/CIN in header, bulk RTGS | | NRE/NRO savings | NRI banking portal | FX amounts, FEMA notations | | BOB Credit Card | BOBcard portal | Separate schema, reward points |

    All variants return the same top-level JSON schema. Your downstream processing doesn't need to branch on statement type.

    Accuracy on BOB Statements

    Across our test corpus of 1,800 Bank of Baroda statements spanning 2020–2026:

    | Metric | Lekha | Tesseract baseline | | ----------------------- | ----- | ------------------ | | Field-level accuracy | 96.8% | 69% | | Transaction count match | 98.9% | 82% | | Amount accuracy | 99.6% | 91% | | Overdraft sign correct | 100% | 34% |

    The overdraft sign accuracy gap is stark — legacy OCR tools strip the Dr suffix and return a positive number, silently flipping the balance sign. Lekha treats Dr-suffixed amounts as negative numbers.

    FAQ

    Does Lekha handle password-protected BOB statements? Yes. BOB World statements are not password-protected by default, but if you encounter a protected PDF, pass the password field in the extract call. BOB does not have a fixed password format like SBI's DOB convention — accept the password from the user and pass it through. Can I extract data from a BOB passbook scan instead of a PDF? Yes — Lekha accepts JPEG, PNG, and WEBP images in addition to PDFs. For multi-page passbook scans, send each page as a separate document and merge the transactions arrays on your end, deduplicating by date + amount + reference. What's the difference between the reference field and the transaction description? The description is the human-readable cleaned version (e.g. "NEFT from HDFC Bank — Salary April"). The reference is the raw reference code from the statement (e.g. BARB0SURAT1CR00001234). Use description for display and reference for deduplication. Does Lekha support BOB statements in regional language? BOB branch statements in Gujarat sometimes include Gujarati headers. Lekha processes the numeric transaction data correctly regardless — amounts, dates, and balances are language-independent. The description field is returned in English even for statements with regional language headers.

    Bank of Baroda statement parsing is solved once the document extraction layer is handled. The accounts-level quirks — overdraft signs, NRI FX amounts, bilingual headers — are the kind of edge cases that take weeks to handle with custom parsers and still break on new PDF templates. Lekha abstracts all of that.

    Try it free at lekhadev.com — 50 documents per month, no credit card required. Test extraction on your own BOB statements at lekhadev.com/playground before writing a line of integration code.