Short answer: to verify case citations with an API, POST your text (or a volume/reporter/page triple) to CourtListener's /api/rest/v4/citation-lookup/ with a Token header. It extracts every citation, normalizes it, and returns a per-citation status (200 = resolved to a real case, 404 = valid format but not in the corpus, 300 = ambiguous, 400 = unrecognized reporter). One request validates up to 250 citations.
TL;DR
- What it is. CourtListener's
/citation-lookup/takes legal text or a citation triple and tells you whether each citation resolves to a real case, returning the cluster and opinion IDs when it does. - One method. It is POST only in v4. Send
text=(up to 64,000 characters, roughly 50 pages) or avolume/reporter/pageset. The old GET form is gone. - Free with a token. It uses the same
Authorization: Tokenauth as the rest of the CourtListener API. Citation lookup is throttled at 250 citations per request and 60 valid citations per minute, not by a daily request count. - Status per cite, not per request.
200resolved,404valid but not found,300ambiguous,400unrecognized reporter,429over the cap. - The 2026 use case. Hallucination-checking LLM-generated briefs before they reach a human or a judge.
- Out of scope. Statutes, law journals,
id., andsupracitations. Route those elsewhere.
In the citation-lookup response, what does a per-citation status of 404 mean?
This post walks the endpoint mechanics, a working curl and Python example, the response shape, and how to compose a verification pipeline that catches the failures the endpoint itself does not.
Part of our MCP and developer guide series.
For related MCP / API / developer coverage, see How to Use the CourtListener API: Auth, Endpoints, Rate Limits and CourtListener MCP: US Federal & State Court Research.
Why you should care about this one endpoint
The sanctions cases keep coming. As of mid-2026, the hallucination tracker maintained by researcher Damien Charlotin lists hundreds of US incidents involving lawyers, pro se litigants, and even a sitting judge filing documents with citations that do not exist.
The pattern is always identical: a generative model invents a plausible-looking citation, the human filer does not check, the citation lands in front of a judge, the judge runs it, the case does not exist. Sanctions follow.
There is exactly one technical fix that scales: programmatic citation verification. Every brief, memo, and answer your product generates should be run through a citation-validation pass before a human ever sees it.
If you are shipping legal AI in 2026 without this step, you are shipping malpractice.
CourtListener's /citation-lookup/ is the cheapest and most reliable starting point. It is what most production legal-AI apps use as the first line of defense, ours included.
The endpoint, end to end
The endpoint is POST /api/rest/v4/citation-lookup/. There is no GET form in v4. It accepts two request shapes against the same URL.
Text block (the workhorse). Send a brief, a memo, or an LLM response in the text field. The endpoint extracts every citation it recognizes and validates each one. This is what you want for bulk verification:
curl -X POST \
-H "Authorization: Token YOUR_CL_TOKEN" \
-H "Content-Type: application/json" \
-d '{"text": "The defendant relies on Brown v. Board of Education, 347 U.S. 483 (1954), and on United States v. Jones, 565 U.S. 400 (2012)."}' \
"https://www.courtlistener.com/api/rest/v4/citation-lookup/"
The response is a JSON array, one entry per citation found:
[
{
"citation": "347 U.S. 483",
"normalized_citations": ["347 U.S. 483"],
"start_index": 24,
"end_index": 36,
"status": 200,
"error_message": "",
"clusters": [
{
"id": 111143,
"case_name": "Brown v. Board of Education",
"case_name_short": "Brown",
"absolute_url": "/opinion/111143/brown-v-board-of-education/",
"date_filed": "1954-05-17",
"...": "more fields"
}
]
}
]
The shape that matters: status is the HTTP-style code for this specific citation, not the request itself. 200 means resolved. 404 means valid format but not in the corpus. 300 means ambiguous. 400 means the reporter is not recognized. Note the field is normalized_citations (an array), not a single string, and start_index/end_index let you wrap the citation in a link in the source text.
The clusters array is empty unless status == 200.
Direct lookup by volume, reporter, page. If you already have the three parts of a citation parsed, skip the text extractor and POST them directly:
curl -X POST \
-H "Authorization: Token YOUR_CL_TOKEN" \
-H "Content-Type: application/json" \
-d '{"volume": 347, "reporter": "U.S.", "page": 483}' \
"https://www.courtlistener.com/api/rest/v4/citation-lookup/"
Most teams over-engineer their own citation parser before discovering they could have just shipped the text to this endpoint. The extractor is Eyecite, the same parser Free Law Project built with the Harvard Library Innovation Lab after analyzing more than 50 million American citations (Free Law Project, April 2024).
A minimal Python client
The same call in Python, with status triage built in:
import requests
URL = "https://www.courtlistener.com/api/rest/v4/citation-lookup/"
HEADERS = {"Authorization": "Token YOUR_CL_TOKEN"}
def verify(text: str) -> list[dict]:
resp = requests.post(URL, headers=HEADERS, json={"text": text}, timeout=30)
resp.raise_for_status()
return resp.json()
def triage(results: list[dict]) -> dict:
buckets = {"resolved": [], "ambiguous": [], "not_found": [], "bad": []}
for r in results:
cite = r["citation"]
status = r["status"]
if status == 200:
buckets["resolved"].append((cite, r["clusters"][0]["id"]))
elif status == 300:
buckets["ambiguous"].append(cite)
elif status == 404:
buckets["not_found"].append(cite) # probable hallucination
elif status == 400:
buckets["bad"].append(cite) # unrecognized reporter
return buckets
brief = "We rely on Brown v. Board of Education, 347 U.S. 483 (1954)."
print(triage(verify(brief)))
# {'resolved': [('347 U.S. 483', 111143)], 'ambiguous': [], 'not_found': [], 'bad': []}
That is the whole client. Everything below is about what you do with the not_found and ambiguous buckets.
What the status codes actually mean
The endpoint's internal status taxonomy is sparse but useful:
200: citation found and resolved,clustershas one or more matches.300: valid citation matching multiple items, ambiguous. Common with parallel cites and short-form references.404: valid citation format but not in CourtListener's database. This is your hallucination signal.400: a citation-like pattern whose reporter is not in the system. Treat as malformed.429: you blew past the cap (more than 250 citations in the request, or more than 60 valid citations a minute). Back off and retry.
In production, you want to alarm on 404 (probable hallucination), warn on 300 (manual review), and log 400 (telemetry on bad input patterns).
Confidence and the false-negative problem
Citation lookup is high-precision but not perfect-recall. A 200 status means the endpoint found a real case matching the citation. A 404 does not always mean the case does not exist; it might mean:
- The citation is in a reporter CourtListener does not index (some state intermediate appellate reporters, some specialized federal courts like the Court of Federal Claims at certain depths).
- The citation is to an unpublished disposition that CourtListener has not ingested.
- The citation format is correct but uses a non-canonical reporter abbreviation.
- The citation is to a 2026 opinion not yet in the corpus.
For a defensive pipeline, treat 404 as "needs human review" rather than "this is definitely fake." The combination of 404 plus also failing to find the case by case-name search is the stronger signal for actual hallucination.
A real production pipeline does both.
Building a verification pipeline
The minimum viable structure, in order:
- Extract citations from your LLM output. POST the whole block to
/citation-lookup/and let it do the extraction for you. - Triage by status. Collect the
404and300entries into a "needs attention" set. - Secondary check on 404s. For each
404, run a/search/query against the case name (if you can parse one from surrounding text) and against the citation as keywords. If the search also returns nothing, this is your high-confidence hallucination flag. - Block or annotate. Either refuse to render the LLM output until the citations are clean, or render with visible warnings next to the suspect citations. The choice depends on your product surface and your tolerance for false positives.
- Log everything. Track resolved-vs-hallucinated rates over time per model. This is the only way to know whether your prompt changes are making the model more or less honest.
Most legal-AI teams stop at step 1. The ones who ship products that do not embarrass themselves go to step 4.
When CourtListener's coverage gaps bite

