CourtListener Citation Lookup API: Resolve and Validate Citations

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 = found, 404 = valid citation that CourtListener could not look up, 300 = matched more than one item, 400 = unrecognized reporter). One request looks up at most 250 citations.

Reviewed and corrected on 12 October 2026. This is general information, not legal advice. The request examples follow Free Law Project's documentation; they were not run, because the endpoint needs a CourtListener token.

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.
  • POST requests. Free Law Project's documentation shows POST requests. Send text= (up to 64,000 characters, roughly 50 pages) or a volume/reporter/page set.
  • A token, and low free-tier limits. It uses the same Authorization: Token auth as the rest of the CourtListener API. Citation lookup is throttled at 250 citations per request and 60 valid citations per minute, and the documentation says the endpoint shares the request throttle rates of the other CourtListener APIs, so plan for each call to count against your account's general API limits, which are low on a free account.
  • Status per cite, not per request. 200 found, 404 valid but could not be looked up, 300 multiple matches, 400 unrecognized reporter, 429 past the 250th citation in the request.
  • The 2026 use case. Hallucination-checking LLM-generated briefs before they reach a human or a judge. A match shows a case exists, not that it says what the brief claims.
  • Out of scope. Statutes, law journals, id., and supra citations. Route those elsewhere.
Spot the problem
Question 1 of 4

An AI-drafted brief cites a case with a plausible name, reporter and page number. You run the citation through a case-law lookup and it comes back 'not found'. What is the sensible next step?

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. The hallucination database maintained by researcher Damien Charlotin says it has identified 2,184 cases worldwide in which a court or tribunal addressed hallucinated content, typically fake citations (last updated 10 October 2026). The database is broader than the United States and counts both lawyers and self-represented litigants.

The pattern is a familiar one: 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.

The cheapest first line of defense that scales is programmatic citation verification. Every brief, memo, and answer your product generates can be run through a citation-validation pass before a human relies on it. Free Law Project built its lookup API as a guardrail against hallucinated citations, and the documentation says so. It is a good place to start for case citations.

A short history of the lookup tool

The API is newer than the problem it solves. For years before it existed, Free Law Project hosted an unofficial page. A person could look up one citation at a time and get HTML back. The launch post says that was "never great for automated systems," yet many organizations used it as the easiest way to look up a citation.

The engine under the new API is Eyecite, a citation parser that Free Law Project built with the Harvard Library Innovation Lab. It is open source under a BSD licence. Both CourtListener and Harvard's Caselaw Access Project use it. Its authors, Jack Cushman, Matthew Dahl and Michael Lissner, described it in a 2021 paper in the Journal of Open Source Software (eyecite on GitHub, JOSS paper). To catalogue reporter abbreviations and their variants, the project says it analyzed more than 50 million citations.

Free Law Project announced the API on April 16, 2024, and Lissner opened the announcement with the story that made the problem famous: a lawyer's brief "contained citations that ChatGPT hallucinated, and when he asked ChatGPT whether it made things up, it insisted it did not" (Free Law Project, April 2024). The best-known case of this kind is Mata v. Avianca, in the Southern District of New York. Its sanctions opinion of June 22, 2023 is worth reading for two reasons. First, Judge Castel did not condemn the technology: "Technological advances are commonplace and there is nothing inherently improper about using a reliable artificial intelligence tool for assistance. But existing rules impose a gatekeeping role on attorneys to ensure the accuracy of their filings." Second, the record shows how cheap the missing check was. The lawyer testified that he knew free sites existed where you could type in a citation and see the decision. He admitted entering the citation for one of the fabricated cases, and when the judge asked what he found, he answered: "I couldn't find it." The judge replied: "And yet you cited it in the brief to me." The court imposed a $5,000 penalty (Opinion and Order on Sanctions, S.D.N.Y., June 22, 2023).

Everything in the rest of this guide is a way to make that "couldn't find it" moment impossible to ignore.

The endpoint, end to end

The endpoint is POST /api/rest/v4/citation-lookup/. The documentation shows POST requests only. 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. This is the example from Free Law Project's documentation:

curl -X POST "https://www.courtlistener.com/api/rest/v4/citation-lookup/" \
  --header "Authorization: Token YOUR_CL_TOKEN" \
  --data 'text=Obergefell v. Hodges (576 U.S. 644) established the right to marriage among same-sex couples'

The response is a JSON array, one entry per citation found. This is the documentation's example response for that request, with the cluster abbreviated:

[
  {
    "citation": "576 U.S. 644",
    "normalized_citations": ["576 U.S. 644"],
    "start_index": 22,
    "end_index": 34,
    "status": 200,
    "error_message": "",
    "clusters": ["...one large cluster object here..."]
  }
]

The shape that matters: status is the HTTP-style code for this specific citation, not the request itself. 200 means found. 404 means the citation looked valid but CourtListener could not look it up. 300 means it matched more than one item. 400 means the reporter is not recognized. Note the field is normalized_citations (an array), not a single string, and start_index/end_index give the citation's position in the source text, which lets you wrap it in a link.

