Parse the US Code XML (USLM) Into Clean JSON in Python

Title card for the Vaquill AI guide: Parse the US Code XML (USLM) Into Clean JSON in Python

Count the section elements in the Title 18 file of the US Code and you get 1,431. Only 1,399 of them have an address in the Code. The other 32 are sections of other laws, quoted inside notes, and they use the same tag. A parser that trusts the tag name invents 32 sections, and that is only the first surprise in the file.

Short answer: the Office of the Law Revision Counsel (OLRC) publishes each title of the United States Code as an XML file in USLM. A parser has to do more than walk the tree. It has to skip old acts quoted inside the notes, read each section's status, normalise addresses that use a dash character citations do not, and take cross-references from the body of the law instead of the notes. This guide builds that parser in Python and runs it on release point 119-120, downloaded on 12 October 2026.

Written and checked on 12 October 2026. This is general information, not legal advice; check the cited statute or opinion before relying on it.

TL;DR

  • Download a title from OLRC and record the release point. This guide uses 119-120, which OLRC calls Public Law 119-120 (10/07/2026). Put it on every record.
  • Match element names without the namespace. The five titles read use the USLM 1.0 namespace, and GPO's public schemas use a different one.
  • Skip section elements inside notes. Title 18 has 1,431 of them and only 1,399 have a Code address.
  • Keep repealed, omitted, transferred and renumbered sections with their status. Some hold a range or two addresses.
  • Definitions are text. The five titles have no def or term tags, although the schema defines both.
  • A parsed copy is not the official Code, as OLRC says in its own words, quoted below.
Spot the problem
Question 1 of 4

Your parser finds more section-like blocks in a title than the title has numbered sections. Where do the extras most likely come from?

Official text, as of 12 October 2026

Citation: 1 U.S.C. 204(a), the subsection on the Code as evidence of the laws of the United States.

What it says (read today in the Title 1 file at release point 119-120): "whenever titles of such Code shall have been enacted into positive law the text thereof shall be legal evidence of the laws therein contained". For other titles, the subsection says the current edition shall "establish prima facie the laws of the United States". OLRC's FAQ adds that "it is the printed version of the Code that is recognized under law as evidence of the laws of the United States in all courts, tribunals, and public offices". Official links: OLRC's download page and GovInfo's link for 1 U.S.C. 204, which redirects to a PDF of the 2024 edition.

What changed in 2025 and 2026: release points keep moving. Files from release point 119-103, which we downloaded on 15 September 2026, give different counts than 119-120 for two of the five titles: Title 42 went from 8,481 sections with a Code address to 8,482, and Title 18 went from 29 quoted sections to 32. OLRC's pages also carried a notice on 12 October 2026 that the site is in a temporary format, while the download files worked.

What this guide ran on

  • Files: the XML zips for Titles 1, 11, 18, 29 and 42 at release point 119-120, downloaded on 12 October 2026 from the releasepoints/us/pl/119/120/ path on uscode.house.gov. Each file names its release point as Online@119-120.
  • Software: Python 3.12.1, standard library only. The {*} wildcard in the code needs Python 3.8 or later.
  • Limit: every finding below comes from these five titles at this release point. Titles 1, 11 and 18 are positive law, and Titles 29 and 42 are not. The other 49 titles were not checked.

What you are parsing

OLRC, an office of the House of Representatives, prepares and publishes the Code. Its download page says, "Each update of the United States Code is a release point." It lists each title as XML, XHTML, PCC (photocomposition codes) and PDF, plus a zip of all titles. One title's XML is one file inside one zip, at an address you can build from the release point:

curl -O https://uscode.house.gov/download/releasepoints/us/pl/119/120/xml_usc29@119-120.zip
unzip xml_usc29@119-120.zip     # one file: usc29.xml

The file's metadata says whether the title is positive law, meaning Congress enacted the title itself as law. Titles 1, 11 and 18 say yes, and Titles 29 and 42 say no. What "codified" means in law explains why that matters.

A short history of the Code in XML

The USLM User Guide says that "Following a 1999 feasibility study on XML/SGML the Committee on House Administration adopted XML as a data standard for the exchange of legislative documents" (footnote marker omitted). It also says OLRC began producing an XML version of the Code for download on July 30, 2013.

