API Documentation
Complete reference for the Lekhā REST API.
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.
https://lekhadev.com/api/v1application/jsonx-api-key header in all authenticated requestsAuthentication
Authenticate every request by including your API key in the x-api-key header.
Key Prefixes
lk_live_Production key — billed against your planlk_test_Test key — free, rate-limited, uses sandbox datacurl -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
/api/v1/extractExtract 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
| Name | Type | Required | Description | Default |
|---|---|---|---|---|
document | string | required | Base64-encoded document content (PDF, PNG, or JPEG) | — |
type | string | optional | Document type. One of: "auto", "bank_statement", "cas", "salary_slip", "itr", "cibil", "gst_invoice", "form_16", "form_26as", "gst_return", "balance_sheet" | "auto" |
options.categorize_transactions | boolean | optional | Categorize each transaction (e.g., salary, rent, groceries) | true |
options.include_confidence_scores | boolean | optional | Include confidence scores on extracted fields | true |
options.include_raw_text | boolean | optional | Include raw OCR text alongside structured output | false |
Example Request
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)
{
"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
{
"success": false,
"error": {
"code": "INVALID_REQUEST",
"message": "Document is required (base64 encoded)",
"suggestion": "Check docs at https://lekhadev.com/docs"
}
}{
"success": false,
"error": {
"code": "MISSING_API_KEY",
"message": "x-api-key header is required.",
"suggestion": "Get your API key at https://lekhadev.com"
}
}{
"success": false,
"error": {
"code": "UNSUPPORTED_TYPE",
"message": "'xyz' extraction not yet implemented.",
"suggestion": "Supported: bank_statement, cas, salary_slip, itr, cibil, gst_invoice, form_16, form_26as, gst_return, balance_sheet."
}
}{
"success": false,
"error": {
"code": "EXTRACTION_FAILED",
"message": "Failed to extract data.",
"suggestion": "Ensure the document is a clear PDF or image."
}
}GET /supported
/api/v1/supportedList all supported document types and institutions. No authentication required.
Response (200)
{
"document_types": {
"bank_statement": {
"supported_institutions": ["HDFC Bank", "SBI", "ICICI", "Axis", "...28 banks"],
"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"]
},
"itr": {
"supported_institutions": ["Generic"],
"extracted_fields": ["assessee", "income", "deductions", "tax_computation", "regime"]
},
"cibil": {
"supported_institutions": ["TransUnion CIBIL"],
"extracted_fields": ["score", "accounts_summary", "accounts", "enquiries"]
},
"gst_invoice": {
"supported_institutions": ["Generic"],
"extracted_fields": ["seller", "buyer", "items", "totals", "hsn_sac"]
},
"form_16": {
"supported_institutions": ["Generic"],
"extracted_fields": ["part_a", "part_b", "employer", "employee", "tds"]
},
"form_26as": {
"supported_institutions": ["Generic"],
"extracted_fields": ["tds_entries", "advance_tax", "self_assessment_tax", "refunds"]
},
"gst_return": {
"supported_institutions": ["Generic"],
"extracted_fields": ["outward_supplies", "inward_supplies_itc", "tax_payable", "itc_claimed"]
},
"balance_sheet": {
"supported_institutions": ["Generic"],
"extracted_fields": ["entity", "period", "assets", "liabilities", "equity", "pnl_summary"]
}
}
}POST /extract/async
/api/v1/extract/asyncSubmit 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
| Name | Type | Required | Description | Default |
|---|---|---|---|---|
document | string | required | Base64-encoded document content (PDF, PNG, or JPEG) | — |
type | string | optional | Document type. One of: "auto", "bank_statement", "cas", "salary_slip", "itr", "cibil", "gst_invoice", "form_16", "form_26as", "gst_return", "balance_sheet" | "auto" |
webhook_url | string | optional | URL to receive extraction results via webhook when processing completes | — |
options.categorize_transactions | boolean | optional | Categorize each transaction (e.g., salary, rent, groceries) | true |
options.include_confidence_scores | boolean | optional | Include confidence scores on extracted fields | true |
Example Request
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)
{
"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
/api/v1/jobs/:idCheck the status of an async extraction job. Returns the full result when complete.
Pending / Processing Response (200)
{
"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)
{
"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
{
"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."
}
}{
"success": true,
"job_id": "job_...",
"status": "failed",
"error": {
"code": "EXTRACTION_FAILED",
"message": "Failed to extract data from the document."
}
}GET /usage
/api/v1/usageGet current billing period usage statistics. Requires authentication.
Response (200)
{
"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
}
}Account & Keys
Manage your API keys and view usage. These endpoints use session authentication (cookie-based) from the dashboard, not API key auth.
/api/v1/account/keysList all API keys for the authenticated user. Keys are masked in the response for security.
Response (200)
{
"success": true,
"keys": [
{
"id": "uuid-...",
"key": "lk_live_ab...xyz1",
"name": "Production Key",
"plan": "pro",
"requests_used": 142,
"requests_limit": 5000,
"is_active": true,
"created_at": "2026-03-15T10:00:00Z"
}
]
}/api/v1/account/keysCreate a new API key. The full key is returned only once — store it securely.
Request Body
| Name | Type | Required | Description | Default |
|---|---|---|---|---|
name | string | required | Display name for the key (e.g., "Production", "Dev") | — |
Response (201)
{
"success": true,
"key": {
"id": "uuid-...",
"key": "lk_live_full_key_shown_once",
"name": "Production",
"plan": "free",
"requests_used": 0,
"requests_limit": 100
}
}/api/v1/account/keys/:idRevoke an API key. This action is irreversible — the key will immediately stop working.
Response (200)
{
"success": true,
"message": "API key revoked."
}/api/v1/account/billingGet current plan info. If on a paid plan with Stripe, includes a link to the billing portal.
Response (200)
{
"success": true,
"plan": "pro",
"billing_interval": "monthly",
"stripe_portal_url": "https://billing.stripe.com/..."
}Error Codes
All error responses follow a consistent shape. The code field is a stable machine-readable identifier.
| Code | Status | Description |
|---|---|---|
MISSING_API_KEY | 401 | No x-api-key header provided |
INVALID_API_KEY | 401 | API key does not exist |
API_KEY_INACTIVE | 401 | API key has been deactivated |
RATE_LIMIT_EXCEEDED | 429 | Monthly limit reached |
INVALID_REQUEST | 400 | Request body validation failed |
CLASSIFICATION_FAILED | 422 | Auto-classification could not determine document type |
UNSUPPORTED_TYPE | 422 | Document type not supported |
EXTRACTION_FAILED | 500 | Internal extraction error |
JOB_NOT_FOUND | 404 | Async job ID does not exist |
SDK
The official TypeScript SDK. Install from npm and start extracting in three lines.
bun add @lekha-dev/sdk # or: npm install @lekha-dev/sdk
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.
| Type | Description | Key Extracted Fields |
|---|---|---|
bank_statement | Bank account statement (28 Indian banks) | account details, transactions, summary, categories, balance reconciliation |
cas | Consolidated Account Statement (CAMS/KFintech) | investor info, portfolio summary, folios, fund holdings, transactions |
salary_slip | Monthly salary/pay slip | employee details, earnings breakdown, deductions, net pay, YTD totals |
itr | Income Tax Return form | assessment year, income sources, 80C/80D/80G deductions, tax computed, regime |
cibil | CIBIL / credit bureau report | credit score (300–900), accounts summary, payment history, enquiries |
gst_invoice | GST tax invoice | GSTIN, HSN/SAC codes, line items, CGST/SGST/IGST, invoice totals |
form_16 | Form 16 (TDS certificate from employer) | employer/employee PAN, salary breakup, TDS deducted, Part A & Part B |
form_26as | Form 26AS (annual tax statement) | TDS entries, TCS entries, advance tax, self-assessment tax, refunds |
gst_return | GST return filing (GSTR-1, GSTR-3B) | GSTIN, return period, outward/inward supplies, ITC, tax payable |
balance_sheet | Business balance sheet (Tally, CA-prepared) | entity, assets (current/fixed), liabilities, equity, P&L summary |
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.
{
"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": { ... }
}
}{
"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" });