Overview

Lekhā extracts structured data from Indian financial documents. Send a base64-encoded PDF or image, and receive clean, agent-ready JSON with account details, transactions, summaries, and validation results.

Base URLhttps://lekhadev.com/api/v1
FormatAll responses are application/json
AuthInclude x-api-key header in all authenticated requests

Authentication

Authenticate every request by including your API key in the x-api-key header.

Key Prefixes

lk_live_Production key — billed against your plan
lk_test_Test key — free, rate-limited, uses sandbox data
Example request with authentication
curl -X POST https://lekhadev.com/api/v1/extract \
  -H "Content-Type: application/json" \
  -H "x-api-key: lk_live_your_key_here" \
  -d '{"document": "<base64>", "type": "auto"}'

Rate Limiting

Rate limits are enforced per API key, not per IP address. Your monthly allocation resets on the 1st of each month at 00:00 UTC. If you exceed your limit, the API returns a 429 status with the RATE_LIMIT_EXCEEDED error code.

POST /extract

POST/api/v1/extract

Extract structured data from a financial document. Send a base64-encoded PDF, PNG, or JPEG and receive parsed account details, transactions, and validation results.

Request Body

NameTypeRequiredDescriptionDefault
documentstringrequiredBase64-encoded document content (PDF, PNG, or JPEG)
typestringoptionalDocument type. One of: "auto", "bank_statement", "cas", "salary_slip", "itr", "cibil", "gst_invoice", "form_16", "form_26as", "gst_return""auto"
options.categorize_transactionsbooleanoptionalCategorize each transaction (e.g., salary, rent, groceries)true
options.include_confidence_scoresbooleanoptionalInclude confidence scores on extracted fieldstrue
options.include_raw_textbooleanoptionalInclude raw OCR text alongside structured outputfalse

Example Request

curl
curl -X POST https://lekhadev.com/api/v1/extract \
  -H "Content-Type: application/json" \
  -H "x-api-key: lk_live_your_key_here" \
  -d '{
    "document": "JVBERi0xLjQK...",
    "type": "auto",
    "options": {
      "categorize_transactions": true,
      "include_confidence_scores": true,
      "include_raw_text": false
    }
  }'

Success Response (200)

response
{
  "success": true,
  "extraction_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "document_type": "bank_statement",
  "institution": "HDFC Bank",
  "confidence": 0.96,
  "data": {
    "account": {
      "holder_name": "Rajesh Kumar",
      "account_number": "XXXX1234",
      "account_type": "savings"
    },
    "period": {
      "from": "2024-01-01",
      "to": "2024-01-31"
    },
    "summary": {
      "opening_balance": 45000,
      "closing_balance": 52300,
      "total_credits": 125000,
      "total_debits": 117700,
      "transaction_count": 12
    },
    "transactions": [
      {
        "date": "2024-01-05",
        "description": "NEFT-CR ACME CORP SALARY JAN24",
        "credit": 85000,
        "debit": null,
        "balance": 130000,
        "category": "salary",
        "category_confidence": 0.98
      }
    ]
  },
  "validation": {
    "balance_reconciled": true,
    "anomalies": [],
    "missing_pages_detected": false
  },
  "metadata": {
    "pages_processed": 1,
    "processing_time_ms": 2340,
    "template_used": "hdfc_v1"
  }
}

Error Responses

400 — Invalid request
{
  "success": false,
  "error": {
    "code": "INVALID_REQUEST",
    "message": "Document is required (base64 encoded)",
    "suggestion": "Check docs at https://lekhadev.com/docs"
  }
}
401 — Missing API key
{
  "success": false,
  "error": {
    "code": "MISSING_API_KEY",
    "message": "x-api-key header is required.",
    "suggestion": "Get your API key at https://lekhadev.com"
  }
}
422 — Unsupported document type
{
  "success": false,
  "error": {
    "code": "UNSUPPORTED_TYPE",
    "message": "'itr' extraction not yet implemented.",
    "suggestion": "Supported: bank_statement, cas, salary_slip."
  }
}
500 — Extraction failed
{
  "success": false,
  "error": {
    "code": "EXTRACTION_FAILED",
    "message": "Failed to extract data.",
    "suggestion": "Ensure the document is a clear PDF or image."
  }
}