A day later, a Hacker News story announced the release (The entire US Code is now online in XML). In that thread a commenter, liscovich, wrote: "It took the US Govt 17 years to release 200,000 pages of the US code in XML" (Hacker News, July 2013).

Read the file before writing code

This is the top of the Title 29 file, trimmed:

<uscDoc xsi:schemaLocation="http://xml.house.gov/schemas/uslm/1.0 USLM-1.0.15.xsd" xml:lang="en"
        identifier="/us/usc/t29" xmlns="http://xml.house.gov/schemas/uslm/1.0" ...>
<meta>
  <dc:title>Title 29</dc:title>
  <dc:type>USCTitle</dc:type>
  <docNumber>29</docNumber>
  <docPublicationName>Online@119-120</docPublicationName>
  <property role="is-positive-law">no</property>
  <dc:publisher>OLRC</dc:publisher>
  <dcterms:created>2026-04-17T11:15:56</dcterms:created>
  <dc:creator>USCConverter 1.7.2</dc:creator>
</meta>

The namespace is the 1.0 one. The USLM User Guide names http://xml.house.gov/schemas/uslm/1.0. GPO's usgpo/uslm repository publishes later schemas, and its 2.0.17 file sets the target namespace to http://schemas.gpo.gov/xml/uslm. The README says "USLM XML files may validate against any of the adopted schema versions." So a parser written against a GPO sample finds nothing in a Code file. Strip the namespace and match local names. The USLM and Akoma Ntoso guide covers identifier anatomy, so this guide skips it.

The release point lives in docPublicationName. The created stamp belongs to the file. In release point 119-120 it reads 2026-04-17 for Titles 11 and 29, 2026-09-09 for Title 1, 2026-09-21 for Title 18 and 2026-10-09 for Title 42, so it is not a release date.

Levels are plain elements, from title and chapter down to subitem. Each has a num whose value attribute holds a normalised label, an optional heading, and content or chapeau for the words. A section is addressed by title and section, as in /us/usc/t29/s207, and a chapter carries its part, as in /us/usc/t18/ptI/ch68.

Step by step

Step 1: load the file

ElementTree is enough. ln strips the namespace from a tag, load reads the release point from the metadata, and real_sections yields only sections that have a Code address.

import xml.etree.ElementTree as ET

LEVELS = {"section", "subsection", "paragraph", "subparagraph", "clause",
          "subclause", "item", "subitem", "subsubitem"}
NOT_BODY = {"notes", "note", "quotedContent", "sourceCredit"}
EN_DASH = "\N{EN DASH}"

def ln(el):
    return el.tag.rsplit("}", 1)[-1]  # drop the namespace, whatever it is

def load(path):
    root = ET.parse(path).getroot()
    release = root.find("{*}meta/{*}docPublicationName").text  # Online@119-120
    return root, release.split("@")[-1]

def real_sections(el):
    """Yield sections that have a Code address, without looking inside notes."""
    for child in el:
        if ln(child) in ("notes", "note", "quotedContent"):
            continue
        if ln(child) == "section" and child.get("identifier"):
            yield child
        else:
            yield from real_sections(child)

For big titles, stream the same file:

def stream_sections(path):
    inside = 0  # depth inside notes or quoted content
    for event, el in ET.iterparse(path, events=("start", "end")):
        name = ln(el)
        if name in ("notes", "note", "quotedContent"):
            inside += 1 if event == "start" else -1
        elif event == "end" and name == "section" and el.get("identifier") and not inside:
            yield el
            el.clear()

On the 113.7 MB Title 42 file, loading the tree and walking it took 0.68 seconds and about 584 MB of peak memory. Streaming took 1.09 seconds and about 36 MB. Both found the same 8,482 sections. These are two runs each on an Apple M4 Max laptop, so read them as an order of magnitude. In a small container, stream.

Step 2: find the real sections

A quoted section looks like this, trimmed from a note under 29 U.S.C. 1001:

