A developer's guide to the lending product that is mostly paperwork, and how to turn that paperwork into logic.

If you build software for banks or NBFCs, sooner or later you will meet a loan against property, or LAP. On the surface it is a simple product. Underneath, it is one of the most document-heavy workflows in lending.

This article explains how LAP works in plain terms, then models the key steps in Python: eligibility, document completeness, name matching across documents and exception routing.

What is a loan against property?

A loan against property is a secured loan. The borrower mortgages a property they already own, such as a house, flat or shop, and the lender gives them a loan against its value.

The borrower keeps owning and usually keeps using the property. The lender holds it as security until the loan is repaid.

That is the key difference from a home loan. A home loan funds the purchase or construction of a property. LAP raises money against a property the borrower already has, often for business expansion, working capital, education or medical expenses.

How does a loan against property work?

From a lender's point of view, every LAP application passes through two parallel tracks:

  1. Borrower assessment: Can this person repay? Income, existing loans, credit history and bank statements.
  2. Property assessment: Is this property acceptable security? Valuation, legal title, ownership history and existing claims.

The loan is sanctioned only when both tracks pass. That is why LAP takes longer than a personal loan, and why it is such a good candidate for automation.

How do you calculate LAP eligibility?

Eligibility is the lower of two limits.

The property limit. Lenders lend only a percentage of the property's value, called the loan-to-value (LTV) ratio. A property worth ₹1 crore at 60% LTV supports a loan of up to ₹60 lakh.

The income limit. The EMI must fit within the borrower's income after existing obligations. Indian lenders often express this as FOIR (fixed obligations to income ratio): the share of monthly income that can go towards all EMIs combined.

def emi(principal, annual_rate, months): """Standard reducing-balance EMI.""" r = annual_rate / 12 / 100 return principal * r * (1 + r) ** months / ((1 + r) ** months - 1) def max_loan_from_emi(max_emi, annual_rate, months): """Invert the EMI formula: the largest principal a given EMI can repay.""" r = annual_rate / 12 / 100 return max_emi * ((1 + r) ** months - 1) / (r * (1 + r) ** months) def lap_eligibility(property_value, ltv, monthly_income, existing_emis, foir, annual_rate, months): property_limit = property_value * ltv max_new_emi = monthly_income * foir - existing_emis income_limit = max(0, max_loan_from_emi(max_new_emi, annual_rate, months)) eligible = min(property_limit, income_limit) return { "property_limit": round(property_limit), "income_limit": round(income_limit), "eligible_amount": round(eligible), "limited_by": "property" if property_limit <= income_limit else "income", } print(lap_eligibility( property_value=10_000_000, ltv=0.60, monthly_income=150_000, existing_emis=20_000, foir=0.50, annual_rate=10.5, months=180, )) 

For this borrower, the property supports ₹60 lakh, but income supports only about ₹50 lakh, so income is the binding limit. This is exactly the point many borrowers miss: a valuable property alone does not guarantee a large loan.

The LTV, FOIR, rate and tenure here are illustrative. Real values come from each lender's credit policy.

How do you check whether the document set is complete?

A LAP file needs two groups of documents: borrower documents and property documents. A missing document is the most common reason a file stalls, so the first automated check is simply completeness.

REQUIRED_DOCS = { "borrower": {"identity_proof", "address_proof", "income_proof", "bank_statement"}, "property": {"sale_deed", "encumbrance_certificate", "property_tax_receipt", "approved_plan"}, } def missing_documents(submitted): """Return required documents that are not in the submitted set.""" return {group: sorted(required - submitted) for group, required in REQUIRED_DOCS.items() if required - submitted} submitted = {"identity_proof", "address_proof", "income_proof", "bank_statement", "sale_deed", "property_tax_receipt"} print(missing_documents(submitted)) # {'property': ['approved_plan', 'encumbrance_certificate']} 