GET /supported

GET/api/v1/supported

List all supported document types and institutions. No authentication required.

Response (200)

response
{
  "document_types": {
    "bank_statement": {
      "supported_institutions": ["HDFC Bank", "SBI"],
      "extracted_fields": [
        "account_details", "period", "summary",
        "transactions", "categories"
      ]
    },
    "cas": {
      "supported_institutions": ["CAMS", "KFintech"],
      "extracted_fields": [
        "investor", "portfolio_summary",
        "folios", "transactions"
      ]
    },
    "salary_slip": {
      "supported_institutions": ["Generic"],
      "extracted_fields": [
        "employee", "earnings",
        "deductions", "net_pay"
      ]
    }
  }
}

POST /extract/async

POST/api/v1/extract/async

Submit a document for asynchronous extraction. Returns immediately with a job ID. Poll GET /jobs/:id for results or provide a webhook_url to receive results automatically.

Request Body

NameTypeRequiredDescriptionDefault
documentstringrequiredBase64-encoded document content (PDF, PNG, or JPEG)
typestringoptionalDocument type. One of: "auto", "bank_statement", "cas", "salary_slip", "itr", "cibil", "gst_invoice", "form_16", "form_26as", "gst_return""auto"
webhook_urlstringoptionalURL to receive extraction results via webhook when processing completes
options.categorize_transactionsbooleanoptionalCategorize each transaction (e.g., salary, rent, groceries)true
options.include_confidence_scoresbooleanoptionalInclude confidence scores on extracted fieldstrue

Example Request

curl
curl -X POST https://lekhadev.com/api/v1/extract/async \
  -H "Content-Type: application/json" \
  -H "x-api-key: lk_live_your_key_here" \
  -d '{
    "document": "JVBERi0xLjQK...",
    "type": "bank_statement",
    "webhook_url": "https://your-app.com/webhooks/lekha"
  }'

Response (202 Accepted)

response
{
  "success": true,
  "job_id": "job_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "pending",
  "poll_url": "/api/v1/jobs/job_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "estimated_time_ms": 5000
}

GET /jobs/:id

GET/api/v1/jobs/:id

Check the status of an async extraction job. Returns the full result when complete.

Pending / Processing Response (200)

response
{
  "success": true,
  "job_id": "job_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "processing",
  "created_at": "2026-03-29T10:30:00Z",
  "updated_at": "2026-03-29T10:30:02Z"
}

Completed Response (200)

response
{
  "success": true,
  "job_id": "job_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "completed",
  "result": {
    "extraction_id": "ext_...",
    "document_type": "bank_statement",
    "institution": "HDFC Bank",
    "confidence": 0.96,
    "data": { ... },
    "validation": { ... },
    "metadata": { ... }
  },
  "created_at": "2026-03-29T10:30:00Z",
  "completed_at": "2026-03-29T10:30:04Z"
}

Error Responses

404 — Job not found
{
  "success": false,
  "error": {
    "code": "JOB_NOT_FOUND",
    "message": "No job found with the given ID.",
    "suggestion": "Check the job_id and ensure you are using the correct API key."
  }
}
200 — Job failed
{
  "success": true,
  "job_id": "job_...",
  "status": "failed",
  "error": {
    "code": "EXTRACTION_FAILED",
    "message": "Failed to extract data from the document."
  }
}

GET /usage

GET/api/v1/usage

Get current billing period usage statistics. Requires authentication.

Response (200)

