Skip to main content
    All articles

    AI Case Analysis Over an API: What You Get and When to Use It

    21 July 202615 min readCourtMesh Team
    Cover card headed Reads Are Cheap, Runs Are Not, with the line: read before you write

    The most expensive line of code in a legal AI integration is usually a single call placed in the wrong spot. Somebody wires an analysis trigger into a page render, ships it, and every visit to a case detail screen generates a fresh model run over a judgment that was already analysed last month. The feature works. The bill does not, and neither does the latency, and nobody notices until the first invoice or the first timeout.

    The fix is not clever engineering. It is understanding that there are three distinct analysis surfaces on the API, that they have very different costs, and that one of them is a read while the other two are generations. Architecting around that distinction is most of what separates an integration that scales from one that has to be rewritten.

    This article walks through all three, sets out exactly what the analysis payload contains, explains the parallel citation arrays that make the output verifiable rather than merely fluent, and is honest about what the analysis is and is not. A generated headnote is a research aid. It is not authority, and the judgment still has to be read.

    Three Surfaces, Two of Which Cost Real Money

    Everything in this article rests on one distinction. GET /cases/:id/analysis reads whatever analysis is stored. POST /cases/:id/analyze and POST /cases/:id/analyze-consolidated cause work to happen. The first is cheap and fast. The second two run models over judgment text and draw AI credit accordingly.

    EndpointWhat it doesWhen to call it
    GET /cases/:id/analysisReturns the stored analysis for a case, watermarked, with a hasAnalysis flag. When nothing is stored, meta.note points you at the analyze endpoint.Always call this first. It is the read, and in a well built integration it is the overwhelming majority of your analysis traffic.
    POST /cases/:id/analyzeBody carries force. With analysis already stored and force unset, returns 200 immediately with alreadyExists true and the stored analysis. Otherwise returns 202 with a message that analysis has been started and status processing, and runs in the background.Only when a read has told you nothing is stored and a human has asked for the case to be analysed. Never inside a request a user is waiting on.
    POST /cases/:id/analyze-consolidatedAnalyses the whole family of documents sharing one case number rather than a single document. Requires a case number, else 400. Returns the existing consolidated analysis with status already_analyzed unless force is true.For matters where the procedural history is the point. Materially more expensive than the single case analyze, so it should be a deliberate user action.

    Read before you write, every time

    The hasAnalysis flag on the GET exists so that no client ever has to guess. A read that comes back with analysis present costs you one cheap call and finishes the job. Calling analyze speculatively, and worse calling it with force set, converts that cheap read into a generation that draws AI credit and takes tens of seconds. force is not a cache buster to sprinkle on when output looks stale. It is an explicit instruction to pay again.

    How the Analyze Endpoint Actually Behaves

    POST /cases/:id/analyze takes a body with a single field, force, defaulting to false. It has three meaningfully different outcomes, and a client that does not distinguish them will misbehave in at least one of them.

    1

    200 with alreadyExists true

    Analysis is already stored and force was not set, so the endpoint returns the stored analysis immediately without generating anything. This is the good path and it costs almost nothing. Treat it exactly as you would a successful read, and note that if you are hitting it often, your client should have read first.

    2

    202 with status processing

    Work has been accepted and is running in the background. There is no result in this response. The accompanying note suggests checking back in roughly 30 to 60 seconds via GET on the case. Your client must have somewhere to put a pending state, because 202 means the answer does not exist yet.

    3

    400 when there is nothing to analyse

    The case has neither at least 100 characters of text nor any stored document to work from. This is not a transient failure and no retry will cure it. It is a coverage fact about that particular record, and the right response is to mark it and move on rather than to queue it forever.

    That 202 is the field most integrations get wrong. It is an asynchronous acceptance, not a result, and it means the only correct pattern is to poll. Trigger the run, record a pending state against the case in your own store, and have a background worker read GET /cases/:id or the analysis endpoint after a sensible interval. Poll on a schedule you control, respecting the 10 requests per minute per key budget, and give up after a bounded number of attempts rather than looping indefinitely against a record that may have failed upstream.

    The 400 case deserves a moment of honesty too. It exists because the corpus is real. Some records are metadata rows where the registry published a case number, a date and a disposal nature and never published a document. There is nothing for a model to read, and generating analysis from a title and a date would be a fabrication rather than a feature. A 400 here is the system declining to invent something, which is the behaviour you want from anything you are going to rely on professionally.

    Consolidated Analysis, and When the Family Is the Unit

    Single case analysis treats one document as the unit. That is right for a reported judgment being read as authority. It is wrong for a live matter, where what a lawyer needs is not one order but the arc: what was filed, what interim relief was granted or refused, what the position is now.

    POST /cases/:id/analyze-consolidated addresses that. It gathers the family of documents sharing one case number and analyses them together. For a Supreme Court matter it collects related records by case number, up to 20. For a High Court matter it takes the case itself plus its five most recent stored orders. The combined text is capped at 100,000 characters. A case with no case number cannot be consolidated at all and returns 400. Where a consolidated analysis already exists, the endpoint returns it with a status of already_analyzed unless force is set.

    Those limits are worth reading as design constraints rather than as trivia. The 20 record ceiling, the five most recent orders rule and the 100,000 character cap all say the same thing: consolidation summarises a bounded window of the record, not the entire file. For a matter with a long procedural history, the consolidated analysis reflects the most recent and most substantial part of it. That is usually what a practitioner wants, and it is not the same as a complete history. When the complete list of documents matters, GET /cases/:id/related is the right call, returning every stored document sharing the case number, sorted by decision date ascending and capped at 50, with an assembled timeline.

    Consolidated is not a better analyze

    It costs materially more AI credit than the single case analyze, because it reads far more text. Offering it as the default on a case detail screen is a way to multiply your costs without improving most users' experience. Offer it where the procedural history is genuinely the question, for example a matter that has moved through several interim applications, and make it something a user chooses rather than something a page triggers.

    What Is Actually In the Analysis Payload

    The analysis is not one blob of prose. It is a structured object, and knowing the shape lets you render exactly the part a given screen needs instead of dumping everything into a panel nobody reads.

    The summary layer, at three depths

    summary, detailedSummary and comprehensiveSummary are three lengths of the same job, which exists because a search result row, a case preview and a full research view have genuinely different needs. Alongside them sit headnote and holding, the two fields that map most directly onto how Indian practitioners already read reported judgments, and keyFacts, which carries the fact matrix separately so it can be shown without the reasoning.

    The reasoning layer

    issues sets out the questions the court framed. courtsReasoning carries how the bench got to its conclusion. arguments records what was put by each side. factPattern describes the situation in a form that can be compared against another matter, which is what makes it useful for finding analogous cases rather than merely similar words.

    The doctrinal layer

    legalPrinciples and doctrinesApplied name what the judgment stands for. statutoryInterpretation records how a provision was construed, which is frequently the reason a judgment is cited at all. constitutionalProvisions captures where a constitutional question was engaged. precedentValue records how much weight the decision carries, and practiceAreas, subCategories and tags support classification and filtering across a set.

    The relational layer

    citedCases lists the authorities the judgment relied on. precedentRelationships records how it treated them, which is the difference between a citation graph and a mere list. linkedCases connects the record to others in the corpus. benchComposition and opinionType tell you who decided it and in what form, which matters when weighing a majority against a concurrence or a dissent, and when assessing the weight of a larger bench.

    There is also analysisSchemaVersion, which is easy to overlook and worth storing. If you persist analysis in your own database, that field is how you will later know which records were produced under which schema and which need refreshing. A pipeline that discards it has thrown away the only way to reason about its own back catalogue.

    The Citation Arrays Are the Verifiability Feature

    Alongside the content fields sit parallel citation arrays: summaryCitations, holdingCitation, courtsReasoningCitations, keyFactsCitations, legalPrinciplesCitations and their siblings. The convention is positional. Entry i of the citation array corresponds to entry i of the content array.

    This is the single most important structural feature of the payload, and it is the one most likely to be ignored by an integration in a hurry. A generated legal statement with no pointer back into the judgment is an assertion. The same statement paired with the place in the record it came from is a claim a lawyer can check in seconds. The difference between those two things is the difference between a tool a senior will use and a tool a senior will quietly stop opening.

    1. Render content and citation together, always. If your UI shows a legal principle without its citation, you have converted a checkable claim into an unsourced one. Do not present the content array on its own because it was easier to iterate.
    2. Preserve order when you store it. The correspondence is positional, so any transformation that sorts, deduplicates or filters one array without applying the same operation to the other silently mismatches statements to sources. This is a real and easy bug, and the resulting output looks perfectly plausible.
    3. Handle length mismatches defensively. If a citation array is shorter than its content array, render the unmatched entries as uncited rather than pairing them with a neighbour's citation. A wrong citation is much worse than a missing one.
    4. Make the citation clickable. A pointer the reader can follow into the judgment is the entire point. GET /cases/:id/pdf returns a presigned link with an expiresIn of 3600 seconds for exactly this purpose, and because the link is time limited and per request it must never be cached as though it were permanent.
    5. Never strip citations to make output look cleaner. The clutter is the feature. A tidy paragraph of unsourced legal conclusions is precisely the artefact that gets a firm into trouble.

    An AI summary without a pointer into the record is an opinion with no author. The citation arrays are what turn generated text into something a lawyer can verify, and verification is the only reason to let generated text near a matter at all.

    Architect Around Cache Aware Billing

    Analysis endpoints should be billed cache aware, meaning that returning stored analysis is not the same commercial event as generating new analysis. That is the right design, because the underlying cost genuinely differs by orders of magnitude, and it has direct architectural consequences for anyone building on top.

    Work through the arithmetic in the abstract, since real rates belong on the pricing page rather than in an article that will age. Suppose a generation costs C and a read of stored analysis costs some small fraction of C. A case detail screen that calls analyze on every render pays C every time. The same screen that reads first, and triggers a generation only when nothing is stored and a user has asked for it, pays C once for the lifetime of the record and the small fraction on every subsequent view. For a case viewed a hundred times across a firm, the difference is not a tuning matter. It is the difference between a viable feature and one that gets switched off.

    1

    Read first, without exception

    GET the analysis and branch on hasAnalysis. This single call answers the only question that determines what happens next, and in a mature integration the overwhelming majority of analysis requests end here.

    2

    Cache the read in your own store

    A decided case does not change. Once you hold its analysis, you can serve it from your own database indefinitely and refresh only when analysisSchemaVersion moves. This also insulates your interactive latency from anything happening at the API.

    3

    Trigger generation only on intent

    A user pressing an Analyse button is intent. A page loading is not. Wiring generation to a render is how a single crawler, a link preview bot or an over eager prefetch turns into a bill nobody authorised.

    4

    Never analyse inside a user facing request

    A 202 means the work has not finished. Holding an HTTP request open waiting for it ties up a connection, produces a spinner of unpredictable length, and will time out somewhere in your own stack. Accept, record a pending state, return immediately, and let the interface fill in when the result arrives.

    5

    Poll, on your schedule and with a ceiling

    Check back after roughly 30 to 60 seconds, then at widening intervals, with a bounded number of attempts. Polling every two seconds spends the 10 requests per minute budget on impatience and starves everything else on the key.

    6

    Reserve force for a named, logged reason

    Regenerating is legitimate when the schema version has moved or when a specific record is known to be bad. It is not a debugging reflex. Log who set it and why, because force is the field most likely to appear in an unexplained cost spike.

    The two failure modes that cost real money

    The first is a background job that iterates a large result set calling analyze on every record, on the theory that having analysis everywhere is better than having it where it is needed. Ten requests per minute makes that job take weeks, and it draws credit for every single generation, most of which nobody will ever read. The second is a 403 on an AI endpoint being treated as a transient error and retried with backoff. A 403 there means the AI allowance is exhausted. It is a budget condition, not a throughput condition, and no amount of waiting will clear it. Stop the AI lane and alert a human.

    Only a Subset of the Corpus Carries Analysis

    This has to be said plainly, because the alternative is an integration built on an assumption that will fail in front of a user. The keyword index holds roughly 310 million case records drawn from eCourts, the National Judicial Data Grid and court registries. The corpus that has been embedded and AI analysed is far smaller, in the low millions. Nobody has run a model roughly 310 million judgments, and any platform implying otherwise is describing something that does not exist.

    So hasAnalysis coming back false is a normal, expected outcome, not an error state, and your interface has to say something sensible about it. The distribution is not uniform either. Metadata and document availability vary by court and by year: District Court records are thinner than Supreme Court records, and older records are thinner than recent ones. A record with no stored document and under 100 characters of text cannot be analysed at all, which is exactly the 400 discussed earlier.

    Calling analyze on a page render, so every view of a case is a fresh generation
    Holding a user facing request open waiting on a 202 that was never going to resolve in time
    Treating 403 on an AI endpoint as transient and retrying it with backoff instead of alerting
    Sorting or filtering a content array without applying the same operation to its citation array
    Bulk analysing an entire result set on the theory that coverage is free
    Presenting a generated headnote in a client facing document as though it were an editorial headnote from a report
    Assuming hasAnalysis false is a bug rather than an ordinary fact about a 310 million record corpus

    The Verification Duty Does Not Move

    Everything above is engineering. This part is not, and it is the part that matters most.

    A generated headnote is a research aid. It is not authority. It has no editorial standing, it has not been settled by a reporter, and it certainly has not been approved by the bench. It is a compression of a judgment produced by a model, and compression loses things. It can lose the qualification in the sentence after the one it summarised. It can present obiter with the same confidence as ratio, because both look like judicial statements in the text. It cannot tell you whether the judgment has since been overruled, distinguished, doubted, or is under challenge in a pending SLP. It cannot know that the paragraph you like was answering a point that does not arise on your facts.

    None of that is a criticism of the technique. It is a description of what the technique is for. Analysis at scale is the thing that lets a practitioner triage two hundred candidate judgments down to the eight worth reading properly, and it does that job extremely well. The eight still have to be read. The proposition still has to be checked against the judgment itself. If it is going into a submission, the citation still has to be verified against the record, which is why the citation arrays and the PDF endpoint exist and why the presigned link goes straight into the source document.

    The same discipline applies to the surrounding metadata. The registry is the authority. Anything aggregated, including a next hearing date, a case status or a disposal nature, is a view of what the registry published, and where a limitation calculation under the Limitation Act 1963 or a listing turns on it, the registry record governs. Build interfaces that make that easy to remember rather than easy to forget.

    The full field list, response shapes and status codes are documented in the endpoint reference, what the corpus covers across the Supreme Court, all 25 High Courts, the district judiciary and the tribunals is set out at the API overview, and because analysis calls draw AI credit, it is worth reading API pricing before you design a workflow that generates rather than reads.

    Read first, generate on intent, verify always

    AI analysis is most useful when it is treated as a cache to read rather than a computation to trigger. On the CourtMesh API, GET /cases/:id/analysis tells you what is already stored through a hasAnalysis flag, POST /cases/:id/analyze returns 202 and runs in the background when there is genuinely something new to do, and POST /cases/:id/analyze-consolidated reads a bounded family of documents sharing a case number when the procedural history is the question. Only a subset of the corpus carries analysis, the parallel citation arrays exist so every generated statement can be checked against the record, and the judgment itself remains the authority. The endpoint reference is here and what a call costs is at API pricing.

    Explore CourtMesh
    AI AnalysisAPIJudgmentsLLMDevelopers
    X LinkedIn