CourtListener's case-law coverage is the best free corpus on the web, but it is not exhaustive. Practical gaps to be aware of:
- State trial court opinions are sparse outside a few states (California, New York, Texas have decent coverage; many others do not).
- Specialized federal courts (Tax Court, Court of Federal Claims, Court of International Trade) are partially indexed but with varying depth.
- Treatise and secondary-source citations are entirely out of scope. If your LLM cites a Wright & Miller section, CourtListener cannot tell you whether the section exists.
- Statutory citations are out of scope. CourtListener focuses on opinions and dockets, not statutes. For verifying USC, CFR, Federal Rules of Procedure, 50-state codes, state constitutions, state court rules, or Executive Orders, you need a separate statute-lookup layer.
A real verification pipeline that ships in production needs both case-law lookup (CourtListener) and statute lookup (somewhere) because LLMs hallucinate both. Statutes are arguably easier to hallucinate because the structure is so regular.
Rate limits and bulk verification
Citation lookup has its own throttles, separate from the general CourtListener API limits (CourtListener docs, June 2026):
- 250 citations per request. Anything past the 250th is parsed but not matched, and the overflow comes back with
429. - 60 valid citations per minute. This, not a daily request count, is the ceiling that bites at scale.
- 64,000 characters per
textpayload, roughly 50 pages. Larger bodies are rejected.
The text form is efficient because one POST validates every citation in the input in a single call. For a typical brief with 15 to 30 citations, that is one request. The per-minute cap is what to plan around: at 60 valid citations a minute, a 5,000-document backlog with 20 cites each is a multi-hour job, so queue it and throttle.
If you are running citation verification across thousands of documents (a doc-matrix product, a brief-template library, a bulk audit of historical filings), do the math early.
Talk to Free Law Project about a higher-rate plan if you are sustained-loading. The Free Law Project does not exist to subsidize VC-backed legal-AI scale, and the team is reasonable about working out commercial arrangements for legitimate use.
Edge cases worth handling explicitly
A few cases that consistently break naive implementations:
Pinpoint citations. "Brown v. Board, 347 U.S. 483, 495 (1954)" resolves to the same cluster as the bare cite, but the pinpoint page does not appear in the response. If your downstream needs the pinpoint, store it from the input text.
Parallel cites. "Roe v. Wade, 410 U.S. 113, 93 S. Ct. 705 (1973)" returns two 200 entries that resolve to the same cluster. Dedupe by clusters[0].id before counting hits.
Short forms. "Brown, 347 U.S. at 495" usually resolves if there is a full cite earlier in the input text and you POST the whole block. The endpoint is reasonably good at carrying context within a single request.
Id. citations. "Id. at 495" cannot resolve in isolation. You need to track the most recent fully-resolved cite in your code and apply it. CourtListener does not do this for you.
Statutory citations. "17 CFR 240.10b-5" is out of scope as noted; it will return a 404 even though the section exists. If your pipeline does not branch on citation type, you will flag valid statutory citations as hallucinations and waste your reviewers' time.
What to ship this week
If you are building anything that generates legal text with LLMs:
- Wire
/citation-lookup/(POST form) into the output pipeline before any rendering. - Alarm on
404, warn on300, log everything. - Add a case-name fallback search for high-confidence hallucination flagging.
- Branch on citation type so statutory cites get routed to a statute verifier, not CourtListener.
- Set up a dashboard tracking hallucination rate by model and prompt version.
For statute-side verification (USC, CFR, Federal Rules, 50-state codes, Executive Orders), pair this with a primary-law API. The same two-lane verification architecture is what most production legal-AI apps converge on.
FAQ
What is the CourtListener citation lookup API?
It is a POST endpoint at /api/rest/v4/citation-lookup/ that takes legal text or a volume/reporter/page triple, extracts the citations, normalizes them, and matches each one against CourtListener's case-law database. It returns a per-citation status so you can tell a real case from a fabricated one.
How do I verify case citations with an API?
Send the text to the citation-lookup endpoint with an Authorization: Token header and read the status on each returned citation. 200 means the cite resolved to a real opinion, 404 means the format is valid but no matching case exists, and 300 means the cite is ambiguous. A 404 is your hallucination signal, though it warrants a second check before you call it fake.
Is the CourtListener citation lookup API free? Yes. It uses the same free token as the rest of the CourtListener API. There is no per-call charge. The constraints are throughput, not cost: 250 citations per request and 60 valid citations per minute.
What are the rate limits?
A single request looks up at most 250 citations, and the endpoint validates at most 60 valid citations per minute (CourtListener docs, June 2026). The text payload is capped at 64,000 characters, about 50 pages. Exceeding these returns status 429.
Does a 404 mean the citation is hallucinated?
Not on its own. A 404 means CourtListener has no matching case, which also happens for unpublished dispositions, reporters it does not index, non-canonical abbreviations, and very recent opinions. Pair the 404 with a failed case-name search before flagging an actual hallucination.
Can it verify statute citations?
No. The endpoint does not look up statutes, law journals, id., or supra citations. A cite like "17 CFR 240.10b-5" comes back as not found even though the section exists, so route statutory cites to a separate statute API.
GET or POST?
POST only in v4. Older guides show a GET form with the citation in the query string; that path is gone. Send text or the volume/reporter/page fields in a JSON body.
Are there alternatives?
Several products wrap this endpoint or build alongside it. LawDroid launched CiteCheck AI in June 2025 (LawSites, June 2025) as a consumer-facing checker, and various open-source verifiers call the same API. For most engineering teams, calling /citation-lookup/ directly is the cheaper and more controllable path.
For more on the broader CourtListener surface (the seven endpoints, auth, gotchas), see the CourtListener API guide, the deeper verify-before-filing workflow, and how this fits a layered citation-verification design. For how the records-vs-data lane choice affects your architecture, see the court records vs court data API breakdown.

For more on a statutes API paired with an in-app research workbench that wires citation lookup into its verification layer, see /legal-api, or email contact@vaquill.ai.
New legal AI guides, weekly.
Further Reading
Court Records API vs Court Data API: Which One Your App Actually Needs
Read postHow to Use the CourtListener API: Auth, Endpoints, Rate Limits
Read postLegal Research API Integration: Pull Statutes Into Your App
Read postUS Statutes API: The 2026 Guide to USC, CFR, and State Code Access
Read postLegal API in 2026: What It Is, What It Returns, and How to Pick One
Read postUSC API: How to Pull US Code Sections Programmatically
Read post
Co-Founder & CTO
Priyansh leads engineering and AI at Vaquill, from the matter workbench to drafting, document comparison, document matrix, and citation-verified research.