If you have ever written a scraper for services.ecourts.gov.in, you already know the pain. The portal is intentionally single threaded. The captcha breaks once a quarter. The PDFs are scanned images without OCR. Half your on-call rotations end with someone restarting a Selenium worker because a session cookie expired in production.
The eCourtsIndia API is what you build on when scraping is no longer your day job. This is the developer’s guide to using it end to end. We will set up authentication, walk through every endpoint that matters, write working code in cURL and Python, and then go deep on the workflows that confuse most teams when they first integrate: the search facets, the difference between litigant search and general search, pagination past 100 records, order retrieval, the refresh API, and how to wire up daily case change alerts.
This is the long version, written from the questions real developers send us while wiring up the API. Every request shown here is taken from the live partner API surface at https://webapi.ecourtsindia.com. The counts and examples were verified against the live eCourtsIndia data layer on 1 June 2026. The CNR (Case Number Record, the unique 16-character case identifier used across the eCourts system) DLHC010001232024 used throughout is a real Delhi High Court writ petition disposed on 5 January 2024 by Hon’ble Mr. Justice Tushar Rao Gedela. You can run any of these calls yourself.
Building with an AI coding assistant? We publish a plain text version of this entire reference, written to be pasted straight into ChatGPT, Claude, Cursor or your own model. Drop it into the context window and your assistant can answer eCourtsIndia API questions and write integration code that actually matches our endpoints. Download it here: eCourtsIndia API reference for LLMs (.txt).

