Skip to main content
    All articles

    Building Your First Legal Tech App on Indian Court Data

    19 June 202612 min readCourtMesh Team
    Cover card headed The Data Excuse Has Expired, with the line: build it smaller

    Ask an Indian developer why there is so little good software for litigation and you will usually get an answer about data. The records are scattered across portals, the portals fight automation, the formats differ by state, and every project dies in the plumbing. It was a completely fair answer for a long time. It is not the answer any more, and the consequence is uncomfortable: if the data is available over an HTTP call, then the reason your firm still runs on a spreadsheet is a product problem, not a data problem.

    This piece walks through building the smallest genuinely useful thing on Indian court data: a case tracker for one litigation team. Not a research platform, not an analytics suite, not an AI associate. A screen where a junior can find a matter, open it, see where it stands, see what has been ordered, and open the document. That is a week of work against a real API, and it is more useful to more lawyers than most of what gets pitched at demo days.

    We will build it against the production endpoints at https://research.courtmesh.ai/api/v1/prod, because building against a real surface teaches you the constraints that matter. The constraints are the design. Ten requests per minute, fields that are frequently absent, and an upstream that publishes on its own schedule are not obstacles to work around. They are the specification.

    Decide What You Are Building, and Make It Smaller

    The first mistake is scope. A first legal tech product should answer exactly one question that somebody currently answers badly. For a litigation team, the strongest candidate is the oldest one: where does this matter stand, and what is the next date.

    That sounds trivial and is not. Today it is answered by opening a portal, typing a number that somebody has to find first, reading a status line, and mentally reconciling it against what the file says. It is done a dozen times a day by the most junior person available, and the answer is often stale by the time it reaches the person who needed it. A screen that holds a team's matters, shows the current position for each, and links straight to the order that changed things is worth more than it looks, precisely because the work it replaces is invisible.

    The scoping test

    If you cannot describe your first version as one sentence containing one noun and one verb, it is too big. A list of matters with their next dates is a product. A platform for litigation intelligence is a fundraising deck.

    The Shape of the Build

    Six moving parts, in the order you should build them. Each one is independently demonstrable, which matters because the person you are building for will change their mind after seeing the second screen and you want that to happen early.

    1

    Prove connectivity before you write anything else

    GET /health needs no key and returns a small object with success, status, version and a timestamp. Hit it from your server, from your CI, and from whatever environment you will eventually deploy into. It takes ten minutes and eliminates an entire category of confusion later, when a call fails and nobody knows whether the problem is the key, the network or the code.

    2

    Put the key somewhere it cannot leak

    Authentication is a single header: X-API-Key with your key, or Authorization with a Bearer prefix. The key lives in server side configuration and nowhere else. It never appears in a mobile bundle, a browser bundle, a repository, a Postman collection you share, or a screenshot in a ticket. Your frontend calls your backend, and only your backend calls the court data API. This is not paranoia. A key is a bearer credential and your query history is a record of which counterparties your firm is looking at.

    3

    Build search first, because it is how matters enter the system

    POST /search/cases takes a body with a required query string and optional filters: court, caseType, caseNumber, judgeName, year, fromDate and toDate in YYYY-MM-DD, plus page, limit and sortBy which accepts relevance or date. Set limit to 100 rather than leaving it at the default of 20. The response is the standard envelope: success, a data array of case records, a meta object, and pagination carrying page, limit, total, totalPages and hasMore. Render the array, wire the pagination, and you have a working search screen.

    4

    Add the detail screen and let the user attach a matter

    GET /cases/:id accepts either the internal case id you got back from search or a case number. It returns the case record without analysis: title, court, caseType, judges, petitioners, respondents, decisionDate, disposalNature, filingDate, registrationDate, caseStatus, caseStage, nextHearingDate, lastListedOn, cnr, acts, sections and, for District Court records, a courtMetadata object naming the state, district and establishment. The user's action on this screen is the one that makes your product a product: add this to my matters.

    5

    Add a timeline strip from the related endpoint

    GET /cases/:id/related returns everything stored under the same case number, up to fifty documents, as relatedDocuments plus an assembled timeline. Each timeline entry has a date, a status that is either Final Judgment or Hearings / Orders, a statusLabel that prefers the recorded disposal nature, and a documentId. One call gives you a hearing by hearing strip along the top of the matter page, and each entry links to a document you can open.

    6

    Add documents last, on demand

    GET /cases/:id/pdf returns an encrypted presigned link with expiresIn set to 3600 seconds, alongside caseId, caseNumber and caseTitle. Fetch it when the user clicks, never in advance, and never store the URL in your database, because it is a one hour credential and not an address. When the record has no stored document the endpoint returns 404 with PDF not available for this case, which is a legitimate answer and needs a real empty state rather than an error toast.

    The Data Model Is Where First Versions Are Won or Lost

    Nearly every painful rewrite in this category traces back to a table designed on the assumption that a case is a row. It is not. A matter is a sequence of proceedings, each with its own number, and your schema has to survive that.

    Keep at least three tables. A matters table, which is the firm's unit: the client, the internal reference, the responsible partner. A proceedings table, one row per case record you have attached to a matter, holding the API's case id, the case number as displayed, a normalised form of that number, the court, and the case type. And a snapshots table, one row per time you fetched, storing the payload you received and when you received it. The third table is the one people skip and the one that saves them, because it lets you answer what the position was on the day somebody made a decision.

    • Key on the API's case id, never on the display case number. Case numbers repeat across courts and across years. A number that looks unique in one High Court will collide with an identical number elsewhere the moment your product grows past one bench.
    • Store the raw payload alongside your parsed columns. Fields arrive and depart as registries improve their publishing. A stored payload means a schema change is a backfill from your own store rather than a re-pull against a rate limited API.
    • Treat every field as optional, including the obvious ones. decisionDate is absent on pending matters. disposalNature is absent until disposal. nextHearingDate is absent once a matter is decided. judges is sometimes a single string in the underlying source rather than a clean list of names. Your renderer needs a defined behaviour for absent, and the behaviour is not to print undefined.
    • Record the retrieval timestamp on every stored value. A status without a date is not a fact, it is a memory. On a matter page, showing as at 14 July is the difference between information and an implication.
    • Never delete a superseded number. When a matter is renumbered or transferred, add the new proceeding and keep the old one. Historic correspondence refers to the old number and somebody will search for it.

    A case number identifies a stage. A matter is a sequence of stages. Software that confuses the two is software that will lose an appeal in its own database.

    Caching, and Living Inside Ten Requests a Minute

    An API key is allowed ten requests per minute. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset, and a breach returns 429 with a retryAfter in seconds. For an interactive product used by a team of ten, that is a comfortable budget, but only if you have thought about it once. If you have not, the first demo where three people click at the same time will be memorable for the wrong reason.

    The saving grace is that court data has an unusually favourable cache profile. A decided judgment is immutable. The text of an order from 2019 will be the same next year. Only the pending matters have a genuine freshness requirement, and even those change on the timescale of listings, not seconds.

    DataHow fresh it needs to bePractical policy
    A decided case recordEffectively never changesCache indefinitely after the first fetch. Refresh only if a user explicitly asks or if you are correcting a known gap.
    A pending matter's status and next dateDaily is generous, hourly is theatreRefresh watched matters once a night in a background job. Show the retrieval timestamp on screen so nobody mistakes yesterday's fetch for this morning's cause list.
    Related documents and timelineChanges when a new order is publishedRefresh with the nightly matter sync. On a decided matter, treat it as static.
    Search resultsQuery specific and cheap to redoCache for minutes, keyed on the full query and filter set, mostly to absorb a user paging back and forth.
    A document linkExpires in one hour by designNever cache. Fetch on click, redirect the user, and forget it. Storing an expired link is a support ticket generator.
    Stored AI analysisStatic once generatedCache permanently. Read the analysis endpoint before ever triggering generation, because the read is cheap and the generation is not.

    With that policy, a ten person team generates a handful of interactive calls a minute at peak and a predictable batch overnight. The one discipline to enforce is that your nightly sync and your interactive traffic should not share a key, because the batch will otherwise consume the window at exactly the hour somebody is preparing for a hearing.

    The Parts Nobody Puts in the Demo

    A demo shows a Supreme Court matter with a full record, a clean bench, a decision date and a judgment PDF. Production shows you the other ninety percent, and the difference is where trust is won.

    Sparse District Court records

    District Court records are thinner than Supreme Court records, and older records are thinner than recent ones. A matter may have a caseStatus and nothing else. Design the matter page so that a record with four populated fields still looks deliberate rather than broken, and label absent data as not published rather than as unknown.

    Silence is ambiguous

    A matter that stops producing updates might be dormant, or renumbered, or transferred, or decided and carried up on appeal. Your product cannot tell the difference and should not pretend to. Show when you last checked and what you last saw, and let the practitioner interpret it.

    Names are not identifiers

    Party names arrive with honorifics, initials, abbreviations, and inconsistent transliteration. A search for one spelling misses the others. Give users a way to record the variants they care about on the matter rather than assuming your search string is canonical.

    The registry is the authority

    Everything your product shows is a view of what registries published. Say so in the interface, near the date, not buried in a terms page. A partner about to rely on a hearing date needs to know from the screen that the official record is the thing to confirm against.

    An API key shipped in a browser or mobile bundle where anyone can read it
    A stored PDF link that expired an hour after you saved it
    A schema keyed on a display case number that collides across courts
    A matter page that reads as authoritative with no indication of when it was fetched
    A nightly sync sharing a key with interactive users and starving them at nine in the morning

    What to Build Second

    Once the tracker is real and somebody is using it daily, the next increments are obvious and each is small. A hearing history that goes deeper than the stored documents, using POST /request-timeline with a case_id to start an asynchronous job and GET /get-timeline/:requestId to poll it, so a user can pull orders on demand rather than waiting on a batch. A research surface using POST /search/cases/semantic, where a user describes a fact pattern in sentences rather than guessing at terms of art. A judge lookup using GET /judges/search to power an autocomplete so nobody types a free text judge name into a filter and gets nothing back.

    Resist the temptation to add AI to the first version. The analyze endpoints are genuinely useful, and they are also the fastest way to make a simple product slow, expensive and hard to explain. Add them when a user asks for them by name, and even then read the stored analysis first rather than generating it, because reading is cheap and generating is not.

    What this product is not

    A case tracker built this way is a way of reducing how much of your practice depends on remembering to open the right portal. It is not the registry, it does not give legal advice, and it is not the last word on the position in any matter. Build the disclaimer into the interface rather than the marketing page, because the interface is where the decision gets made.

    The interesting thing about this build is how unremarkable it is. Six endpoints, three tables, a nightly job and a caching policy. There is no novel algorithm in it and no research problem to solve. That is exactly the point: the hard part of Indian legal tech was never the software, and it is no longer the data. It is knowing which small thing a litigation team would actually open every morning, and being disciplined enough to build only that. Endpoint details and parameters are at the API documentation, and the coverage behind them at the API overview.

    Start with one screen somebody opens every day

    The CourtMesh API gives you keyword search over roughly 310 million case records drawn from official government portals, semantic search over the roughly 2 million judgment subset that carries semantic indexing, case detail, related documents and timeline, judgment PDFs, and AI analysis, all behind one key and one base URL. Read the endpoint reference, check what each call costs at API pricing, and see the full coverage across the Supreme Court, all 25 High Courts, District Courts and tribunals at the API overview. Then build the smallest thing your team would miss if you took it away.

    Explore CourtMesh
    Legal TechAPIDevelopersTutorialIndia
    X LinkedIn