Keyword search across the full corpus, meaning based semantic search over the analysed subset, and judge name lookup.
Keyword search across 315.6 million case records from the Supreme Court, all 25 High Courts, District Courts and Tribunals. This is the endpoint that sees the whole corpus.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
| query | string | Yes | Search text. Trimmed, must be at least 1 character. Case numbers work here: put 123-2024 in the query rather than in caseNumber. |
| court | string | string[] | No | Court name or names. Comma separated strings are also split, so "Bombay High Court,Delhi High Court" behaves like an array. |
| year | number | string | array | No | Four digit year, or an array of them. Must be between 1947 and the current year. |
| caseType | string | string[] | No | Case type label or code. Labels are expanded server side to the full set of codes they cover, so "Appeal" matches CA, CRA, LPA and the rest. |
| judgeName | string | string[] | No | Judge name. judges and judge are accepted as aliases; the first one present wins. Use GET /judges/search to get exact spellings. |
| fromDate | string | No | Inclusive lower bound on decision date, YYYY-MM-DD. Any other format is rejected with HTTP 400. |
| toDate | string | No | Inclusive upper bound on decision date, YYYY-MM-DD. |
| page | number | No | Page number, default 1, minimum 1. Must be a JSON number. A quoted string is rejected with HTTP 400. |
| limit | number | No | Results per page, default 20, maximum 100. Must be a JSON number. |
| searchAfter | string | No | Cursor from the previous response. Prefer this over page for anything beyond the first few pages. |
| sortBy | enum | No | One of relevance (default), recent, oldest. See the note below on the deprecated value date. |
| caseNumber | string | string[] | No | ACCEPTED BUT INERT. The schema allows it and the response echoes it back in meta.filters, but the handler never forwards it to the search engine, so it does not narrow results. Put the case number in query instead. |
| practiceArea | not supported | No | NOT A PARAMETER. It is absent from the schema, and unknown keys are stripped silently during validation, so sending it produces no error and no filtering. |
sortBy takes relevance (the default), recent for newest decision date first, or oldest for the reverse. The value date is a deprecated alias kept only so that existing integrations do not start failing validation. The search layer does not understand it and silently ranks by relevance instead, so it is not a way to sort by date. Use recent or oldest.
The body is parsed against a fixed schema and anything outside it is removed before the handler runs. That is why sending practiceArea produces neither an error nor a filtered result set. If a filter you sent does not appear in the meta.filters echo of the response, it was not applied. The one filter that appears in that echo and is still not applied is caseNumber.
Every response also carries meta.someRecordsWithheld, a boolean noting whether a restricted or de-indexed record was left out of the results, and on a degraded restricted-record check meta.restrictedCheckDegraded is set to true with affected titles masked in the response.
curl -X POST https://research.courtmesh.ai/api/v1/prod/search/cases \
-H "X-API-Key: cm-YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"query": "property dispute adverse possession",
"court": ["Bombay High Court", "Delhi High Court"],
"year": [2006, 2007],
"judgeName": "D.Y. CHANDRACHUD",
"fromDate": "2006-01-01",
"toDate": "2007-12-31",
"page": 1,
"limit": 20,
"sortBy": "recent"
}'// HTTP 400. page and limit are JSON numbers, not strings.
// Sending "limit": "20" fails; sending "limit": 20 passes.
{
"success": false,
"error": "Validation failed. Please check your request and try again.",
"details": ["limit: Expected number, received string"]
}Meaning based search over the vector index, which holds roughly 2 million analysed and embedded cases. That is a curated subset of the corpus, not the whole archive, so a semantic query can genuinely miss a document that keyword search finds. Results carry a similarity score.
TIER_LIMITS.free.semanticSearchAllowed is false. A Free tier key calling this endpoint is refused before any work or charge with HTTP 403 and code: "SEMANTIC_NOT_ALLOWED". Every paid tier (Pay As You Go, Scale, Enterprise) has full access at the published 10 credit price and its own per minute ceiling.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
| query | string | Yes | Natural language description of what you are looking for. Minimum 3 characters. |
| limit | number | No | Results per page, default 20, capped at 100. |
| page | number | No | Page number, default 1. Translated into a vector search offset. |
| filters | object | No | The only place filtering works on this endpoint. Understood keys: court, caseType, judgeName (judges and judge are mapped onto it), caseYear, caseNumber, and decisionDate with $gte and $lte. Your values are merged over anything the model inferred from the query and win on conflict. |
| court, year, caseType, caseNumber, judgeName, judges, judge, fromDate, toDate | inert | No | ACCEPTED BUT INERT at the top level. The schema allows all nine, so sending them raises no error, but the handler reads only query, limit, page and filters. Move them inside filters. |
| sortBy | not supported | No | NOT A PARAMETER on this endpoint. Semantic results are always ordered by vector similarity, returned as a similarity score on each result. |
This is the single most common mistake on this endpoint. The schema is permissive and accepts court, year, caseType, caseNumber, judgeName, judges, judge, fromDate and toDate at the top level, so you get a clean 200 back. The handler never reads them. Nest them under filters instead, and note the key is caseYear there, not year, with date bounds expressed as decisionDate with $gte and $lte. Check meta.appliedFilters in the response to see exactly what was used.
curl -X POST https://research.courtmesh.ai/api/v1/prod/search/cases/semantic \
-H "X-API-Key: cm-YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"query": "cases about state surveillance and the right to privacy",
"limit": 10,
"page": 1,
"filters": {
"court": "Supreme Court of India",
"caseYear": 2017
}
}'filters is merged over what it inferred and wins on conflict.fallbackMode: "opensearch".total and totalPages are conservative estimates that become exact on the final page. Do not build a page count UI on them mid stream.meta.someRecordsWithheld for a restricted or de-indexed record left out of results, and, on a degraded restricted-record check, meta.restrictedCheckDegraded: true with affected titles masked.Look up exact judge name spellings across the combined Supreme Court and High Court lists. Use it before filtering a search by judge, because the search filters match the stored spelling exactly.
Query parameters
| Field | Type | Required | Notes |
|---|---|---|---|
| q | string | No | Case insensitive substring match against the combined Supreme Court and High Court judge lists. Omit it to get the first 50 names. Never returns more than 50 matches. |
curl "https://research.courtmesh.ai/api/v1/prod/judges/search?q=chandrachud" \
-H "X-API-Key: cm-YOUR_KEY_HERE"