← Back to blog
·8 min read

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 statementbank statement parserindian bank apidocument extractionfinancial document aifintech apitypescriptai agent

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:

  • Mixed currency rows — INR transactions alongside USD/GBP for NRI accounts
  • UPI reference IDs embedded in the narration field with no consistent delimiter
  • Page-break transactions — a single debit split across two pages with a "continued" footer
  • Password protection on statements downloaded from the mobile app
  • 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 like REV/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. Set type: "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.