<quotedContent origin="/us/pl/101/239/tVII/s7861/d/1">
<section><num value="302">“SEC. 302.</num><heading> GENERAL EFFECTIVE DATES.</heading>
  <subsection><num value="a">“(a)</num> ...

It is a section of another law, quoted in a note. It has no identifier, and origin points at the public law provision that the note quotes it from. The counts at release point 119-120:

TitleSize (MB)Positive lawsection elementsWith a Code addressInside notesRepealed, omitted, transferred, renumbered
10.3yes5339140, 0, 0, 0
114.4yes28228022, 0, 0, 0
1812.1yes1,4311,3993275, 0, 0, 3
2911.7no87784433107, 16, 7, 0
42113.7no8,8618,482379698, 202, 909, 0

In these five files every quoted section lacks an identifier, so either test alone gives the same count. Keep both in case a later release point differs.

Step 3: record the status

Status is an attribute. OLRC's schema file, USLM-1.0.15.xsd, lists 19 values, and the USLM User Guide lists the same 19. The five titles use four of them: repealed, omitted, transferred and renumbered. Keep the status as a string and do not assume four.

Three real records, trimmed, with the source's dash characters shown as escapes:

{"identifier": "/us/usc/t29/s7", "num": "7", "status": "repealed",
 "heading": "Repealed. Pub. L. 86\u20133, §§ 15, 23, Mar. 18, 1959, 73 Stat. 11, 13; Pub. L. 96\u2013470, title I, § 110, Oct. 19, 1980, 94 Stat. 2239"}
{"identifier": "/us/usc/t29/s18...18c", "num": "18 to 18c", "status": "transferred", "heading": "Transferred"}
{"identifier": "/us/usc/t18/s221 /us/usc/t18/s222", "num": "221, 222", "status": "renumbered",
 "heading": "Renumbered §§ 216, 217]"}

Four traps sit in those lines.

  • A repealed section's heading holds the repeal history in place of a title.
  • One record can cover many sections. A transferred range is written s18...18c, and a renumbered pair holds two addresses separated by a space. In all five titles, every record with a range or a second address has a status.
  • Status is not only on sections. Title 18 has 8 repealed chapters, such as /us/usc/t18/ptI/ch68, plus three repealed paragraphs, a subparagraph and a subsection. Title 11 has one repealed paragraph. The sections inside the repealed chapters carry the status too. Title 42 has five repealed subsections, but all five sit in quoted text inside notes, so none is in the Code itself.
  • The destination is in the notes. The notes under 29 U.S.C. 18 to 18c say that section 18 was transferred to section 191 of Title 42. The heading only says "Transferred".

The test for each section element comes down to this:

Loading diagram...

Step 4: build records

A level's own words sit in content, chapeau and continuation, and those hold inline footnotes: a note element inside the text plus a ref marker with an idref. Counting every note element of type footnote in the file, Title 11 has 45, Title 18 has 267 and Title 29 has 212. A few sit inside the notes, which the parser skips anyway. itertext() would put them in the sentence, so text_of skips them:

def text_of(el):
    """Text of an element, minus inline footnotes and their markers."""
    parts = [el.text or ""]
    for c in el:
        if not (ln(c) == "note" or (ln(c) == "ref" and c.get("idref"))):
            parts.append(text_of(c))
        parts.append(c.tail or "")
    return " ".join("".join(parts).split())

def node(el):
    num = el.find("{*}num")
    out = {"identifier": el.get("identifier"), "keys": keys(el.get("identifier")),
           "level": ln(el), "num": num.get("value") if num is not None else None}
    if el.get("status"):  # not only sections: a paragraph or chapter can be repealed
        out["status"] = el.get("status")
    head = el.find("{*}heading")
    if head is not None:
        out["heading"] = text_of(head)
    out["text"] = " ".join(text_of(c) for c in el if ln(c) in ("content", "chapeau"))
    cont = [text_of(c) for c in el if ln(c) == "continuation"]
    if cont:
        out["continuation"] = " ".join(cont)
    kids = [node(c) for c in el if ln(c) in LEVELS]
    if kids:
        out["children"] = kids
    return out