What the API gives you
The eCourtsIndia API is a REST interface to a structured layer of 28 crore+ and growing Indian court records, served from https://webapi.ecourtsindia.com with a bearer token. It exposes case retrieval, order text and AI analysis, single and bulk refresh, full text search and cause lists, so you can build court data products without scraping the government portal.
The eCourtsIndia data layer covers 28 crore+ and growing case records across the Supreme Court, all 25 High Courts, the district and taluka judiciary across 800+ districts, and 18 tribunal types carrying more than 26.8 lakh matters (NCLT, NCLAT, ITAT, CESTAT, DRT and DRAT, NGT, AFT, CAT, SAT, TDSAT, APTEL, CCI, RCT and the consumer commissions), with newer forums such as the GST Appellate Tribunal (GSTAT) still being seeded as coverage rolls out. Every record is structured, deduplicated and continuously refreshed from the official eCourts substrate. Orders and judgments are stored as both digitally signed PDFs and OCR cleaned markdown. The hard parts that every team rebuilds when scraping, court code normalisation, case type variance, party name aliasing, scanned PDF extraction, are already handled.
It serves Solr backed full text search across every uploaded order and judgment in the index, not just metadata. It returns OCR cleaned markdown for the order text, ready to paste into a model. It exposes pre computed AI analysis of orders, including summary, key points, outcome, relief granted and statutory provisions. It accepts bulk refresh requests for multiple cases in a single POST. And it returns enum dictionaries for case type, status, court code and state so you never have to hardcode a mapping again.
If you want the strategic case for moving off the portal, we wrote about it in From Case Lookup to Case Intelligence. This post is the engineering version.
Step 1. Sign up and get your bearer token
Every endpoint is authenticated with a bearer token in the Authorization header. Tokens look like this.
eci_live_t3e5s7t9i1n2g4a6c8c0o2u4n6t8x1y3
To get one, register at ecourtsindia.com. New accounts receive ₹200 in free credits, enough to call most endpoints a few hundred times while you build the integration, and no credit card is needed to start. Pricing tiers and per call rates are listed at ecourtsindia.com/api/pricing.
The single most common integration error: the word Bearer. The header value must be Bearer, a space, then the token. If you send the raw token with no prefix, or you misspell the scheme, the API returns INVALID_TOKEN even though the token itself is perfectly valid. This trips up almost every new team.
# WRONG, returns INVALID_TOKEN-H "Authorization: eci_live_xxx"# CORRECT-H "Authorization: Bearer eci_live_xxx"
Creating, naming and rotating tokens
You manage your own tokens from the dashboard at ecourtsindia.com/dashboard/settings. You do not need to email support for a key, and you should not be sharing one key by hand. From the settings page you can generate a token, and you can create more than one. Give each token a clear name tied to where it runs, for example production-backend, staging, nightly-portfolio-job or data-science-notebook. Naming tokens by use is not cosmetic. It is how you read your usage and your logs later, and it is how you revoke one environment without breaking the others. If a notebook key leaks, you rotate just that key.
A token stays valid as long as the account has credit. There is no daily expiry to manage. If a token suddenly starts returning INVALID_TOKEN in production, the usual cause is that it was rotated on the account, so log in, read the current token list under settings, and update your secret store. Treat tokens like any production secret: environment variable in development, secret manager in production, never committed to a repo, never logged.
Every call is logged, and you can see it
You do not need to build your own audit trail just to debug with us. Every API request against your account, successful or failed, is recorded and visible in your dashboard, with the endpoint, the timestamp and the outcome. So when something looks wrong, the first stop is your own logs. You will usually see exactly which call failed and why before you even open a support thread. If you do raise something with us, the fastest path is to send the request_id from the response (it is in meta.request_id on both success and error payloads) along with the CNR. We can pull that exact call from our side.
Step 2. Set the base URL
The partner API is served at one hostname.
https://webapi.ecourtsindia.com
All endpoints in this guide use that as the base. The interactive Swagger documentation lives at ecourtsindia.com/api/docs, and a full Postman collection of every endpoint is available to partner accounts on request.
Step 3. Your first call
The simplest sanity check is to fetch the list of states. This is the entry point into the court hierarchy; it needs your Bearer token but is free, no credits are charged. One breaking change to note if you integrated early: the court-structure endpoints moved from the old /api/CauseList/court-structure/* path to /api/partner/causelist/court-structure/*, and the old path is no longer accessible to partners.
cURL
curl -X GET \
"https://webapi.ecourtsindia.com/api/partner/causelist/court-structure/states" \
-H "Authorization: Bearer eci_live_YOUR_TOKEN_HERE" \
-H "Accept: application/json"
Python
import requests
BASE = "https://webapi.ecourtsindia.com"
TOKEN = "eci_live_YOUR_TOKEN_HERE"
HEADERS = {"Authorization": f"Bearer {TOKEN}", "Accept": "application/json"}
resp = requests.get(f"{BASE}/api/partner/causelist/court-structure/states", headers=HEADERS)
resp.raise_for_status()
print(f"{len(resp.json())} states/UTs available")
A successful response returns 38 states and union territories (including the Supreme Court, keyed as SC). Codes follow the two letter convention used across the eCourts system: AP for Andhra Pradesh, DL for Delhi, MH for Maharashtra, KA for Karnataka, TN for Tamil Nadu, WB for West Bengal, and so on. The Supreme Court of India is keyed as SC because the eCourts taxonomy treats it as a separate jurisdiction rather than a state.
Reference data: court codes, case types and other enums
The first thing most teams ask for is the court list and the code lists. You do not need a spreadsheet from support for this. There are two free sources of truth, and you should pull from them at build time rather than hardcoding.
The enum endpoint returns the authoritative dictionaries for case type, case status, court code and state code. It is free to call and it is the single source of truth for any code you see in a response.
curl -X GET \ "https://webapi.ecourtsindia.com/api/partner/enums?types=caseType,caseStatus,courtCode,stateCode" \ -H "Authorization: Bearer eci_live_YOUR_TOKEN_HERE"
At the time of writing there are 202 case-type codes, 71 case status codes, 36 High Court codes and 38 state codes, but treat those figures as a snapshot. The enum endpoint is always the authoritative live set, and it grows as new courts and forums come online, for example, GST Appellate Tribunal (GSTAT) bench court codes and case types are being seeded into these dictionaries as that coverage rolls out, so pull the enums at build time rather than baking in a fixed count. See the full list in TXT for reference. A few you will use constantly: WP_C writ petition civil, CS civil suit, CRL_A criminal appeal, BA bail, ABA anticipatory bail, SLP_C special leave petition civil, CC criminal complaint, MACA motor accident claims. High Court codes carry a numeric suffix, for example DLHC01 for Delhi, KAHC01 for Karnataka, HCBM01 and HCBM02 for Bombay.
Wrong codes fail silently. If you pass a case type or court code that does not exist, the search does not error. It returns zero results with a 200 status. So when a query unexpectedly comes back empty, the first thing to check is whether your codes are real. Look them up in the enum endpoint before you blame the data.
The court hierarchy is walked top down when you need district and complex codes for cause lists. Each level is its own call.
GET /api/partner/causelist/court-structure/states
GET /api/partner/causelist/court-structure/states/{state}/districts
GET /api/partner/causelist/court-structure/states/{state}/districts/{districtCode}/complexes
GET /api/partner/causelist/court-structure/states/{state}/districts/{districtCode}/complexes/{courtComplexCode}/courts
The endpoints you will actually use
There are five endpoint groups. Most production integrations use only the first three.

1. Case retrieval
This is the workhorse. Given a CNR, fetch the complete case record with parties, advocates, judges, hearings, orders and interlocutory applications.
curl -X GET \ "https://webapi.ecourtsindia.com/api/partner/case/DLHC010001232024" \ -H "Authorization: Bearer eci_live_YOUR_TOKEN_HERE"
For the Delhi HC writ petition above, you get the case type (WP_C), filing and registration numbers, filing date, judge name, party details, advocate names, the interlocutory applications, and a list of order files. As eCourts has migrated its High Court and District Court records to a newer structured format, the case record now also carries richer fields wherever the source provides them: for High Courts, things like the e-filing number and date, filing type, litigant type, case-type conversion and structured petitioner/respondent lists; for district courts, the processes, IA filings, transfers and hearing-history arrays. The full OCR cleaned text of each order is already embedded in the same response, so for most cases you never need a second call to read the order. More on that in the order retrieval section below.
2. Order retrieval
Three flavours of the same order, depending on what you need downstream.
GET /api/partner/case/{cnr}/order/{filename} -> PDF (signed certified copy)GET /api/partner/case/{cnr}/order-md/{filename} -> markdown (OCR cleaned)GET /api/partner/case/{cnr}/order-ai/{filename} -> markdown + structured AI analysis
For most LLM pipelines you want order-md. For lawyer facing dashboards that need a one paragraph teaser, you want order-ai. For evidence and filing, you want the signed PDF.
You now choose signed or raw. The PDF endpoints take a signed flag. The default, signed=true, gives you the watermarked, digitally signed certified true copy that you would file or hand a client. Pass ?signed=false to pull the raw court PDF with no watermark or signature, which is cleaner for your own OCR or text pipeline. The per call cost is the same either way, and the markdown text is unaffected.
3. Case refresh
Two endpoints, single case and bulk. We cover these in depth further down because this is where most integrations go wrong.
4. Search
Solr backed full text search across case metadata, AI keywords, AI summary text and the full markdown text of every uploaded order. This is the endpoint behind almost every developer question we get, so it has its own dedicated section next.
5. Cause list
Three endpoints for daily court schedules. A cause list is the daily roster a court publishes of the matters it will hear, and who is on the bench. Covered in the cause list section, including the freshness window that catches teams out. There is now a fourth, CNR-first way in: POST /api/partner/causelist/cnr/batch takes 1 to 100 CNRs in one call and checks whether each case is listed for an upcoming hearing and, if it is, when, where, and what it is listed for. Pass a one-element array to check a single case. It is billed per CNR checked, ₹0.30 pay-as-you-go or ₹0.10 on a subscription, and it is the quick way to answer “is anything on my docket listed tomorrow?” without pulling each full cause list. The public causelist/search also gained a cnr filter for the same job.
Search recipes developers actually ask us for
The search endpoint is one URL with more than 40 optional filters, and almost every support thread we open is a question about how to use it correctly. The endpoint is GET /api/partner/search. Parameters are case insensitive, so Query, query and QUERY all work, but we will write them in the documented PascalCase form. This section is the part most teams copy into their own wiki, and it pairs well with our longer eCourtsIndia Search Guide.
Recipe 1: replicate the website Case Status search
The most frequent request is some version of this: “I want the same Case Status search the eCourts website has, State then District then Court then Case Type then Case Number then Year, and I want the right CNR back.” There is no separate case status endpoint. You build that search out of the facet filters on /api/partner/search. The relevant parameters are:
| What you pick on the website | API parameter | Example value |
|---|---|---|
| State | StateCodes | DL |
| Court | CourtCodes | DLHC01 |
| Case Type | CaseTypes | WP_C |
| Year | FilingYears | 2024 |
| Case Number | Query | 138/2024 |
So the Delhi High Court writ petition number 138 of 2024 is found like this.
curl -X GET \ "https://webapi.ecourtsindia.com/api/partner/search?CourtCodes=DLHC01&CaseTypes=WP_C&FilingYears=2024&Query=138/2024&Page=1&PageSize=20" \ -H "Authorization: Bearer eci_live_YOUR_TOKEN_HERE"
Honest nuance. The facets (CourtCodes, CaseTypes, FilingYears) filter precisely. The case number you put in Query does not filter to one row, because the registration and filing numbers live inside the full text index, not as a separate exact field. What happens is the matching case ranks first. In the call above, the target case DLHC010001232024 comes back as result number one out of the filtered set. Read the cnr from that top result, then call GET /api/partner/case/{cnr} for the authoritative record. If you search without the court, type and year facets, you will get results from many states, which is the “search returns multiple states” problem teams report. The fix is always: add the facets.
Recipe 2: litigant search vs general search vs petitioner and respondent
This is the single biggest source of confusion, so it is worth being precise. There are three different ways to search by a name, and they return very different result sets.
| Parameter | What it searches | Live count for “Virendra Vora” (as of 1 June 2026) |
|---|---|---|
Query=Virendra Vora | Everything: party names, advocate, judge, and the full text of every order. It is a keyword search, so it matches the words across all that text and ranks by relevance | 12,319 |
Litigants=Virendra Vora | Party names only, both petitioner and respondent sides | 205 |
Counts verified live on 1 June 2026. They grow over time as new cases are indexed, so treat the numbers as illustrative of the ratio, not as constants.
The takeaway: general Query is a full text search across order text too, so a name will match even when it only appears once inside the body of an order. If you want to find cases where someone is actually a party, use the Litigants field. If you want to pin one side, use Petitioners or Respondents.
# All cases where the name is a party (petitioner OR respondent).../api/partner/search?Litigants=Virendra Vora# Only where the name is the petitioner/appellant.../api/partner/search?Petitioners=Virendra Vora# Only where the name is the respondent/defendant.../api/partner/search?Respondents=Virendra Vora
“A case has 8 respondents and my person is one of them. Will I get it?” Yes, with Litigants. We verified this with the name “Nemin Virendra Vora”. The Litigants filter returns 3 cases, and that set includes a Supreme Court matter and an Assam company petition where Nemin Virendra Vora is one of four respondents, not the first named party. The Litigants field matches the name anywhere in the petitioner or respondent lists, regardless of position. If you were only filling the Petitioners parameter with the first party, that is why you were missing these. Switch to Litigants.
For a deeper walk through of party name search, alias handling and the difference between a person and a company search, see the eCourtsIndia Litigant Search guide.
Recipe 3: “the website shows 11,000 records but the API gave me 150”
This one comes up almost word for word. The cause is two things stacked together.
First, the website default search is a general unquoted search, so it includes every partial and full text match across order bodies. That is the 12,319 number from the table above, not a clean list of party matches. The website is showing you the same large, relevance ranked set, just paginated.
Second, the API paginates. One response is a page, not the whole result set. The default page size is small and the maximum is 100. If you read one response and count the array, you get one page, which is where the “I only got 100” and “I only got 150” reports come from. To pull the whole set you loop the Page parameter until you have walked every page.
# Page 1, 100 per page.../api/partner/search?Query=Virendra Vora&Page=1&PageSize=100# Page 2 gives results 101 to 200, page 3 gives 201 to 300, and so on.../api/partner/search?Query=Virendra Vora&Page=2&PageSize=100
Python: walk every page
import requestsBASE = "https://webapi.ecourtsindia.com"HEADERS = {"Authorization": "Bearer eci_live_YOUR_TOKEN_HERE"}def search_all(params, page_size=100): results, page = [], 1 while True: q = {**params, "Page": page, "PageSize": page_size} r = requests.get(f"{BASE}/api/partner/search", headers=HEADERS, params=q) r.raise_for_status() data = r.json()["data"] results.extend(data["results"]) if not data.get("hasNextPage"): break page += 1 return resultsrows = search_all({"Litigants": "Virendra Vora"})print(len(rows), "party matches")
Use Litigants if you want the clean party set rather than the inflated full text count. If you genuinely want all the full text hits, the loop above will fetch them page by page, but be mindful that every page is a metered call. For very large pulls, talk to the team about whether a data partnership fits your use case better than paginating the whole index.
Recipe 4: narrowing a noisy keyword search
Start with the mindset that this is a keyword and full-text engine, not a query language you must learn: the backend tokenises your words, matches them across case text and order bodies, and ranks by relevance, so plain keywords are the robust default. That said, the Query field does understand Solr operators when you need precision: an exact phrase in double quotes, uppercase AND/OR/NOT, grouping with parentheses, and a trailing wildcard such as neglig*. Phrase-plus-boolean combinations work; "specific performance" AND injunction returned 28,155 matters in a live July 2026 check. Two hard rules: never start a term with *, which forces a full-index scan, and do not use operators inside the name fields (Litigants, Petitioners, Respondents), where nameMatchMode is the right tool. Most of the time, though, precision comes from which field you search and which facets you add. Two rules cover almost every case.
Search the right field, not just the general query. The general Query reads the full text of every order, so a common or short name gets noisy fast. Searching AU Small Finance Bank in Query matches those words wherever they appear, including deep inside an unrelated order. If you want the bank’s own cases, put the name in the Litigants field instead, which only looks at the party lists. The field you choose does far more to clean up results than any phrasing trick.
Punctuation is tokenised, so it rarely behaves as a literal. A query like A.P.A.C gets broken on the dots, so the general search can match unrelated text that happens to contain those letters. We had a team report that searching APAC returned a case with no APAC in its metadata, because of a literal mention buried in an order body. The reliable fix is the same one: search the party, advocate or judge field rather than the general query, and add facets to pin the court, case type and year.
To combine terms, facets are still the sharpest tool. Filtering by CourtCodes, CaseTypes and FilingYears narrows a broad keyword search precisely, which is how you get a clean, targeted result set, and unlike operator syntax it cannot be tripped up by tokenisation. Reach for operators when the facets cannot express the constraint, for example an exact phrase or an exclusion.
# Narrow a noisy keyword search with facets
.../api/partner/search?Query=airtel&CourtCodes=DLHC01&CaseTypes=WP_C&FilingYears=2024
# Operators work in Query when you need them (phrase + boolean, verified July 2026)
.../api/partner/search?Query=%22specific%20performance%22%20AND%20injunction
Remember to URL encode the query string in code. A space becomes %20, so Virendra Vora on the wire is Virendra%20Vora.
Useful extra filters
Beyond names, the search supports Advocates, Judges, CaseStatuses, BenchTypes, registration and decision date ranges, HasOrders, HasJudgments, MinOrderCount, and sorting with SortBy and SortOrder. SortBy takes relevance, which is the default, or a structured field such as a filing, decision or hearing date, a filing year, an order, hearing or judgment count, case type, status, court code or CNR, and multi-field chains like orderCount desc, filingDate asc work; an unsupported sort field simply falls back to relevance. Four newer capabilities are worth wiring in. The name filters (Litigants, Petitioners, Respondents, Advocates, Judges) take a nameMatchMode of all (default), any, phrase or fuzzy, where fuzzy tolerates one edit per token and absorbs Virendra/Virender-style spelling drift, and they accept multiple values by repeating the key (litigants=A&litigants=B, OR’d). Presence filters (existsFields/missingFields) return only cases where a field has, or lacks, a value, for example missingFields=decisionDate for still-pending matters. Field projection (fields=cnr,caseStatus,nextHearingDate) trims responses for high-volume scans. And the whole supported surface, every sortable, facetable, projectable and presence-filterable field, is machine-readable from the free GET /api/partner/search/capabilities, so discover it from there rather than hardcoding. Add IncludeFacetCounts=true to get drill down facet counts back with the result set, which is how you build a filter sidebar like the one on the website. When you are paging very deep into a large result set, cursor-based paging is cheaper than requesting ever-higher page numbers. Teams building background verification and due diligence flows lean on these heavily, and we wrote a full build along for that in Building a Legal Due Diligence Engine on India’s Court Data.
Order and PDF retrieval, the part that confuses everyone once
When you fetch a case, the response carries a files block and a judgmentOrders block. Developers get tripped up by the two different filename forms, so here is exactly what each field is.
"judgmentOrders": [ { "orderDate": "2024-01-05", "orderType": "Final Order", "orderUrl": "order-1.pdf" }],"files": { "files": [ { "pdfFile": "DLHC010001232024-order-1.pdf", "markdownFile": "DLHC010001232024-order-1.md", "markdownContent": "...full OCR cleaned order text is right here...", "aiAnalysis": { ... } } ]}
Two things to take away.
First, the full order text is already in the case response, inside files.files[].markdownContent. For most use cases you do not need a second call to read an order. Just read that field.
Second, when you do want the standalone PDF or markdown endpoint, pass the bare filename from orderUrl, which is order-1.pdf, not the prefixed storage name DLHC010001232024-order-1.pdf you see in pdfFile. The prefixed name is the internal storage key. The endpoint expects the short form.
# Correct: bare filename from orderUrlGET /api/partner/case/DLHC010001232024/order/order-1.pdf# Same case, OCR cleaned markdownGET /api/partner/case/DLHC010001232024/order-md/order-1.pdf
Signed certified copy by default, raw PDF on request. Both the PDF endpoint and the markdown endpoint return the watermarked, digitally signed certified true copy unless you say otherwise. Add ?signed=false when you only need the underlying court PDF for machine reading. The suggested download filename then switches to an unsigned label, so the two never get mixed up in storage.
# Certified true copy (default): watermark + digital signatureGET /api/partner/case/DLHC010001232024/order/order-1.pdf# Raw court PDF, no watermark or signature, same costGET /api/partner/case/DLHC010001232024/order/order-1.pdf?signed=false
What the order AI analysis actually contains
The order-ai endpoint is far more than a one line teaser. It returns the OCR cleaned extractedText of the order alongside a deep aiAnalysis object, and for a lawyer facing product or a research pipeline this is the part worth wiring up properly. The analysis is organised into four blocks.
- Foundational metadata. Clean case identifiers, bench composition, judge names, order date, every party with its exact role, and the counsel who appeared on each side.
- Deep legal substance. The primary legal issues, the statutes cited and how each was applied, the arguments on both sides, the court’s reasoning, and the extracted ratio decidendi with a confidence score.
- Intelligent insights. An executive summary, a plain language outcome summary written for the litigant, actionable alerts, and any compliance directives flowing from the order.
- Actionable outputs. Cited case network data, similarity search hints, and topic cluster suggestions you can use to surface related matters.
The fields are deeply nested, so here are the paths you will reach for most often.
# Executive summaryaiAnalysis.intelligent_insights_analytics...ai_generated_executive_summary# Plain language summary written for the litigant...plain_language_summary_for_litigants_outcome_focused# Order nature and outcomeaiAnalysis.foundational_metadata.procedural_details_from_order.order_nature...disposition_outcome_if_disposed# Judges and order dateaiAnalysis.foundational_metadata.core_case_identifiers.judge_names...core_case_identifiers.order_date# Statutes applied and the court's reasoningaiAnalysis.deep_legal_substance_context.core_legal_content_analysis.statutes_cited_and_applied...arguments_and_reasoning_analysis.court_reasoning_for_decision
The analysis is generated on demand the first time you ask for it. The first call to order-ai for a given order can take 10 to 60 seconds while the document is processed, and the result is cached after that, so every later call is fast. If aiAnalysis comes back null, do not treat it as missing. Wait 15 to 30 seconds and retry, up to three times. If you only need raw text and not the analysis, fall back to order-md or the markdownContent already sitting in the case response.
The markdown endpoint can be slow, by design. order-md runs a real time PDF conversion pipeline and can take up to 300 seconds on a heavy order. If you hit a 429 TOO_MANY_CONVERSIONS, the pipeline is saturated, so wait for the duration in the Retry-After header before trying again. For most reads you never need this call at all, because the full order text is already embedded in the case response under files.files[].markdownContent. Reach for order-md only when you specifically need the watermarked PDF for an official submission. If you are building case intelligence on top of this, our walkthrough in From Case Lookup to Case Intelligence shows where the AI analysis fits.
The refresh API, in depth
The refresh API is the workflow that catches teams off guard on day one. The semantics are different from “fetch the data right now”, and getting it wrong produces silent staleness in production.
What refresh does, and why it is not instant
When you call POST /api/partner/case/{cnr}/refresh, you are not fetching data from us. You are asking our backend to go to the official eCourts source, pull the latest record for that CNR, parse and normalise it, OCR any new orders, and write the updated record. The call returns immediately and the case enters the refresh queue.
It is worth understanding why this is a queue and not an instant read, because it explains the behaviour you will see. We are reaching through to government court servers in real time. Those servers are the same ones behind the public portal, and they are not always fast or reliable. Some are slow at peak hours, some return partial data, some are briefly down. When a refresh takes longer than usual or has to retry, it is almost always the source server having a moment, not your request. Building in a poll and a sensible timeout, rather than expecting a single instant answer, is what makes an integration robust against that reality.
Refresh is POST, not GET. Calling GET .../refresh returns 405 Method Not Allowed. This is a common first attempt. Use POST.
curl -X POST \ "https://webapi.ecourtsindia.com/api/partner/case/DLHC010001232024/refresh" \ -H "Authorization: Bearer eci_live_YOUR_TOKEN_HERE"

Refresh is asynchronous, so do not expect fresh data on the next line. After you queue a refresh, wait, then call GET /api/partner/case/{cnr} and watch for dateModified to advance. How long the round trip takes depends on what changed and on how the source server is behaving. A metadata only update can settle in seconds. A case with brand new order PDFs that need OCR, or a slow source server, can take materially longer, sometimes a few minutes. Poll rather than assume, and set a sensible cap.
Refresh is idempotent for a short window. Calling refresh on the same CNR more than once within about 15 seconds will not create duplicate jobs or charge you twice for the scrape, so a stray double click or a retry is safe. After you queue a refresh, give it 5 to 10 seconds before you fetch the case again, then watch dateModified as shown above.
Python: refresh then poll until the record moves
import time, requestsBASE = "https://webapi.ecourtsindia.com"HEADERS = {"Authorization": "Bearer eci_live_YOUR_TOKEN_HERE"}def refresh_and_fetch(cnr, max_wait=300, poll_every=10): requests.post(f"{BASE}/api/partner/case/{cnr}/refresh", headers=HEADERS).raise_for_status() started, baseline = time.time(), None while time.time() - started < max_wait: time.sleep(poll_every) resp = requests.get(f"{BASE}/api/partner/case/{cnr}", headers=HEADERS) resp.raise_for_status() modified = resp.json()["data"]["entityInfo"]["dateModified"] if baseline is None: baseline = modified elif modified != baseline: return resp.json() return resp.json()
Refresh also works when we do not have the CNR yet
This is a genuinely useful property that is easy to miss. Refresh is not limited to cases already in our index. If you call refresh on a valid CNR that is not yet in the eCourtsIndia database, the backend will go and fetch it from the official source, parse it, and add it. Either refresh endpoint, single or bulk, behaves this way. So the reliable pattern when a CNR is missing is: call refresh on it, wait, then call the case endpoint. The case that came back empty a moment ago will now be populated with proper court data. In other words, you do not need us to already know a case for you to onboard it. Push the CNR through refresh and it gets pulled in.
Search indexing lags behind a refresh, by up to an hour or two. There is one subtlety to plan for. After a refresh, the case detail endpoint, GET /api/partner/case/{cnr}, reflects the fresh data quickly. The full text search index does not update in the same instant. With our full index of 28 crore+ records and growing, rebuilding the search index on every single change is not practical, so reindexing runs in batches, and a freshly refreshed or newly added case can take up to one to two hours to appear or update in /api/partner/search results. The takeaway: for the freshest view of a specific case, read it by CNR through the case endpoint. Use search for discovery, and expect a short lag before brand new changes are searchable.
When to call refresh
Call refresh when you have a reason to believe the cached data is stale. The cache is kept reasonably fresh by background workers, so most reads do not need an explicit refresh first. The most reliable staleness signal is a nextHearingDate that is in the past, which means a hearing has happened and the snapshot predates it. A user pressing “get the latest” in your UI is another good trigger. For monitoring a portfolio overnight, batch the CNRs and use bulk refresh rather than looping the single endpoint.
Bulk refresh for portfolios
If you are refreshing more than two or three cases, do not loop the single endpoint. Use bulk refresh.
curl -X POST \ "https://webapi.ecourtsindia.com/api/partner/case/bulk-refresh" \ -H "Authorization: Bearer eci_live_YOUR_TOKEN_HERE" \ -H "Content-Type: application/json" \ -d '{ "cnrs": ["DLHC010001232024", "MHAM030051402019", "HCBM010186932022"] }'
The backend validates each CNR, deduplicates the list, queues the valid ones and reports invalid ones back so you can clean your watchlist. A single bulk-refresh call accepts up to 50 CNRs, so for a larger watchlist, chunk it into batches of 50 rather than sending one giant array. This is the endpoint behind the weekly routine in Litigation Portfolio Monitoring for General Counsel.
Building daily case update alerts
One of the most common things teams build on top of refresh is a watcher: tell me when anything changes on these cases. The eCourtsIndia API does not yet push webhooks, so the production pattern is a scheduled pull and compare. The logic is simple and reliable once you see it.
For each CNR you care about, store a small snapshot of the fields that matter. Once a day, refresh the case, fetch it, and compare the new snapshot against the stored one. If anything you track has changed, raise an alert and save the new snapshot as the baseline. The fields worth watching are usually caseStatus, nextHearingDate, lastHearingDate, orderCount and decisionDate. A change in orderCount means a new order or judgment was uploaded. A change in nextHearingDate means the matter was relisted.
Python: detect changes for a watchlist
import time, requestsBASE = "https://webapi.ecourtsindia.com"HEADERS = {"Authorization": "Bearer eci_live_YOUR_TOKEN_HERE"}WATCHED = ("caseStatus", "nextHearingDate")def snapshot(cnr): requests.post(f"{BASE}/api/partner/case/{cnr}/refresh", headers=HEADERS) return cnr
Run that on a daily cron, ideally early morning before the working day, and persist each baseline in your own database. For a watchlist of any real size, replace the per case refresh with a single bulk refresh call for all the CNRs first, wait, then fetch and compare each one, which is gentler on your rate limit. Because search indexing can lag a refresh by an hour or two as noted above, always do the comparison off the case endpoint, not off search. The same nightly shape powers the portfolio monitoring workflow in Litigation Portfolio Monitoring for General Counsel.
Cause lists in the real world
Cause lists are daily court schedules, and the API exposes four endpoints: available-dates tells you which dates a court has a list on file (free with authentication), causelist/search does a full text search across cause list entries, causelist/cnr/batch checks up to 100 CNRs at once for upcoming listings, and the court structure walk under /api/partner/causelist/court-structure/* gives you the codes. The 5 am brief workflow built on these is written up in The 5 am Cause List Problem. Three real world facts save a lot of debugging.
The freshness window is short. We keep cause lists for roughly the day before through seven days ahead. Cause lists change right up to the hearing, so holding a long future window would mean serving data that is likely to be revised. The government district sites keep a longer forward window, and High Courts and the Supreme Court typically publish a single day. So if you query a cause list for a date more than a week out, an empty result is expected, not a bug. Keep your lookups inside the window.
Search cause lists by name, not by case number. The causelist/search free text field is built to match advocates, judges, litigants and parties. Searching by a full case number string such as O.S./25794/2021 often returns an empty array even when the case is genuinely listed, because cause list rows are keyed and indexed differently from the case registry. Searching by advocate name, judge name or party name returns the rows reliably. If you need to go from a specific case to its listing, the robust pattern is the mapping approach below.
That said, the q field does accept a case number string directly, so you can paste something like G.R.case/533/2023 straight in. The catch is encoding: a forward slash has to be URL encoded as %2F, so on the wire that becomes q=G.R.case%2F533%2F2023. Even then a listing only exists inside the freshness window, so for anything you depend on, search by advocate, judge or party name and fall back to the mapping pattern below.
Bench number is not a room number. The number you see, for example court number 21, is the court establishment identifier, not a physical room. What advocates actually care about in a cause list is which bench, meaning which judge and bench number, is hearing the matter on a given day, because that changes through the life of a case. So when you map cause list data, carry the bench and judge fields through, since those are the fields with real value to the end user.
Linking a case to today’s list now has a first-class endpoint. POST /api/partner/causelist/cnr/batch takes 1 to 100 CNRs in one call (body { "cnrs": [ ... ] }; for a single case, pass a one-item array) and returns, per CNR, whether the case is listed for an upcoming hearing and what it is listed for. Batch checks are billed at ₹0.30 per CNR pay-as-you-go, ₹0.10 per CNR on a subscription. This replaces the old workaround of maintaining your own CNR-to-establishment-plus-case-number mapping table and picking the matching cause list row by hand; that mapping pattern still works and can save calls when a hearing is far out (if the next hearing is weeks away, there is no point querying, just tell the user when to check back), but for a watchlist the batch endpoint is the clean answer.
The filters and the pagination are not the same as the case search. Cause list search has its own set of filters: listType as CIVIL or CRIMINAL, plus judge, advocate, litigant, state, districtCode, courtComplexCode, court, courtNo, and either a single date or a startDate and endDate range. Two fields are easy to confuse: courtNo is the physical courtroom number, while court is the internal establishment identifier, and they hold different values. Set includeCourtroom=true to get the room assignment back with each row.
Pagination here is offset based, not page based. Unlike the main search, cause list search uses limit and offset. The first page is limit=20&offset=0, the second is limit=20&offset=20, and you keep adding the limit to the offset. When returnedCount is less than your limit, you have reached the last page. Both caseNumber and judge come back as arrays, because one listing can carry more than one of each.
Codes and quirks the API will not warn you about
A handful of behaviours return a clean 200 with the wrong result rather than an error, which makes them hard to spot. These are the ones support sees most, and they pair well with the longer eCourtsIndia Search Guide.
High Court codes need their bench suffix. Searching courtCodes=DLHC returns zero rows with no error, because the index keys High Courts by the full code. Use DLHC01, KAHC01, HCBM01 and so on. The base code without the number is not a real search key.
NCLT codes need a trailing zero. The NCLT benches are a special case: NCLTDL returns nothing, you have to use NCLTDL0 or NCLTMB0. The same silent empty result applies, so if an NCLT search looks dead, check the trailing zero first. There is more on the insolvency benches in our NCLT and NCLAT case status guide.
Do not lean on the acts and sections filter. The actsAndSections filter only matches the exact stored text, something like INDIAN PENAL CODE - 302 rather than IPC 302, and most cases are not indexed with that field at all, so it usually comes back empty. For anything to do with a statute or a section, put it in the general query instead, where the full text search across order bodies finds hundreds of thousands of matches.
Case category and case title are not what you expect. The category enum codes work as search filters, but the caseCategory value inside a case record is free form court text such as “Constitutional Writ”, not a code from the enum. And the search results carry no case title field at all, so build a display title yourself from the petitioners and respondents arrays, for example “ABC Ltd vs XYZ Corp”.
The search response already hands you the labels. You do not need a second enum call just to render a result. Every search response includes an enumDescriptions.enumLookup block that maps the codes in that result set to their descriptions, so WP_C arrives next to “Writ Petition (Civil)” and DISPOSED next to “Disposed”. The same response carries facets with counts, totalHits, totalPages and the activeFilters it applied, which is everything you need to draw a filter sidebar.

Errors you will hit, and how to debug them
Handle these at the client layer, not in your business logic.
| Status | Code | What it means | What to do |
|---|---|---|---|
401 | INVALID_TOKEN | Token missing, malformed, or the Bearer prefix was left off | Send Authorization: Bearer eci_live_.... If it still fails, read the current token from the dashboard. |
401 | TOKEN_INACTIVE / TOKEN_EXPIRED | Token was deactivated or has expired | Generate a fresh token in settings and update your secret store. |
403 | ACCOUNT_INACTIVE | The partner account is suspended | Check billing, then contact support. |
402 | INSUFFICIENT_CREDITS | Not enough credit left for the call | Top up; current rates are on the pricing page. |
402 | SUBSCRIPTION_REQUIRED | An active subscription is needed before credits can be spent | Subscribe from the pricing page. |
400 | INVALID_CNR | CNR is not the 16 character format | Check the pattern: four letters then twelve digits. |
400 | PAGE_SIZE_EXCEEDED | You asked for more than 100 results per page | Cap PageSize at 100 and paginate. |
400 | INVALID_FILENAME | Order filename is in the wrong form | Pass the bare filename from orderUrl, like order-1.pdf. |
404 | CASE_NOT_FOUND | CNR is not in the index, or a typo | Confirm the format. If it is genuinely missing, call refresh to fetch and add it, then read it. |
404 | ORDER_NOT_FOUND | The order file does not exist on that case | Re-read the case to get current orderUrl values. |
405 | Method not allowed | Wrong HTTP method | Refresh is POST, not GET. |
429 | RATE_LIMIT_EXCEEDED | Too many requests | Back off with exponential jitter: 1s, then 2s, then 4s. |
429 | TOO_MANY_CONVERSIONS | The PDF conversion pipeline is saturated | Wait for the Retry-After header before retrying order-md. |
500 | INTERNAL_ERROR | Rare, usually transient or a source server hiccup (not charged) | Retry with backoff, capped at four. Sustained 500s go to support with the request_id. |
Every response carries a request_id. Look in meta.request_id on both success and error payloads. Your full API request log, including failures, is visible in your dashboard, so you rarely need to build special storage just to debug. When you do raise something with support, send the request_id and the CNR and we can pull that exact call.
Rate limits, in plain numbers
Every partner account starts on the same default limits. They are generous enough that you will only brush against them if you parallelise hard on day one, and you can ask for higher ceilings on an Enterprise plan.
| Limit | Value |
|---|---|
| Per minute | 100 requests |
| Per hour | 3,000 requests |
| Per day | 50,000 requests |
| Concurrent | 10 requests |
When you cross a limit you get a 429. The right response is exponential backoff, wait one second, then two, then four, rather than hammering the endpoint. The same backoff handles the rarer TOO_MANY_CONVERSIONS on the markdown pipeline, except there you honour the Retry-After header exactly.
Prefer an AI agent? The same data over MCP
Everything in this guide is also available over the Model Context Protocol, so an AI coding assistant or agent framework can reach the same court data without hand-writing HTTP calls. Point your MCP client at https://mcp.ecourtsindia.com/mcp?token=YOUR_API_KEY — it speaks Streamable HTTP and uses the same bearer token and credits as the REST API — and you get the same records across the Supreme Court, all 25 High Courts, the district and taluka judiciary, and 18 tribunal types, exposed as 22 tools. That includes the new v4.10 list_case_orders tool for enumerating every order and judgment on a case. See the eCourtsIndia MCP page for the full tool list and client setup. For a walkthrough, see our step-by-step guide to connecting the eCourtsIndia MCP to Claude.
FAQs
How do I get the court list and all the codes?
Call GET /api/partner/enums?types=caseType,caseStatus,courtCode,stateCode for the code dictionaries, and walk GET /api/partner/causelist/court-structure/... from state to district to complex to court for the hierarchy. Both are free; the hierarchy walk needs your Bearer token but is not billed. Do not hardcode, pull at build time.
Where do I create and manage my API keys?
At ecourtsindia.com/dashboard/settings. You can generate multiple tokens and name each one by use, for example production, staging and nightly-job. Naming them lets you read usage per environment and revoke one without breaking the others.
Where can I see my API call logs?
In your dashboard. Every request, successful or failed, is logged with the endpoint, timestamp and outcome. You do not need to store request metadata yourself just to debug. Keep the request_id from responses if you want fast correlation with support.
Why does my search return cases from many states?
Because you did not add facets. The general Query searches the whole index. Add StateCodes, CourtCodes, CaseTypes and FilingYears to narrow it the way the website’s Case Status form does.
What is the difference between the litigant filter and the general query?
Litigants searches only the party fields, both sides. The general Query is a full text search that also reads the body of every order, so it returns far more, including mentions of the name inside an order rather than as a party. Use Litigants when you want actual parties.
Why do I only get 100 results?
Results are paginated, maximum 100 per page. Loop the Page parameter until hasNextPage is false to walk the whole set.
How do I get a precise result instead of a noisy one?
Search the right field and add facets first. For a name, use the Litigants (or Petitioners/Respondents) field instead of the general Query, then narrow with CourtCodes, CaseTypes and FilingYears. That removes the noise that comes from a name matching deep inside an unrelated order body. When a facet cannot express the constraint, Query also accepts operators: quoted phrases, uppercase AND/OR/NOT, parentheses and trailing wildcards.
The order field gives me a name like DLHC010001232024-order-1.pdf. How do I open it?
That is the internal storage name. Pass the bare filename from orderUrl, which is order-1.pdf, to GET /api/partner/case/{cnr}/order/order-1.pdf. In most cases you do not even need that call, since the full order text is already in files.files[].markdownContent on the case response.
Is the refresh API synchronous?
No. It queues a re scrape from the official source and returns immediately. Poll GET /api/partner/case/{cnr} until dateModified advances. Metadata updates settle quickly, but new order PDFs that need OCR, or a slow government server, can take a few minutes. It is also POST, not GET.
What happens if I refresh a CNR that is not in your database?
It gets fetched and added. Refresh is not limited to known cases. Call refresh on any valid CNR you do not see, wait, then read it through the case endpoint and it will be populated from the official source. Either the single or the bulk refresh endpoint works this way.
I refreshed a case but search still shows the old data. Why?
The case endpoint updates quickly, but the full text search index reindexes in batches, not on every change, because the dataset is enormous. A freshly refreshed or newly added case can take up to one to two hours to appear or update in search. For the freshest view of a specific case, always read it by CNR rather than relying on search.
Why does refresh sometimes take longer or fail once?
Because refresh reaches through to the official government court servers in real time, and those servers are not always fast or reliable. A slow response or an occasional retry is usually the source having a moment, not your request. Build in a poll with a timeout, and retry transient failures with backoff.
How do I get notified when a case changes?
There are no webhooks yet, so run a daily job that refreshes and fetches each watched CNR, compares caseStatus, nextHearingDate, orderCount and decisionDate against your stored snapshot, and alerts on any change. See the daily alerts section above for working code. Use bulk refresh for the watchlist and compare off the case endpoint, not search.
How does Enterprise pricing and top up work?
The Enterprise plan is a monthly subscription of ₹10,000 that becomes API credit you spend on calls. The real benefit is the rate: per call prices on Enterprise are typically about one third of the Pay As You Go rate, so the same rupee of credit goes roughly three times as far. The monthly subscription credit is use it or lose it within the billing cycle, so if you spend ₹8,000 in a month, the remaining ₹2,000 expires at the end of that cycle. You can top up at any time during the month, and top ups are charged at the same Enterprise rate. Crucially, top up credit does not expire and carries forward. The system always spends your monthly subscription credit first and only draws on top up credit once the monthly credit is used up, which is what makes topping up safe: an extra top up will never be wasted, it simply rolls into the next cycle and is consumed after that cycle’s subscription credit. Current rates and plan details are on the pricing page.
Can I fetch cause lists for next month?
No. The cause list window is roughly one day back to seven days forward, because lists change up to the hearing. Queries outside that window return empty by design.
How do I cite an API call in research?
Use the request URL, the request date and the case identifier, for example "Retrieved via eCourtsIndia API endpoint GET /api/partner/case/DLHC010001232024 on 1 June 2026". For aggregate dataset citations, contact us for a reference.
What is the difference between a signed and an unsigned order PDF?
By default the order endpoints return the watermarked, digitally signed certified true copy, which is what you file or show a client. Add ?signed=false to get the raw court PDF with no watermark or signature, which is better for your own OCR or text extraction. The cost is the same and the markdown text does not change.
The order AI analysis came back null. Is the order missing?
No. The AI analysis on order-ai is generated on demand the first time it is requested, which takes 10 to 60 seconds, then cached. A null just means it is still processing. Wait 15 to 30 seconds and retry, up to three times. If you only need text, use order-md or the markdownContent already in the case response.
What are the API rate limits?
The defaults are 100 requests a minute, 3,000 an hour, 50,000 a day, and 10 concurrent requests. Crossing one returns a 429, so back off exponentially and retry. Enterprise plans can raise these ceilings.
TL;DR
Base URL is https://webapi.ecourtsindia.com. Authenticate with Authorization: Bearer eci_live_..., and remember the Bearer prefix is mandatory. Create and name your tokens at the dashboard, where every call is also logged. Pull court codes and case types from the free enum endpoint instead of hardcoding, and pull the search’s own capabilities (valid filters, sort fields, facets) from the free GET /api/partner/search/capabilities. To replicate the website Case Status search, use the StateCodes, CourtCodes, CaseTypes and FilingYears facets and read the CNR from the top result. Use Litigants for party search, with nameMatchMode=fuzzy for spelling variants, and the general Query only when you want full text including order bodies. Query accepts Solr operators (quoted phrases, uppercase AND/OR/NOT, parentheses, trailing wildcards; never lead with *), though plain keywords plus facets stay the robust default. Paginate with Page and PageSize, maximum 100 per page. Order text is already embedded in the case response, and the order endpoint wants the bare order-1.pdf filename. Refresh is an asynchronous POST that also pulls in CNRs we do not have yet; poll dateModified, and note that search can take one to two hours to reflect a refresh. Build case alerts with a daily refresh, fetch and compare, and check listings with the batch cause-list endpoint, POST /api/partner/causelist/cnr/batch, which takes up to 100 CNRs at once. Cause lists cover one day back to seven days forward and search best by name. Court-structure lookups now live under /api/partner/causelist/court-structure/* with your Bearer token (free, no billing). Order PDFs default to a signed certified true copy, add signed=false for the raw court PDF. The order AI analysis is generated on demand, so retry if it comes back null. Default rate limits are 100 calls a minute, 3,000 an hour and 50,000 a day. Every response carries a request_id for support.
Keep building
More for developers and power users on the eCourtsIndia blog and site: the eCourtsIndia Search Guide, the Litigant Search Guide, Building a Legal Due Diligence Engine, From Case Lookup to Case Intelligence, and Litigation Portfolio Monitoring for General Counsel. Start at the API home, read the live docs, check pricing, manage keys at the dashboard, or run a search at ecourtsindia.com/search.
Sources
eCourtsIndia API landing page, ecourtsindia.com/api. API pricing, ecourtsindia.com/api/pricing. API docs, ecourtsindia.com/api/docs. Live partner API surface, https://webapi.ecourtsindia.com. Search behaviour, result counts, the Nemin Virendra Vora litigant scenario and the order file structure were all verified against the live eCourtsIndia data layer on 1 June 2026 using the example CNR DLHC010001232024 (Shubham Pratap Singh vs Kendriya Vidyalaya Sangathan, W.P.(C) 138/2024, Delhi High Court).