The clusters array is empty on a 404. On a 300 it lists each matching cluster.

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 "https://www.courtlistener.com/api/rest/v4/citation-lookup/" \
  --header "Authorization: Token YOUR_CL_TOKEN" \
  --data 'volume=576' \
  --data 'reporter=U.S.' \
  --data 'page=644'

That returns the same response as above. 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. It sends a form body, as the documentation's curl examples do:

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, data={"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) # unverified: needs a second check
        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', 105221)], 'ambiguous': [], 'not_found': [], 'bad': []}
# Expected output, not run (needs a token). 105221 is the cluster id CourtListener's search API
# returns for Brown v. Board of Education, 347 U.S. 483.

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 status taxonomy, from Free Law Project's documentation, is sparse but useful:

  • 200: a valid citation, found in CourtListener. clusters has the match.
  • 300: a valid citation that matched more than one item. The documentation's example is "1 H. 150", where the abbreviation "H." can mean Handy's Ohio Reports, the Hawaii Reports or Hill's New York Reports, and two of those series have a case at that volume and page.
  • 404: a valid citation that CourtListener could not look up. Treat it as unverified.
  • 400: something that looks like a citation, but the reporter is not in the system (the documentation's example is "33 Umbrella 422").
  • 429: on a single citation, it means the citation came after the first 250 in the request and was parsed but not looked up. Separately, if a request pushes you past the 60-per-minute throttle, the HTTP response itself is 429 with a body that carries the time you can retry.

In production, send 404 and 300 to review, and log 400 (telemetry on bad input patterns).

Confidence and the false-negative problem

A 200 status means the endpoint found a case matching the citation. A 404 does not prove the case does not exist. The documentation defines it as a valid citation that CourtListener "was unable to look up", which can have several causes. CourtListener may not hold the opinion (for example a very recent one, or one from a court or reporter it does not cover well), the citation may be wrong, or the case may be invented. The endpoint cannot tell you which. A non-canonical abbreviation is not one of the causes, because the documentation shows such citations being normalized ("576 US 644" comes back as "576 U.S. 644" with a 200).

For a defensive pipeline, treat 404 as "needs human review" rather than "this is definitely fake." A 404 plus a failed search by case name is a stronger signal. Even then, check the court's own site before you say a case is fake.

What practitioners and developers say

Three Hacker News comments on this problem are worth reading.

A one-time programmer wrote in September 2026 that the lawyers he knows are "very fixated" on fake citations, which amuses him because "case citations have a well-defined syntax and would be relatively easy to check programmatically" (jordanpg, Hacker News, September 2026). He is right about the syntax, and Eyecite handles that part. Matching a citation to a real opinion is a second step. Whether the opinion says what the brief claims is a third.

A commenter who said they have used general-purpose AI heavily to draft legal documents described why the problem is hard to spot: "You can get really comfortable after checking its output a few times and getting no false citations, and then BAM, it'll put three in the next motion it writes" (qingcharles, Hacker News, June 2026). That is the case for checking every document automatically.

Another commenter added the point that exists-versus-valid matters. In their view "Court rules always require you only cite good law," so a real case that has since been overturned still gets you in trouble (DannyBee, Hacker News, September 2025). A lookup that returns 200 settles the first question only. Add a check for later history before anything goes to a court.

Building a verification pipeline

The minimum viable structure, in order:

  1. Extract citations from your LLM output. POST the whole block to /citation-lookup/ and let it do the extraction for you.
  2. Triage by status. Collect the 404 and 300 entries into a "needs attention" set.
  3. 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, that is a stronger hallucination signal, and a person should still confirm it.
  4. 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.
  5. Log everything. Track resolved-versus-unresolved rates over time per model, so you can see whether prompt changes help.
Loading diagram...

Steps 1 and 2 catch the easy cases. Steps 3 and 4 are where false positives get handled.

When CourtListener's coverage gaps bite

CourtListener product screenshot

The documentation describes a citation database of 18,136,787 citations (read on 12 October 2026), and Free Law Project says its collection holds more than nine million decisions from more than 2,000 courts. That is large, and it is still a defined collection. Practical limits to be aware of:

  • Coverage differs by court. If your documents cite a specific court or reporter, test a few real citations from it before you rely on 404 as a signal.
  • Treatises and other secondary sources are outside the endpoint's stated scope, which is case citations. If your LLM cites a Wright & Miller section, this endpoint cannot tell you whether the section exists.
  • Statutory citations are out of scope. The documentation says the API "does not look up statutes, law journals, id, or supra citations". For verifying USC, CFR, Federal Rules of Procedure, 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. Models can get statute numbers wrong too, so check both lanes.

Rate limits and bulk verification

Citation lookup has its own throttles, from Free Law Project's documentation (read 12 October 2026):

  • 250 citations per request. Anything past the 250th is parsed but not matched, and comes back with a 429 status. A follow-up request that starts at the next start_index continues the job.
  • 60 valid citations per minute. A request that pushes you past it is refused with an HTTP 429 and a wait_util timestamp (ISO-8601) telling you when to retry.
  • 64,000 characters per text payload, roughly 50 pages. Larger bodies are blocked.
  • The general API throttle also applies. The documentation says the endpoint has the same request throttle rates as the other CourtListener APIs, and that even requests with no citations in them can be throttled. Plan for each lookup to count as a request against your account's limits. Free Law Project's membership page lists a free account at 5 requests per minute, 50 per hour and 125 per day.

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 volume: at 60 valid citations a minute, a 5,000-document backlog with 20 cites each is 100,000 citations, or about 28 hours of lookups, so queue it and throttle.

If you are running citation verification across thousands of documents (a brief-template library, a bulk audit of historical filings), do the math early. A membership raises the general API limits (Free Law Project membership). For sustained high volume, ask Free Law Project directly what it can offer, because the documentation does not describe a higher citation-lookup tier.

Edge cases worth handling explicitly

A few cases that consistently break naive implementations:

Typos and non-canonical abbreviations. The documentation shows "576 US 644" coming back with normalized_citations of "576 U.S. 644" and a 200. Use the normalized form when you store or display the citation.

Ambiguous abbreviations. A 300 can return several clusters, as in the "1 H. 150" example. Show the candidates to a reviewer. Do not pick the first one.

Long documents. Past 250 citations, the extra ones come back as 429 and are not looked up. Start the next request at the first throttled citation's start_index, as the documentation describes, and keep your own record of which citations were checked.

Pinpoints, short forms and parallel cites. The documentation does not describe how these are returned, and these cases were not tested. Run a few of your own real documents through the endpoint and check the output before you build on it.

id. and supra. The documentation says the API does not look these up and points to Eyecite for that. If you need them resolved, run Eyecite yourself and keep track of the last fully resolved citation in your code.

Statutory citations. The API does not look up statutes. A cite like "17 CFR 240.10b-5" is not a case citation, so do not expect a match. If your pipeline does not branch on citation type, you may flag valid statutory citations as problems and waste your reviewers' time.

What to ship this week

If you are building anything that generates legal text with LLMs:

  1. Wire /citation-lookup/ (POST form) into the output pipeline before any rendering.
  2. Send 404 and 300 to review, log everything.
  3. Add a case-name fallback search for a stronger hallucination signal.
  4. Branch on citation type so statutory cites get routed to a statute verifier, not CourtListener.
  5. Set up a dashboard tracking unresolved-citation rate by model and prompt version.

For statute-side verification (USC, CFR, Federal Rules, state codes, Executive Orders), pair this with a primary-law API so each kind of citation goes to a source built for it.

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 database. It returns a per-citation status so you can see which citations resolved. A resolved citation shows the case exists, not that it supports your point.

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 was found, 404 means the format is valid but CourtListener could not look it up, and 300 means it matched more than one item. A 404 is a flag for review, not proof that a case is fake.

Is the CourtListener citation lookup API free? There is no separate per-call charge in the documentation. You need a CourtListener account token, and the limits are the constraint: 250 citations per request, 60 valid citations per minute, and your account's general API limits, which are low on a free account (5 requests per minute, 50 per hour and 125 per day on the membership page).

What are the rate limits? A single request looks up at most 250 citations, and the endpoint is throttled to 60 valid citations per minute (CourtListener docs, read 12 October 2026). The text payload is capped at 64,000 characters, about 50 pages. Citations past the 250th come back with status 429, and a request over the per-minute throttle gets an HTTP 429.

Does a 404 mean the citation is hallucinated? Not on its own. A 404 means CourtListener could not look the citation up, which can happen when it does not hold the opinion. Pair the 404 with a failed case-name search and a check on the court's own site before flagging an actual hallucination.

Can it verify statute citations? No. The documentation says the endpoint does not look up statutes, law journals, id., or supra citations, so route statutory cites like "17 CFR 240.10b-5" to a separate statute API.

GET or POST? The v4 documentation shows POST requests, with text or the volume/reporter/page fields as form data. Free Law Project also keeps an older unofficial lookup page at /c/ that returns HTML for one citation at a time. It is meant for people, not automated systems.

Are there alternatives? Yes. LawDroid launched CiteCheck AI on June 27, 2025, a site where lawyers can verify the citations in a document (LawSites, June 2025). If you are building your own pipeline, you can also run Eyecite yourself for extraction, which Free Law Project releases as open source.

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 the US statutes, regulations and court rules these citations point to, see the US primary law API.

Connect our US primary law database.
Every US statute, regulation, constitution, and executive order via REST, MCP or SQL. 5.4M+ sections, section-level citations, and links to the official source. Plus a free open dataset.
Updated October 12, 202621 min read

New legal AI guides, weekly.

Priyansh Khodiyar

Priyansh Khodiyar

Co-Founder & CTO

Priyansh leads engineering and AI at Vaquill AI: the pipelines that pull statutes, regulations and court rules from every US jurisdiction's official publisher, and the REST API, MCP server and open dataset that serve them.