Parse Indian Financial Docs with Mastra and Lekha
Build AI agents that extract structured data from Indian bank statements, salary slips, and ITRs using Mastra and the Lekha API. Full TypeScript walkthrough.
Mastra is the TypeScript-first AI agent framework that has taken the developer community by storm in 2026. It gives you workflows, tools, agents, and memory — all in one cohesive SDK with first-class TypeScript types. If you are already building AI agents in TypeScript and need to process Indian financial documents, plugging Lekha into Mastra takes under 30 minutes.
This guide walks through building a complete financial document analysis agent: it accepts a bank statement, salary slip, or ITR, extracts structured data using Lekha, and returns an AI-generated financial summary — all wired together in Mastra.
Why Mastra + Lekha?
Mastra handles the agentic layer — tools, workflows, LLM orchestration, and memory. Lekha handles the document intelligence layer — turning any Indian financial PDF into clean, typed JSON without you writing a single extraction prompt.
The combination is powerful:
| Layer | Responsibility | Technology | | ------------------- | -------------------------- | ------------------ | | Agent orchestration | Routing, reasoning, memory | Mastra | | Document extraction | PDF → structured JSON | Lekha API | | LLM reasoning | Summarisation, decisions | Claude / GPT-4o | | Transport | REST + streaming | Mastra HTTP server |
Lekha supports bank statements from 40+ Indian banks, salary slips, ITRs, Form 16, CAS statements, CIBIL reports, GST invoices, and balance sheets. Every response is typed JSON with amounts as numbers and dates as ISO 8601 — exactly what Mastra tools expect.
Project Setup
Start a new Mastra project and install the dependencies:
npx create-mastra@latest findoc-agent
cd findoc-agent
bun add @mastra/core zod
Set your environment variables:
# .env
LEKHA_API_KEY=lk_live_your_key_here
ANTHROPIC_API_KEY=sk-ant-...
Get your Lekha API key at lekhadev.com — the free plan handles 50 documents per month.
Step 1: Define a Lekha Tool
Mastra tools are typed functions the agent can call. Define one that accepts a base64-encoded PDF and calls the Lekha extraction endpoint:
// src/mastra/tools/lekha-extract.ts
import { createTool } from "@mastra/core/tools";
import { z } from "zod";
export const lekhaExtractTool = createTool({
id: "lekha-extract",
description:
"Extract structured financial data from an Indian financial document (bank statement, salary slip, ITR, Form 16, CAS, CIBIL, GST invoice). Returns typed JSON.",
inputSchema: z.object({
documentBase64: z
.string()
.describe("Base64-encoded PDF content of the financial document"),
mimeType: z
.enum(["application/pdf", "image/jpeg", "image/png"])
.default("application/pdf"),
}),
outputSchema: z.object({
documentType: z.string(),
data: z.unknown(),
confidence: z.number(),
}),
execute: async ({ context }) => {
const response = await fetch("https://api.lekhadev.com/v1/extract", {
method: "POST",
headers: {
Authorization: Bearer ${process.env.LEKHA_API_KEY},
"Content-Type": "application/json",
},
body: JSON.stringify({
document: context.documentBase64,
mimeType: context.mimeType,
}),
});
if (!response.ok) {
const err = await response.json();
throw new Error(Lekha extraction failed: ${err.error?.message});
}
const result = await response.json();
return {
documentType: result.data.documentType,
data: result.data,
confidence: result.data.confidence ?? 1,
};
},
});
The tool automatically classifies the document — you do not need to tell Lekha whether it is a bank statement or ITR. The classifier handles that.
Step 2: Build the Financial Analysis Agent
With the tool ready, define an agent that uses it:
// src/mastra/agents/financial-analyst.ts
import { Agent } from "@mastra/core/agent";
import { anthropic } from "@ai-sdk/anthropic";
import { lekhaExtractTool } from "../tools/lekha-extract";
export const financialAnalystAgent = new Agent({
name: "financial-analyst",
instructions: You are a financial analyst specialising in Indian personal finance.
When given a financial document:
- Use the lekha-extract tool to extract structured data from it.
- Analyse the extracted data and provide a concise financial summary.
- For bank statements: highlight monthly cash flow, top spending categories, average balance, and any irregular transactions.
- For salary slips: confirm net take-home, deductions breakdown, and effective tax rate.
- For ITRs: summarise total income, tax paid, refund due, and effective rate.
- Always express amounts in Indian numbering (lakhs, crores) with the ₹ symbol.
- Flag any concerns — overdrafts, frequent cash withdrawals, high EMI burden, income-expense mismatches.
Be concise but thorough. The output will be read by a loan officer or financial advisor.
,
model: anthropic("claude-opus-5"),
tools: { lekhaExtractTool },
});
Step 3: Wire it into a Mastra Workflow
For production use cases — like a loan pre-screening pipeline — you want a workflow rather than a bare agent call. Workflows give you retries, branching, and audit logs:
// src/mastra/workflows/document-screening.ts import { createWorkflow, createStep } from "@mastra/core/workflows"; import { z } from "zod"; import { lekhaExtractTool } from "../tools/lekha-extract"; import { financialAnalystAgent } from "../agents/financial-analyst";.trim();const extractStep = createStep({ id: "extract-document", inputSchema: z.object({ documentBase64: z.string(), applicantName: z.string(), }), outputSchema: z.object({ documentType: z.string(), extractedData: z.unknown(), applicantName: z.string(), }), execute: async ({ inputData }) => { const result = await lekhaExtractTool.execute({ context: { documentBase64: inputData.documentBase64, mimeType: "application/pdf", }, runId: "workflow", mastra: undefined as any, });
return { documentType: result.documentType, extractedData: result.data, applicantName: inputData.applicantName, }; }, });
const analyseStep = createStep({ id: "analyse-document", inputSchema: z.object({ documentType: z.string(), extractedData: z.unknown(), applicantName: z.string(), }), outputSchema: z.object({ summary: z.string(), recommendation: z.enum(["proceed", "review", "reject"]), }), execute: async ({ inputData }) => { const prompt =
Applicant: ${inputData.applicantName} Document type: ${inputData.documentType} Extracted data: ${JSON.stringify(inputData.extractedData, null, 2)}Analyse this document and provide:
- A 3-paragraph financial summary
- A recommendation: "proceed" (looks good), "review" (needs human check), or "reject" (red flags found)
Return JSON: { "summary": "...", "recommendation": "proceed|review|reject" }
const response = await financialAnalystAgent.generate(prompt, { output: z.object({ summary: z.string(), recommendation: z.enum(["proceed", "review", "reject"]), }), });
return response.object; }, });
export const documentScreeningWorkflow = createWorkflow({ id: "document-screening", inputSchema: z.object({ documentBase64: z.string(), applicantName: z.string(), }), outputSchema: z.object({ summary: z.string(), recommendation: z.enum(["proceed", "review", "reject"]), }), }) .then(extractStep) .then(analyseStep) .commit();
Step 4: Register and Run
Register everything in your Mastra instance and start the server:
// src/mastra/index.ts
import { Mastra } from "@mastra/core";
import { financialAnalystAgent } from "./agents/financial-analyst";
import { documentScreeningWorkflow } from "./workflows/document-screening";
export const mastra = new Mastra({
agents: { financialAnalystAgent },
workflows: { documentScreeningWorkflow },
});
bun run dev
Mastra server running on http://localhost:4111
Trigger the workflow via the Mastra API:
curl -X POST http://localhost:4111/api/workflows/document-screening/start \
-H "Content-Type: application/json" \
-d '{
"applicantName": "Priya Sharma",
"documentBase64": "'$(base64 -i statement.pdf)'"
}'
Or call it directly from your Next.js or Express server:
import { mastra } from "./mastra";
const run = await mastra.getWorkflow("documentScreeningWorkflow").execute({
inputData: {
applicantName: "Priya Sharma",
documentBase64: Buffer.from(pdfBytes).toString("base64"),
},
});
console.log(run.result);
// { summary: "...", recommendation: "proceed" }
Handling Multiple Document Types
Real loan applications include several documents — a bank statement, a salary slip, and sometimes an ITR. Run them in parallel using Mastra's parallel step:
import { createWorkflow, createStep, parallel } from "@mastra/core/workflows";
// Run three document extractions simultaneously
const parallelExtract = parallel([
extractBankStatementStep,
extractSalarySlipStep,
extractItrStep,
]);
export const loanApplicationWorkflow = createWorkflow({
id: "loan-application",
// ...
})
.then(parallelExtract)
.then(consolidateAndScoreStep)
.commit();
Lekha's API handles concurrent requests gracefully — each document is processed independently in under 8 seconds on average.
Production Tips
Stream responses for UX. When surfacing the analysis to a user, stream the agent's response rather than waiting for the full output:const stream = await financialAnalystAgent.stream(prompt);
for await (const chunk of stream.textStream) {
process.stdout.write(chunk);
}
Store only the structured JSON, never the raw PDF. Lekha processes documents in memory and returns only the extracted fields — no raw document is retained. Follow the same pattern in your application: store the JSON output, discard the PDF bytes. This keeps you on the right side of India's DPDP Act.
Use Mastra memory for multi-turn conversations. If you are building a chatbot that lets users ask questions about their documents, enable Mastra memory so the extracted data persists across turns:
import { Memory } from "@mastra/memory";
const memory = new Memory({ storage: new LibSQLStore({ url: "file:fin.db" }) });
export const financialAnalystAgent = new Agent({
// ...
memory,
});
Set confidence thresholds. Lekha returns a confidence score with each extraction. Route low-confidence results to a human review queue:
if (result.confidence < 0.85) {
await notifyHumanReviewer(applicantId, result);
return { recommendation: "review" };
}
What Lekha Extracts
Here is a snapshot of what the Lekha API returns for a bank statement — the exact shape your Mastra tools and workflows will work with:
{
"documentType": "bank_statement",
"bank": "HDFC Bank",
"accountHolder": "Priya Sharma",
"accountNumber": "XXXX1234",
"period": { "from": "2026-01-01", "to": "2026-06-30" },
"openingBalance": 42500,
"closingBalance": 87300,
"totalCredits": 345000,
"totalDebits": 300200,
"transactions": [
{
"date": "2026-01-05",
"narration": "SALARY CREDIT INFOSYS LTD",
"amount": 95000,
"type": "credit",
"balance": 137500,
"category": "salary"
}
],
"monthlyStats": [
{
"month": "2026-01",
"credits": 95200,
"debits": 52100,
"avgBalance": 98400
}
]
}
Amounts are always numbers, dates are always ISO 8601, and categories are pre-applied — no post-processing needed in your Mastra workflow.
Try the API live at lekhadev.com/playground before writing a single line of integration code.
FAQ
Does Mastra support streaming with Lekha responses? Lekha returns a standard JSON response — it is not a streaming API. However, you can stream the _agent's analysis_ of the extracted data using Mastra's built-in streaming support, which is what users experience as real-time output. How do I handle Lekha rate limits in a Mastra workflow? The Lekha paid plan has generous rate limits (300 requests/minute), but you can add retry logic using Mastra's step-level retry configuration:createStep({ ..., retryConfig: { attempts: 3, delay: 1000 } }). The free plan is limited to 10 requests/minute.
Can I use Mastra workflows to process bulk document batches?
Yes. Trigger multiple workflow runs in parallel using Promise.all() on the workflow execute calls. Each run is independent and Mastra tracks them separately in its run history.
Is Lekha DPDP-compliant?
Lekha processes documents in memory only — no raw PDFs or images are stored after extraction. The API returns only structured JSON fields. This aligns with India's DPDP Act's data minimisation principle. See the Lekha docs on compliance for details.
Start Building
Mastra and Lekha together give you a production-grade financial document intelligence pipeline in TypeScript — with typed schemas, parallel processing, streaming, and memory — without writing document parsing code from scratch.
Sign up for Lekha's free plan and make your first extraction in five minutes — no credit card required.