Brent Resh Law PLLC

Solo practice, IP and corporate · Solo practitioner

A simple Python citation checker with a Vaquill API key

Brent Resh, a solo practitioner in Las Vegas, replaced a two-pass highlighter review with a short Python script: eyecite to find the citations, Vaquill to verify them, and a local cache so revisions cost nothing.

Vaquill is the piece that made statutes verifiable rather than merely findable.
Brent M. Resh, Attorney, Brent Resh Law PLLC

I am a solo practitioner. Nothing I file goes out without every citation checked, and for years that meant reading the draft twice with a highlighter. Now a short Python script does the first pass. It has three pieces, and none of them is hard.

1. Find the citations: eyecite

eyecite is an open-source Python library that pulls citations out of plain text, cases and statutes and more, and hands them back as structured objects with the matched text and its position. Install it, call get_citations() on your draft, and you have a list to verify instead of a document to re-read.

from eyecite import get_citations

text = open("draft.txt").read()
for c in get_citations(text):
    print(type(c).__name__, "->", c.matched_text())

One thing worth knowing: eyecite is built around standard reporter and code abbreviations, so it may not recognize the shorthand your jurisdiction actually uses. In Nevada that means NRS 41.141 rather than Nev. Rev. Stat. § 41.141. The long form comes back as a FullLawCitation; the shorthand comes back as nothing at all.

It is designed to be extended. Give its tokenizer one more pattern, built with the token class it already uses, and the extractor speaks your dialect. This adds Nevada's shorthand in eight lines. Local rules of court are the same idea with a different pattern.

import re
from eyecite.models import SectionToken, TokenExtractor
from eyecite.tokenizers import EXTRACTORS, Tokenizer

NRS = TokenExtractor(
    r"\b(N\.?\s?R\.?\s?S\.?\s*\d+[A-Z]?\.\d+"
    r"(?:\([A-Za-z0-9]+\))*)",
    SectionToken.from_match, flags=re.I)

tok = Tokenizer(EXTRACTORS + [NRS])
cites = get_citations(text, tokenizer=tok)
# "NRS 41.141(2)(a)" now comes back too

A note on what you get back: the extended pattern returns NRS 41.141(2)(a) as an UnknownCitation, so you have the matched text but not the parsed fields you would get from a citation eyecite recognizes natively. For a checker that hands the string straight to a resolver, the matched text is all you need.

2. Verify the statutes: Vaquill

Extraction tells you what the draft claims to cite; it does not tell you whether the section exists or what it says today. That is the job of Vaquill's API. Its citation resolver takes a citation string and returns either the exact current section, with a link to the official source, or a clear "no."

For a checker, the "no" is the whole point. A mistyped section number, a repealed provision, or a hallucinated cite is caught before a human reads the draft. When the text itself matters, a second call returns the section's current body, so the proposition in the draft can be checked against the words of the statute.

import os, requests

API = "https://api.vaquill.ai/api/v1"
H = {"Authorization": f"Bearer {os.environ['VAQUILL_API_KEY']}"}

def resolve(cite, state=None):
    url = f"{API}/us/statutes/resolve"
    r = requests.get(url, headers=H, timeout=20,
        params={"cite": cite, "state": state})
    r.raise_for_status()
    return r.json()

# -> {"resolved": bool, "section": {...}}

3. Don't pay twice: cache

Drafts get revised and re-checked many times, and most citations do not change between versions. A small local cache, citation in and verified result and date out, means each section is resolved once and every later pass costs nothing. Expire entries after a reasonable interval so amendments still surface.

import json, datetime as dt

CACHE, TTL = "cites.json", 90  # days

def load():
    try: return json.load(open(CACHE))
    except FileNotFoundError: return {}

def checked(cite, state=None):
    db, today = load(), dt.date.today()
    hit = db.get(cite)
    if hit:
        on = dt.date.fromisoformat(hit["on"])
        if (today - on).days < TTL:
            return hit["ok"]
    res = resolve(cite, state)
    ok = res.get("resolved", False)
    db[cite] = {"ok": ok, "on": str(today)}
    json.dump(db, open(CACHE, "w"), indent=1)
    return ok

That is the whole checker: extract, resolve what is new, report what failed. Vaquill is the piece that made statutes verifiable rather than merely findable, and I am grateful for it.

Getting started

Sign up at app.vaquill.ai. Create an API key in your dashboard settings and put it in VAQUILL_API_KEY. Calls go to https://api.vaquill.ai/api/v1 with the header Authorization: Bearer <your key>, and pricing is per call in credits, free to query before you spend any.

The documentation has the endpoints and examples, and there is an MCP server if you would rather call it from an AI assistant than from a script.

New legal AI guides, weekly.

Brent M. Resh

Attorney, Brent Resh Law PLLC · Esq., registered patent attorney (NV, AZ)

Brent is a solo practitioner in Las Vegas working in corporate transactions, intellectual property and appellate litigation. He clerked for the Supreme Court of Nevada. He is a Vaquill AI customer and wrote this piece himself.

Put real US law behind your product.