Skip to content

Work with district court records

DistrictCourtClient reads the district eCourts portal. Discover its location codes before using number or party searches; a CNR lookup can identify a case without that cascade.

Search through an assistant

For an assistant connected to Docker MCP, follow the complete district-court prompt walkthrough. It covers choosing a court, searching by party/number, direct CNR details, orders/PDFs, cause lists and errors. Start with state, district, party and registration year; a known CNR can skip location discovery.

Discover the location codes

The selection order is state, district, court complex and, where required, establishment. Codes are portal identifiers rather than names or postal codes.

import asyncio
from bharat_judgements import DistrictCourtClient


async def main():
    async with DistrictCourtClient() as client:
        print("States:", await client.list_states())
        state = input("State code: ").strip()
        print("Districts:", await client.list_districts(state))
        district = input("District code: ").strip()
        print("Complexes:", await client.list_complexes(state, district))
        complex_code = input("Court complex code: ").strip()
        print("Establishments:", await client.list_establishments(state, district, complex_code))
        establishment = input("Establishment code, or Enter if not needed: ").strip()
        print(
            "Case types:",
            await client.list_case_types(state, district, complex_code, establishment),
        )


asyncio.run(main())

This complete example queries the portal and lets you select returned codes. The SDK normalises compound complex codes before sending them to endpoints that require the bare code.

Search by number or party

case_status needs state_code, dist_code, court_complex_code, optional est_code, plus case_type, case_number and registration year. case_status_by_party uses the same location inputs with party_name and mandatory year.

Both return CaseInfo search records. Select the correct establishment and registration year; do not infer detailed hearing information from fields absent in this response.

Look up a CNR directly

case_status_by_cnr(cnr) returns the fuller CaseDetail and does not require state, district or complex codes. Supply a 16-character alphanumeric identifier; spaces and hyphens are stripped.

The response can include stage, judge, parties and advocates, acts, hearing history and orders. Missing sections remain empty. A CAPTCHA or malformed-response failure can exhaust the retry budget and raise an error.

Retrieve orders

court_orders uses the same location and case-number arguments as case_status. It returns CaseOrder records with the PDF URLs exposed by the portal. There is no district download_order_pdf SDK method; download available URLs using your own HTTP client, or use the CLI's orders --download workflow.

Read a cause list

Call list_cause_list_courts after selecting the location and establishment. Use a returned court code as court_no when calling cause_list. Supply court_name if you already have it; otherwise the client discovers the corresponding display name.

causelist_date uses DD-MM-YYYY. civil=False selects the criminal list. Results are CauseListEntry rows rather than High Court PDF links. Availability depends on what the portal publishes for that court and date.

Use the CLI

Start with the discovery command:

bharat-judgements --json districtcourts states

Then use districts, complexes, establishments, case-types and courts to complete the cascade. Each command's --help lists its required location flags. The CLI currently exposes number and party searches, orders and cause lists, but not CNR case-detail lookup.

Run each command after inspecting the previous response. The prompts let you copy returned codes, so the example also applies to districts other than Patna. The CLI needs the cli extra: pip install -e '.[cli,ocr]' from this checkout.

bharat-judgements --json districtcourts states
$stateCode = Read-Host "State code from the response"

bharat-judgements --json districtcourts districts --state $stateCode
$districtCode = Read-Host "District code from the response"

bharat-judgements --json districtcourts complexes --state $stateCode --dist $districtCode
$complexCode = Read-Host "Full court complex value from the response"

$complexParts = $complexCode.Split("@")
$estCode = ""
if ($complexParts.Length -gt 2 -and $complexParts[2] -eq "Y") {
    bharat-judgements --json districtcourts establishments --state $stateCode --dist $districtCode --complex $complexCode
    $estCode = Read-Host "Establishment code from the response"
}

$districtLocation = @("--state", $stateCode, "--dist", $districtCode, "--complex", $complexCode)
if ($estCode) {
    $districtLocation += @("--est", $estCode)
}

$partyName = Read-Host "Party name (at least 3 characters)"
$registrationYear = Read-Host "Registration year (YYYY)"
bharat-judgements --json districtcourts search-by-party @districtLocation --party $partyName --year $registrationYear --status both

If the complex does not require an establishment, skip its discovery call and leave $estCode empty. Broad party searches can return many candidates: inspect the returned case identifiers before treating one as a match.

To look up a specific case number and retrieve its orders:

bharat-judgements --json districtcourts case-types @districtLocation
$caseType = Read-Host "Full case-type code, including any ^suffix"
$caseNumber = Read-Host "Case number without the year"

bharat-judgements --json districtcourts search @districtLocation --case-type $caseType --case-number $caseNumber --year $registrationYear
bharat-judgements --json districtcourts orders @districtLocation --case-type $caseType --case-number $caseNumber --year $registrationYear

Add --download ./output to the orders command to save available PDFs locally. For a cause list, discover its court first:

bharat-judgements --json districtcourts courts @districtLocation
$courtNumber = Read-Host "Cause-list court code from the response"
$listDate = Read-Host "Published list date (DD-MM-YYYY)"
bharat-judgements --json districtcourts cause-list @districtLocation --court-no $courtNumber --date $listDate

Add --criminal for the criminal list. The CLI uses lowercase status choices pending, disposed and both; SDK/MCP calls use Pending, Disposed, Both. For fuller live details by CNR, use the Python example or districtcourts_case_status_by_cnr through MCP.

Run the CLI with Compose

Build with docker compose build mcp, then override the MCP entrypoint for CLI commands. For example:

docker compose run --rm --no-deps -T --entrypoint bharat-judgements mcp --json districtcourts states

The same prefix can run districts, complexes, establishments, case-types, search-by-party, search, orders and cause-list with the flags above. For an export to the Compose output volume, use orders --download /output; see host-folder exports to save it to your computer.

Save the following as district_search.py. Install the package with OCR support and run it from your chosen Python environment:

pip install 'bharat-judgements[ocr]'
python district_search.py

Before publication, install from this checkout with pip install -e '.[ocr]'. The script lists real portal choices instead of hard-coding location codes. For an example search, choose Bihar → Patna → the appropriate complex/establishment, then enter party Union of India and registration year 2024. The returned matches depend on the selected court and the current portal response.

import asyncio

from bharat_judgements import DistrictCourtClient
from bharat_judgements.config import BharatJudgementsConfig
from bharat_judgements.districtcourts.parser import parse_complex_value


def choose(label, choices):
    if not choices:
        raise ValueError(f"No {label} choices returned; check the previous selection")
    print(f"\n{label}:")
    for code, name in choices.items():
        print(f"  {code}: {name}")
    while True:
        selected = input("Copy the code for your choice: ").strip()
        if selected in choices:
            return selected
        print("Choose one of the returned codes exactly as displayed.")


async def main():
    settings = BharatJudgementsConfig(timeout=120)
    async with DistrictCourtClient(config=settings) as client:
        state = choose("State", await client.list_states())
        district = choose("District", await client.list_districts(state))
        selected = choose("Court complex", await client.list_complexes(state, district))
        complex_code, _, needs_establishment = parse_complex_value(selected)
        establishment = ""
        if needs_establishment:
            establishment = choose(
                "Establishment",
                await client.list_establishments(state, district, complex_code),
            )
        location = {
            "state_code": state,
            "dist_code": district,
            "court_complex_code": complex_code,
            "est_code": establishment,
        }
        print("\nSelected location:", location)
        party = input("Party name (at least 3 characters): ").strip()
        year = input("Registration year (YYYY): ").strip()
        if len(party) < 3 or len(year) != 4 or not year.isdigit():
            raise ValueError("Provide a party name of at least 3 characters and a YYYY year")
        cases = await client.case_status_by_party(
            **location, party_name=party, year=year, status_filter="Both"
        )
        print(f"\nPortal returned {len(cases)} candidates; showing at most 10.")
        for index, case in enumerate(cases[:10], 1):
            print(f"\nCandidate {index}:")
            print(case.to_json(indent=2, exclude_none=True))
        if not cases:
            print("No rows returned for these inputs; check the location/name/year.")
            return
        cnr = input("Copy a candidate's CNR for fuller details, or Enter to finish: ").strip()
        if cnr:
            detail = await client.case_status_by_cnr(cnr)
            print("\nLive case details:")
            print(detail.to_json(indent=2, exclude_none=True))


asyncio.run(main())

The party search returns candidates, not a confirmed match to your matter. Compare both parties and the case identifier before selecting a CNR. The script prints the fuller detail response, including hearing history and order records when available. A network/CAPTCHA error raises an exception instead of being reported as an empty successful search.

For a known case number, reuse the selected location in the same client:

case_types = await client.list_case_types(**location)
case_type = choose("Case type", case_types)
case_number = input("Case number, without the year: ").strip()
year = input("Registration year (YYYY): ").strip()
cases = await client.case_status(
    **location, case_type=case_type, case_number=case_number, year=year
)
for case in cases:
    print(case.to_json(indent=2, exclude_none=True))
orders = await client.court_orders(
    **location, case_type=case_type, case_number=case_number, year=year
)
for order in orders:
    print(order.to_json(indent=2, exclude_none=True))

Insert this second block inside main() and the active client context, after location has been constructed. It is an alternative to the party-search block, not a standalone script. Keep the full returned case_type, including a suffix such as ^2. CaseInfo.case_type is a display label and should not be assumed to be the numeric/compound input code.

For MCP PDF retrieval, ask for the selected order's returned download_id and use download_pdf; read its PDF resource before summarising. Python SDK order records carry URLs rather than MCP download IDs.

Portal changes and errors

The client discovers rotating request headers from the portal's JavaScript and refreshes them once after an invalid-request response. A rejected request does not necessarily mean an outage. Record the error, selected identifiers and operation if retries fail.

Broad party searches may take longer than simple discovery calls. Increase the timeout deliberately rather than removing the request delay.

Next: client reference, CLI guide, or live High Court records.