Legal citation resolution is the job of turning a human-written citation string into one specific, retrievable section of law. It sounds like string parsing. It isn't, because the same provision gets cited a dozen different ways, several of those ways are ambiguous across states, and some of the most common "citations" aren't citations at all. The hardest engineering problem in a resolver isn't coverage. It is knowing when to refuse.
Reviewed and corrected on 12 October 2026. This is general information, not legal advice.
A resolver that returns a plausible wrong section is worse than one that returns nothing, because nothing gets checked and a wrong answer gets filed.
TL;DR
- A citation is a human string, not an identifier. "42 U.S.C. 1983", "42 USC 1983", and "Section 1983" all point at one section, and only one of them is a well-formed citation.
- State abbreviation forms are set by each state, not by a national standard. O.C.G.A. (Georgia), MCA (Montana) and IC (Indiana) are all real, and a resolver has to know which jurisdiction a short form belongs to.
- The single-regex approach fails in a specific, predictable way. A forward-scanning search doesn't stop at the shortest match, so widening a pattern to catch one more state makes it match a fragment inside another string.
- A wrong match is more expensive than a miss. The most valuable property of a citator is a clear not-found with a suggestion, not a confident guess.
actIdis stable and not guessable. Take it from a resolve or search response. Hand-built ids usually 404.- Unresolved citations are still charged, failed calls are not. The parsing and lookup work happens either way, so an honest not-found costs the same as a hit.