In 11 U.S.C. 101(56A) the raw text of content reads 44 So in original. The term “term overriding royalty” means ..., because the marker "4" and the footnote's own number "4" run together. After text_of it starts with "The term". One limit: a continuation can come after the sub-levels it closes, as in 29 U.S.C. 207(a)(2), and the record keeps it in its own field without its position.

Next, wrap a section with its status, source credit, references and release point. Then flatten it, because a database or a search index wants one flat record per addressable unit, each pointing at its parent. Keep the tree for display.

def section_record(sec, release):
    rec = node(sec)
    rec.setdefault("status", None)
    credit = sec.find("{*}sourceCredit")
    rec["source_credit"] = text_of(credit) if credit is not None else None
    rec["code_refs"] = sorted(set(body_refs(sec)))
    rec["release_point"] = release
    return rec

def flatten(rec, parent=None):
    """One flat record per addressable unit, each pointing at its parent."""
    kids = rec.pop("children", [])
    rec["parent"] = parent
    yield rec
    for kid in kids:
        yield from flatten(kid, rec["identifier"])

Run it on Title 29, once the body_refs helper from Step 5 and the keys helper from Step 7 are defined in the same file:

root, release = load("usc29.xml")
records = [section_record(s, release) for s in real_sections(root)]
print(len(records), "sections, release point", release)
844 sections, release point 119-120

Step 5: take cross-references from the body

Every ref has an href in the same path language as the identifiers. The targets take four forms: /us/usc/... for the Code, /us/pl/... for a public law, /us/stat/... for a Statutes at Large page and /us/act/... for an act by date and chapter. Title 29 has 23,520 ref elements with an href: 7,357 to the Code, 10,499 to public laws, 5,089 to Statutes at Large pages and 575 to acts.

Notes carry references too. 11 U.S.C. 750 says, "The trustee may not distribute a security except under section 751 of this title." Collecting every ref under the section returns /us/usc/t11/s748 and /us/usc/t11/s751, because section 748 appears in a Senate report note under the section. Walking the body alone returns section 751:

def body_refs(el):
    """Code references in the words of the law, not in notes or the source credit."""
    for c in el:
        if ln(c) in NOT_BODY:
            continue
        href = c.get("href") or ""
        if ln(c) == "ref" and href.startswith("/us/usc/"):
            yield href.replace(EN_DASH, "-")
        yield from body_refs(c)

The tags are not complete either. 29 U.S.C. 1027 mentions "section 1024(a)(2) or (3) of this title", and no ref in the section points at section 1024. If you need every citation, run a citation extractor over the text as well and treat the tags as a head start. Also, 163 of Title 29's href values contain an en dash, which Step 7 explains.

Step 6: pull out defined terms

The schema defines def and term elements, and the User Guide describes them. The five titles contain none, so extraction is a pattern. This one looks for "the term" or "the terms" followed by a curly-quoted word, or a quoted word at the start of a unit, and checks the sub-levels, because a definition often hides one level down:

import re

DEF = re.compile(r"(?:^|\bthe terms? )\N{LEFT DOUBLE QUOTATION MARK}"
                 r"([^\N{RIGHT DOUBLE QUOTATION MARK}]+)"
                 r"\N{RIGHT DOUBLE QUOTATION MARK}", re.I)

def first_term(rec):
    m = DEF.search(rec["text"])
    if m:
        return m.group(1)
    for kid in rec.get("children", []):
        term = first_term(kid)
        if term:
            return term

def definitions(rec):
    found, missed = [], []
    for kid in rec.get("children", []):
        term = first_term(kid)
        (found if term else missed).append(term or kid["identifier"])
    return found, missed

Test it on sections you know. 11 U.S.C. 101 has 88 numbered paragraphs, and the pattern finds a term in all 88. 29 U.S.C. 203, the definitions section of the chapter that section 201 titles the "Fair Labor Standards Act of 1938", has 25 lettered units, and the pattern finds a term in 24. The one miss, /us/usc/t29/s203/o, starts "In determining for the purposes of sections 206 and 207 of this title the hours for which an employee is employed". That is a rule about hours and not a definition, so the miss is right.

"88 of 88" does not mean 88 correct definitions. The pattern finds definition-shaped text, so read the misses and sample the hits. In 11 U.S.C. 101 the paragraph numbered 56A sits between 53C and 53D in the file, so keep the file's order and do not sort on the number.