response
{
  "plan": "free",
  "period": {
    "start": "2026-03-01",
    "end": "2026-03-31"
  },
  "usage": {
    "credits_used": 42,
    "credits_limit": 100,
    "credits_remaining": 58
  },
  "breakdown": {
    "bank_statement": 38,
    "salary_slip": 4
  }
}

Error Codes

All error responses follow a consistent shape. The code field is a stable machine-readable identifier.

CodeStatusDescription
MISSING_API_KEY401No x-api-key header provided
INVALID_API_KEY401API key does not exist
API_KEY_INACTIVE401API key has been deactivated
RATE_LIMIT_EXCEEDED429Monthly limit reached
INVALID_REQUEST400Request body validation failed
UNSUPPORTED_TYPE422Document type not supported
EXTRACTION_FAILED500Internal extraction error

SDK

The official TypeScript SDK. Install from npm and start extracting in three lines.

Install
npm install @lekha-dev/sdk
Usage
import { Lekha } from "@lekha-dev/sdk";

const lekha = new Lekha("lk_live_...");

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

// result.data.summary.opening_balance → 45000
// result.data.transactions[0].category → "salary"

Document Types

Lekhā supports 9 Indian financial document types. Use "auto" to let the classifier detect the type automatically, or specify it explicitly for faster processing.

TypeDescriptionKey Extracted Fields
bank_statementBank account statement (40+ Indian banks)account details, transactions, summary, categories, balance reconciliation
casConsolidated Account Statement (CAMS/KFintech)investor info, portfolio summary, folios, fund holdings, transactions
salary_slipMonthly salary/pay slipemployee details, earnings breakdown, deductions, net pay, YTD totals
itrIncome Tax Return formassessment year, income sources, 80C/80D/80G deductions, tax computed, regime
cibilCIBIL / credit bureau reportcredit score (300–900), accounts summary, payment history, enquiries
gst_invoiceGST tax invoiceGSTIN, HSN/SAC codes, line items, CGST/SGST/IGST, invoice totals
form_16Form 16 (TDS certificate from employer)employer/employee PAN, salary breakup, TDS deducted, Part A & Part B
form_26asForm 26AS (annual tax statement)TDS entries, TCS entries, advance tax, self-assessment tax, refunds
gst_returnGST return filing (GSTR-1, GSTR-3B)GSTIN, return period, outward/inward supplies, ITC, tax payable

Webhooks

When you provide a webhook_url in an async extraction request, Lekhā will POST the result to your endpoint when processing completes. Every webhook request includes a cryptographic signature so you can verify authenticity.

Verification

Each webhook request includes an X-Lekha-Signature header containing an HMAC-SHA256 signature of the request body, signed with your API key. Always verify this signature before processing the payload.

Retry Policy

If your endpoint returns a non-2xx status code, Lekhā retries delivery up to 3 times with exponential backoff: 1 second, 5 seconds, then 15 seconds.

Webhook payload — extraction.completed
{
  "event": "extraction.completed",
  "job_id": "job_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "timestamp": "2026-03-29T10:30:04Z",
  "data": {
    "extraction_id": "ext_...",
    "document_type": "bank_statement",
    "institution": "HDFC Bank",
    "confidence": 0.96,
    "data": { ... },
    "validation": { ... },
    "metadata": { ... }
  }
}
Webhook payload — extraction.failed
{
  "event": "extraction.failed",
  "job_id": "job_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "timestamp": "2026-03-29T10:30:06Z",
  "error": {
    "code": "EXTRACTION_FAILED",
    "message": "Failed to extract data from the document.",
    "suggestion": "Ensure the document is a clear PDF or image."
  }
}

Signature Verification Example

import crypto from "crypto";

function verifyWebhook(body: string, signature: string, apiKey: string): boolean {
  const expected = crypto
    .createHmac("sha256", apiKey)
    .update(body)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// In your webhook handler:
const isValid = verifyWebhook(rawBody, req.headers["x-lekha-signature"], YOUR_API_KEY);
if (!isValid) return res.status(401).json({ error: "Invalid signature" });