In practice the checklist varies by lender, property type and state. That is why it should live in configuration, not be hard-coded into the workflow.

How do you match names across LAP documents?

This is where LAP gets messy. The borrower's name appears on the application, identity proof, sale deed, tax receipts and bank statement. It is rarely written the same way twice.

"S. Ramesh" on one document and "Ramesh Subramanian" on another may well be the same person. In South India especially, an initial often stands for the father's name, and it may be expanded in one document and abbreviated in another.

A strict string comparison would flag this as a mismatch. A loose one might accept two different people. A practical middle ground compares name tokens and treats an initial as a possible match for a full word starting with the same letter.

import re def name_tokens(name): return [t for t in re.split(r"[\s.]+", name.upper()) if t] def compare_names(a, b): """Return 'match', 'possible_match' or 'mismatch'.""" ta, tb = name_tokens(a), name_tokens(b) if sorted(ta) == sorted(tb): return "match" full_a = {t for t in ta if len(t) > 1} full_b = {t for t in tb if len(t) > 1} if not full_a & full_b: return "mismatch" # no full name in common def covered(x, y): # every token in x is in y, or is an initial of a word in y return all(t in y or (len(t) == 1 and any(w.startswith(t) for w in y)) for t in x) if covered(ta, tb) or covered(tb, ta): return "possible_match" return "mismatch" print(compare_names("S. Ramesh", "Ramesh Subramanian")) # possible_match print(compare_names("Ramesh S", "S Ramesh")) # match print(compare_names("S. Ramesh", "K. Suresh")) # mismatch 

Notice the result is not a yes or no. "Possible match" is deliberately its own category, because a person should confirm it. Real systems add transliteration handling, fuzzy matching for spelling variations and comparisons against father's names and addresses, but the principle is the same.

How do you route exceptions to a reviewer?

Once the checks run, the workflow needs to decide what happens next. A clean file moves forward. A file with issues goes to a reviewer with every reason listed, so nobody has to re-check the whole file to find the problem.

def route_application(app): """Collect every issue, then decide whether the file needs review.""" flags = [] for group, docs in missing_documents(app["documents"]).items(): flags.append(f"Missing {group} documents: {', '.join(docs)}") applicant = app["applicant_name"] for doc, name in app["names_on_documents"].items(): result = compare_names(applicant, name) if result != "match": flags.append(f"Name on {doc} is a {result.replace('_', ' ')}: '{name}'") status = "ready_for_sanction" if not flags else "needs_review" return {"status": status, "flags": flags} app = { "applicant_name": "Ramesh Subramanian", "documents": submitted, "names_on_documents": {"sale_deed": "S. Ramesh", "property_tax_receipt": "Ramesh Subramanian"}, } print(route_application(app)) 

This is the maker-checker model, expressed in code. The system does the repetitive checking. The reviewer makes the judgement calls, with the evidence already in front of them.

What does a production LAP workflow add?

The examples above are deliberately small. A production LAP system also has to:

  • Extract data from documents: Sale deeds and encumbrance certificates are often scanned, multi-page and partly in regional languages.
  • Trace the ownership chain: Confirm that each previous owner validly transferred the property to the next.
  • Check encumbrances: Identify existing mortgages or claims on the property.
  • Keep an audit trail: Record every check, flag and decision for compliance.

Extraction is usually the hardest part to build well. Tools such as DocuGenie.AI's LAP QC Automation handle that layer, extracting and cross-checking property and borrower documents so reviewers can focus on flagged exceptions. But the workflow logic above stays the same whether you build or buy.

Wrap Up

A loan against property is simple to explain: borrow against a property you already own. Processing one is not, because every LAP decision depends on two things at once: whether the borrower can repay, and whether the property is acceptable security.

For developers, that makes LAP a great example of exception-driven design. Let code handle the maths and the repetitive checks. Make uncertainty a first-class result instead of forcing a yes or no. And give reviewers every reason up front, so their time goes into judgement rather than searching.