Step 7: normalise the identifiers

Many addresses use an en dash (U+2013) where the citation uses a hyphen. In Title 42, 1,841 of 8,482 addresses hold one. In Title 29 it is 23 of 844, and Titles 1, 11 and 18 have none. The citation "42 U.S.C. 1320a-7" is spelled /us/usc/t42/s1320a\u20137 in the file. The num text after the section sign also holds a narrow no-break space (U+202F), which text_of turns into an ordinary space because str.split() treats it as whitespace. Exact string comparison fails on both.

Store a normalised key next to the raw address and compare on the key. The keys helper splits on spaces because one record can hold two addresses:

def keys(identifier):
    # one section can carry several addresses, and citations use a plain hyphen
    return [i.replace(EN_DASH, "-") for i in identifier.split()]

The lookup on Title 42, with the output below it:

root, release = load("usc42.xml")
records = [section_record(s, release) for s in real_sections(root)]

raw_ids = {r["identifier"] for r in records}
by_key = {k: r for r in records for k in r["keys"]}
want = "/us/usc/t42/s1320a-7"   # from the citation 42 U.S.C. 1320a-7
print(want in raw_ids, want in by_key)
print(by_key[want]["identifier"].encode("ascii", "backslashreplace").decode(),
      "|", by_key[want]["heading"][:48])
print(sum(EN_DASH in i for i in raw_ids), "of", len(raw_ids), "identifiers hold an en dash")
False True
/us/usc/t42/s1320a\u20137 | Exclusion of certain individuals and entities fr
1841 of 8482 identifiers hold an en dash

A range such as s42\u20131...42b keeps its dots and gets a hyphen. Only the two ends are written down, so a citation to a section inside the range matches no key. Treat a range as one record for a whole run of non-current sections.

What the output looks like

For a short section with a source credit, 29 U.S.C. 625, the record is below. Two long strings are trimmed with "...", and the dash characters in the source are shown as escapes.

{
 "identifier": "/us/usc/t29/s625", "keys": ["/us/usc/t29/s625"],
 "level": "section", "num": "625", "heading": "Administration",
 "text": "The Secretary shall have the power\u2014",
 "children": [
  {"identifier": "/us/usc/t29/s625/a", "keys": ["/us/usc/t29/s625/a"], "level": "subsection", "num": "a",
   "heading": "Delegation of functions; appointment of personnel; technical assistance",
   "text": "to make delegations, to appoint such agents and employees, ... under this chapter;"},
  {"identifier": "/us/usc/t29/s625/b", "keys": ["/us/usc/t29/s625/b"], "level": "subsection", "num": "b",
   "heading": "Cooperation with other agencies, employers, labor organizations, and employment agencies",
   "text": "to cooperate with regional, State, local, and other agencies, ... of this chapter."}
 ],
 "status": null,
 "source_credit": "(Pub. L. 90\u2013202, § 6, Dec. 15, 1967, 81 Stat. 604.)",
 "code_refs": [],
 "release_point": "119-120"
}

For a bigger section the flat form is the one to store. 29 U.S.C. 207 flattens to 89 records, and these are the first eight with the text cut at 48 characters for display:

/us/usc/t29/s207             section      parent=None                     text=''
/us/usc/t29/s207/a           subsection   parent=/us/usc/t29/s207         text=''
/us/usc/t29/s207/a/1         paragraph    parent=/us/usc/t29/s207/a       text='Except as otherwise provided in this section, no'
/us/usc/t29/s207/a/2         paragraph    parent=/us/usc/t29/s207/a       text='No employer shall employ any of his employees wh'
/us/usc/t29/s207/a/2/A       subparagraph parent=/us/usc/t29/s207/a/2     text='for a workweek longer than forty-four hours duri'
/us/usc/t29/s207/a/2/B       subparagraph parent=/us/usc/t29/s207/a/2     text='for a workweek longer than forty-two hours durin'
/us/usc/t29/s207/a/2/C       subparagraph parent=/us/usc/t29/s207/a/2     text='for a workweek longer than forty hours after the'
/us/usc/t29/s207/b           subsection   parent=/us/usc/t29/s207         text='No employer shall be deemed to have violated sub'