An AI draft cites "Rule 12(b)(6)" with no court or jurisdiction named anywhere in the document. Your checker has to decide what to do. What's the sound move?
This post sits in our legal data infrastructure series.
For related coverage, see Parsing Legal Citations in Code: Bluebook Forms, State Variants, and the Ones You Must Refuse for the string formats behind every case below, Hybrid Search Over 20 Million Legal Passages: Why Semantic Alone Fails on Law for where a resolved citation enters the ranking, and Amendment History and Point-in-Time Law: What a Legal API Can and Cannot Tell You.
The shape of the problem
Every legal AI product hits the same wall. A model, a user, or a contract emits a string that looks like a citation, and something has to turn it into a document. Between the string and the document sit four classes of hard case, each breaking a different naive assumption.
A short history: how a citation got to be a string
Lawyers were solving this problem long before software. For case law, the trick was the printed reporter. A Hacker News commenter argued in a 2017 thread that, unlike a URL, "47 F.3d 167 will always refer to the same page of the same case. Forever." (rayiner, Hacker News, January 2017) For reporters that mostly holds. It's the abbreviations for state statutes, covered in Class 2 below, that don't hold still.
The style manual that tells lawyers how to write all this down has a contested birth. Harvard Law School's own account is that the Bluebook was first compiled in 1926 by Erwin Griswold, then an editor of the Harvard Law Review and later dean of the law school (Harvard Law School). In 2016 Yale law librarian Fred Shapiro and his co-author Julie Graves Krishnaswami published "The Secret History of the Bluebook" in the Minnesota Law Review (100 Minn. L. Rev. 1563). They argued that the real origin was a single-page pamphlet by Karl Llewellyn, an editor of the Yale Law Journal. Shapiro put the contrast this way in an interview: "the original, created by Karl Llewellyn, was only one page of rules." (Harvard Law Record, February 2016) The same article describes the book that grew from it as a 582-page edition. One page of rules to 582 pages is a decent summary of why a single regex never survives contact with citation practice.
There were also attempts to make citations independent of print. In August 1996 the American Bar Association's House of Delegates resolved that "All jurisdictions adopt a system for official citation to case reports that is equally effective for printed case reports and for case reports electronically published." (South Carolina law library guide to universal citation) Adoption has been slow and uneven. The same guide notes that Illinois adopted a universal citation in 2011 and North Carolina in 2019, effective 2021. That unevenness is the practical lesson for anyone building a resolver. There's no single authority to implement. There are many authorities, and they agree on the easy cases.
Class 1: federal forms that vary by punctuation
Start with the easy one, which is still not easy. Consider the civil rights cause of action.
| String a user might send | Well formed | Resolvable |
|---|---|---|
42 U.S.C. 1983 | Yes | Yes |
42 U.S.C. § 1983 | Yes | Yes |
42 USC 1983 | Informal | Yes |
42 U. S. C. 1983 | Reporter style with spaces | Yes |
Section 1983 | No | No: resolved: false, missReason: ambiguous_jurisdiction |
1983 | No | No: resolved: false, missReason: ambiguous_jurisdiction |
Punctuation and the section symbol are cosmetic, and normalization handles them. We ran each of these through the Vaquill resolve endpoint on 12 October 2026, and the table shows what came back. The interesting rows are the last two.
Section 1983 is a colloquial name. So is Section 230, so is Rule 12(b)(6), so is the ADA. They carry meaning to a lawyer and almost no structure to a parser. A resolver that treats them as citations will fail or, worse, guess. In our run Section 230 came back malformed (it reads as a court or docket abbreviation) and Rule 12(b)(6) came back ambiguous_jurisdiction with a hint to pass state.
1983 is a number. It is also a year. A bare number gets a no, because the failure mode of guessing is silent and unbounded.
Class 2: state abbreviations, set jurisdiction by jurisdiction
There is no national standard for abbreviating a state code. Each state's courts and legislature set the form. O.C.G.A. is Georgia, MCA is Montana, IC is Indiana and G.L. is Massachusetts, which numbers by chapter and section rather than title and section. Some forms are used by more than one jurisdiction. The full inventory of shape classes, and the parsing traps that break them, is in Parsing Legal Citations in Code.
What matters here is the consequence for the resolver. A form that more than one jurisdiction prints cannot be disambiguated from the string alone. It needs jurisdiction context from elsewhere in the request, the document, or the conversation, and the right behavior is to require that context or refuse. A resolver that picks the likelier jurisdiction is right most of the time and catastrophically wrong the rest of the time, in a way the caller can't detect. In our 12 October run, IC 35-42-1-1 resolved to Indiana because its structure is Indiana's, and the other abbreviations we tried (O.C.G.A. § 16-5-1, 8 CCR 1206-2, 22 CCR 76227) resolved to Georgia, Colorado and California without a state hint.
Which forms a given resolver actually parses is an empirical question. Send the form, read the response, store the result. The forms we accept are listed in the citation formats reference.
Class 3: regulations, which have a different tree
Statutes are title, chapter, section. Regulations are title, part, section, and the section number itself contains a period that is structural rather than decorative.
29 C.F.R. 1910.1200 is the hazard communication standard. 1910 is the part, 1200 is the section within it. Splitting on the period and treating 1200 as a subsection loses the part, and the part is what disambiguates.
State administrative codes are worse, because the naming isn't shared. The California Code of Regulations, the Texas Administrative Code, and the Code of Maryland Regulations each structure and cite themselves differently. Across 56 jurisdictions the regulations corpus runs to more than 1.5 million sections, each with its own citation grammar.
What a resolve call returns
curl -s -G https://api.vaquill.ai/api/v1/us/statutes/resolve \
-H "Authorization: Bearer $VQ_KEY" \
--data-urlencode "cite=42 U.S.C. 1983"
{
"resolved": true,
"inputCitation": "42 U.S.C. 1983",
"section": {
"actId": "USC_T42_C21_S1983",
"citation": "42 U.S.C. § 1983 (2026)",
"title": "Civil action for deprivation of rights",
"corpusType": "USC",
"state": "federal",
"actStatus": "in_force",
"goodLawStatus": "good_law",
"currentThrough": "2026-09-18",
"sourceUrl": "https://uscode.house.gov/view.xhtml?req=granuleid:USC-prelim-title42-section1983&num=0&edition=prelim"
},
"subsection": null,
"citationOutsideFilters": null,
"creditsConsumed": 2
}
That is a real response from 12 October 2026. The section object is trimmed to 9 of its 148 fields, and processingTimeMs is left out.
The important branch is the other one. When a citation does not resolve, resolved is false and section is null, and that reads as unverified. An unresolved citation can carry a missReason: ambiguous_jurisdiction, out_of_scope_corpus, former_law, malformed or not_held, with a one-sentence missReasonHint. For a form that more than one jurisdiction uses, the optional state parameter says which jurisdiction you mean. It is a constraint. When we sent 22 CCR 76227 with state=co, the response was resolved: false with citationOutsideFilters naming the California section and excludedBy: ["state"], rather than a guess that silently returns another state's law.
Class 4: renumbered, repealed, moved, or superseded
This is the class that separates a lookup table from a citator.
A citation written in 2014 may point at a section that no longer exists under that number. Four things could have happened:
- Renumbered. The provision is intact under a new number. The right answer is the new section plus a note that the number moved.
- Moved. The provision went to a different title or chapter, sometimes a different code entirely.
- Repealed. The number is now empty. The right answer is "repealed," not "not found," because those mean different things to a lawyer.
- Superseded. A newer enactment covers the ground, and the old text may still be printed. This is the dangerous one, because a naive retriever serves it as current law.
Why the single-regex approach fails
Almost every citation resolver starts as one regular expression, and then gains an alternative each time a state fails to resolve. The failure isn't that the regex gets slow. It is that a forward-scanning search does not stop at the shortest match. The engine returns the first position where any alternative succeeds, so a widened pattern can match a fragment sitting partway through a longer string. That produces a match, not an error, and the fragment then gets minted into a permanent identifier and stored. A narrow regex misses, and a miss is visible. A widened regex mislabels, and a mislabel is invisible until a lawyer catches it. The anchoring and staged-parse fixes are worked through in detail in the parsing post.
One fix belongs here, because it is a property of the resolver rather than of the pattern. Round-trip the result. Take the section you resolved to, render its own citation, and resolve that. If a code's own published citation form doesn't come back to that code's sections, the resolver has a hole. Making that a test rather than an assumption is the difference between a citator you can trust and one that has never been challenged.
Knowing when you do not know
A citator's most important property is its refusal behavior. Coverage is second.
When resolution fails, three things belong in the response:
- A clear not-found, distinguishable from an error and from an empty result set.
- A reason. Unknown abbreviation, ambiguous jurisdiction, structurally valid but no such section, and repealed are four different situations, and the caller should be able to branch on them.
- A suggestion. If the string almost matched something, say what. The most common cause of a failed resolve is a caller who has the right provision and the wrong form.
Returning the nearest neighbor as if it were a hit optimizes a demo and breaks a product. A wrong section that looks right gets pasted into a memo. A not-found gets checked by a human in thirty seconds.
What the resolve API gives you
Resolution is one operation. The rest of the surface exists because a resolved citation is rarely the end of the question.
| Operation | MCP tool | What it is for |
|---|---|---|
| Resolve a citation string | resolve_statute_citation | Turn a human string into an actId and an official source URL |
| Resolve up to 50 unique citations at once | resolve_statute_citations_batch | Check a brief's worth of cites without N round trips, at the same per-citation price |
| Find and resolve every citation in a block of text | none (REST: POST /us/statutes/extract-citations) | Check a whole document in one call |
| Fetch many sections at once | get_sections_batch | Pull metadata for known actId values in one call |
| Find what cites this section | get_section_cited_by | Build a local citation graph |
| Find the analog in other states | get_section_cross_state | Answer "what is the equivalent of this in the other 49" |
| Pull defined terms | get_section_definitions | Get the definitions that govern the section you resolved |
| Read adjacent sections | get_section_neighbors | Recover the structural context a bare citation drops |
| Read change history | get_section_changes | See what moved on that section |
Two deserve a note.
Batch resolve changes an integration's shape. A brief has 40 citations in it, and forty sequential resolves plus forty sequential fetches is a slow, fragile pipeline. Batch turns citation checking into one pass, billed per citation at the single-resolve price (a round-trip win, not a discount), and the citation lookup recipe walks that pass end to end.
Neighbors is underrated. A citation names a section, but the meaning of that section frequently lives in the one before it, which carries the definitions and scope. When you resolve for an LLM's benefit, neighbors is often the difference between a correct answer and a confident misreading.
The actId contract
An actId is a stable identifier for one citable section. Two examples:
USC_T42_C21_S1983for 42 U.S.C. 1983.SAL_PL116-136_DVA_TII_S1109for section 1109 of the CARES Act, in the Statutes at Large corpus.
They look guessable. They aren't. The second carries a public law number, a division, and a title, and no pattern-matching on the first will let you construct it.
The rule: take the actId from a response and store it. Search returns it, resolve returns it, and every downstream operation takes it. Building one by hand produces a 404, which fails loudly but avoidably.
This is deliberate, not an accident of the schema, and the section id reference explains the shape. An id you can construct is an id callers will construct wrongly. An id you must fetch keeps the corpus as the authority on its own structure.
What happens when you send garbage
Unknown filter values are rejected with a 422, not silently matched against nothing. A typo in a corpusType or source value returns an error rather than an empty result set that looks like a coverage gap. That empty-set behavior is the most common cause of "your API has no data for X" reports against systems that allow it.
The source filter's error message lists every valid code. Send a bad one and the response tells you the whole vocabulary, at the point of failure, which is where documentation actually gets read.
Provenance, and why it belongs in a resolution post
Resolved sections carry the publisher's own source URL where the publisher exposes one, and the sourcing rule is a government publisher, never a commercial aggregator and never one as a fallback. How to tell where a statute actually came from walks the fields that make that checkable.
That matters more for resolution than for search. When a resolver says "this citation points at this text," the only way to audit the claim is to open the publisher's page and compare. A resolution result without a checkable source URL is an assertion.
Resolved sections carry sourceUrl, the government's own page for the section, and the API never substitutes a third-party compiler for it.
What to design around
Resolve returns today's section, and earlier text is a separate call. A citation maps to the section in force now. To read it as it stood on a past date, take the actId from the resolve response and pass asOf=YYYY-MM-DD to the section body endpoint. Where we hold the publisher's printed editions (the CFR annual editions, for one), that returns the edition governing your date; for the US Code most historical dates fall outside the editions we hold. Elsewhere the text is rebuilt from the first change we observed after your date, and the asOf block reports isBounded when that rests on when capture began. A date before capture can come back unavailable. Also available are amendment history per section, a lastAmendedYear, a yearFrom/yearTo currency filter that reads the last amendment year the publisher credits, and per-section diffs on watched sources going forward.
Some sections carry no amendment credit and no other date the filter can read, because some publishers print none. Those are excluded once you set either currency bound, which is correct and also something to know before you set one.
Freshness is a field you can read. The free GET /boards call returns cadence and lastRetrievedAt for every watchable source, so you can see when a source was last pulled and compare that against its cadence before you rely on a resolved section being current. Where a publisher states what its text is current through, that date rides on the section as currentThrough.
You can check current per-jurisdiction coverage in the API coverage docs, try resolution against live data in the playground, or read the parsers themselves, since the collection layer is published at open-us-law.
FAQ
What is legal citation resolution?
It is the process of turning a written legal citation into a specific, retrievable section of law. A resolver normalizes the string, identifies which code it belongs to, parses the structural parts, and looks up the matching section. The output should be a stable identifier plus the publisher's own source URL.
Why do state citation formats vary so much?
Because each state's courts and legislature set their own citation style, and there is no binding national standard for primary law abbreviations. Georgia uses O.C.G.A., Montana uses MCA, Indiana uses IC, and Massachusetts uses a chapter-and-section form. Some forms are shared across jurisdictions, which is why jurisdiction context matters.
Is "Section 230" a citation?
No. It is a colloquial name for 47 U.S.C. 230. The string carries no title and no code, so a resolver cannot parse it as a citation. In our 12 October 2026 run the Vaquill resolve endpoint returned resolved: false for it (malformed), and the same for "Section 1983" and "Rule 12(b)(6)" (ambiguous_jurisdiction). Write the code out in full, as in 47 U.S.C. 230.
What happens when a cited section has been repealed?
A good citator distinguishes repealed from not-found, because they mean different things. Repealed means the number existed and is now empty. Not-found means the resolver could not identify the citation at all. If a provision was renumbered or moved instead, the useful answer is the successor section plus an explicit note that the number changed.
Can I build an actId myself instead of calling resolve?
No. Ids are stable but not guessable, and hand-built ones usually 404. USC_T42_C21_S1983 includes the chapter, which the citation string doesn't name, and session law ids carry public law numbers and division structure. Take the id from a search or resolve response and store it alongside your own record.
Why am I charged for a citation that did not resolve?
Because the work is the same either way. Normalization, code identification, and corpus lookup all run whether or not the string matches a section. Charging only for hits would push a resolver toward returning a guess instead of an honest not-found. Calls that fail on our side, meaning 5xx errors, aren't charged.
How do I resolve 40 citations from one brief efficiently?
Use the batch resolve endpoint (POST /us/statutes/resolve, up to 50 unique citations per call) rather than looping. It turns a fragile sequence of round trips into one pass, and billing is per citation at the single-resolve price. If the 40 citations sit in a document, the extract-citations endpoint finds and resolves them in one call. Pair either with the neighbors operation if you are feeding results to an LLM, since the governing definitions frequently sit in the preceding section.
Does the API support point-in-time citation lookup?
Yes, in two steps. Resolve gives you the actId and today's section, then asOf=YYYY-MM-DD on the section body endpoint returns the text as it stood on that date: a held published edition where one exists, or a reconstruction from recorded changes elsewhere, with isBounded marking an answer that rests on when capture began. Amendment history, a lastAmendedYear per section, a yearFrom/yearTo currency filter, and per-section change records sit alongside it.
What is the difference between citation resolution and citation verification?
Resolution answers "which section does this string point at." Verification answers "does that section support the claim it was cited for." A system needs both, and the verification layers are covered in our four-layer citation verification post.
New legal AI guides, weekly.
Further Reading
Hybrid Search Over 20 Million Legal Passages: Why Semantic Alone Fails on Law
Read postParsing Legal Citations in Code: Bluebook Forms, State Variants, and the Ones You Must Refuse
Read postWhat a Legal Data API Should Guarantee: The Schema Contract
Read post128 Fields on One Statute: What Rich Legal Metadata Actually Buys You
Read postFrom a State Website to an API Response: Every Layer a Legal Corpus Passes Through
Read postAmendment History and Point-in-Time Law: What a Legal API Can and Cannot Tell You
Read post
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.