What goes wrong

  • Hard-coding the 2.x namespace and finding nothing in a Code file.
  • Counting quoted sections as Code sections. Title 18 would gain 32.
  • Dropping non-current sections. Title 42 has 698 repealed, 202 omitted and 909 transferred sections that still have headings and history notes.
  • Reading note references as cross-references. 11 U.S.C. 750 would cite section 748.
  • Matching citations without normalising the dash. 1,841 Title 42 sections would go missing.
  • Expecting term markup and returning zero definitions without a warning.
  • Leaving footnotes in the text, so a stray "So in original." reaches your index.
  • Mixing release points in one corpus without a release point on each record.

Provenance: what a parsed copy is

Put the release point and the download date on every record you keep, and on any page that shows the text. OLRC's FAQ says the online versions "are produced using the same database" from which files go to GPO for the printed volumes, and that versions from private companies "are unofficial." The USLM User Guide is plainer about copies made downstream: "Once data moves beyond the direct control of the Office of the Law Revision Counsel, the Office cannot vouch for its accuracy."

A JSON file built with this parser is your compilation of OLRC's compilation. Say which release point it came from, link the OLRC page, and send readers to the source when the text matters. 1 U.S.C. 204(a), quoted above, draws a line between positive-law titles such as 1, 11 and 18 and titles such as 29 and 42. The provenance guide for legal data goes through the sources, and point-in-time law explains why a record also needs the date it is true for.

When you would rather not run this

Keeping the parser alive is the ongoing job. Each release point can change what a title contains, as the 119-103 and 119-120 counts above show, and point-in-time text, definitions, cross-references and citation matching all need upkeep. If that is not the work you want, the Vaquill AI API has endpoints for a US Code section's full text, the defined terms that govern it, the sections around it, the sections that cite it, its change history, and a citation resolver. The guide to pulling US Code sections programmatically shows the calls, and the GovInfo guide covers the other free route. The schema contract post shows what a record looks like from an API.

This guide is part of US Law Data: The Complete Guide, a map of where US law comes from and how to use it.

FAQ

What is USLM?

USLM is the United States Legislative Markup, an XML schema that the House, the Senate and GPO use for bills, laws and the Code. OLRC publishes the Code in it. See the USLM and Akoma Ntoso guide for the format and its identifiers.

Where can I download the US Code in XML?

From OLRC's download page, uscode.house.gov/download.html, as one zip per title or one zip for all titles, by release point. On 12 October 2026 the older address download.shtml showed a maintenance page, while download.html and the zip files worked. GovInfo's bulk data list has no US Code entry.

How do I parse the US Code XML in Python?

Parse the title with ElementTree, strip the namespace from tag names, skip anything inside notes, and build one record per section with its status, source credit, body references and release point. Steps 1 to 7 above show each piece, and each was run on release point 119-120.

Is the US Code XML official?

OLRC publishes it, and the User Guide says OLRC uses the same data for the print, HTML and XML versions. OLRC's FAQ says it is the printed version that is recognized under law as evidence, and 1 U.S.C. 204 makes the text of positive-law titles legal evidence. Treat your parsed copy as a derived copy and record where it came from.

Can I get the US Code as JSON?

OLRC's download page lists XML, XHTML, PCC and PDF, and no JSON. You build JSON from the XML, as this guide does, or use a third-party service that does it. Ask that service about its source and update schedule.

How are defined terms marked in the US Code XML?

The schema defines def and term elements. The five titles read here contain none, so definitions are plain sentences with curly quotes. Extract them with a pattern, test it on a section such as 11 U.S.C. 101, and read the misses.

How do I handle repealed sections in the US Code XML?

Keep them with their status. The file marks them as repealed, omitted, transferred or renumbered, and the heading and notes say where the law went. Some cover a range or two addresses in one record. Store the status as a string, because the schema lists 19 values.

For the US statutes, regulations and court rules these laws sit in, 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.3M+ sections, section-level citations, and links to the official source. Plus a free open dataset.
Updated October 12, 202624 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.