ecfr
The eCFR API for Hot: the Code of Federal Regulations, with no key. Covers the titles list, structure, ancestry, version history, search, and full text by part or section.
Install
Add it to your project's deps in hot.hot:
hot.project.my-app.deps {
"hot.dev/ecfr": "0.1.0"
}
Requires Hot 3.0.3 or later for ::hot::gzip.
Auth and settings
eCFR takes no credential. The one setting comes from per-call opts first, then the Context Variable, then the default (::ecfr::api/resolve). Every API function takes a trailing opts Map.
| Context Variable | opts key | Default | Description |
|---|---|---|---|
ecfr.base-url | base-url | https://www.ecfr.gov/api | eCFR API root |
Quick start
// One section as text, for citing
s ::ecfr/get-section(31, "1010.230")
s.citation // "31 CFR 1010.230"
s.heading // "§ 1010.230 Beneficial ownership requirements for legal entity customers."
s.text // paragraphs split by a blank line
// A whole part, with every section in it
part ::ecfr/get-part(31, "1010")
map(part.sections, (x) { x.citation })
// All of 31 CFR Chapter X
chapter ::ecfr/full(31, {chapter: "X"})
// The text as it stood on a date
::ecfr/get-section(31, "1010.230", {date: "2024-01-01"})
// Where a section sits, and when it changed
::ecfr/ancestry(31, {section: "1010.230"})
::ecfr/versions(31, {part: "1010", section: "1010.100", issue-date-gte: "2024-01-01"})
// Search the text in force
date ::ecfr/latest-date(31)
page ::ecfr/search("beneficial ownership", {title: 31, date: date, per-page: 10})
Functions by area
| Namespace | Functions |
|---|---|
::ecfr | Titles: titles, get-title, latest-date. Text: get-section, get-part, full, full-xml, parse-full. Hierarchy: structure, ancestry, versions. Search: search, search-count. Paging: next-page, collect. Helpers: hierarchy-params, versions-params, search-params, dated-path, find-title, flatten-inline, node-text, blocks, citation-of |
::ecfr::api | resolve, request, get-json, get-json-or-null, get-xml-or-null, body-text, query-string, path-segment, http-error, html-error, is-html, is-not-found, is-past-date, null-on-404; type HttpError |
Pick a place in a title with the hierarchy keys: subtitle, chapter, subchapter, part, subpart, section, appendix.
get-section gives {type, identifier, citation, heading, text, notes, source-note}. notes holds eCFR's editorial notes on pending or delayed amendments, which aren't part of the rule text but do affect what it will say. source-note is the Federal Register history. get-part and full give {type, identifier, citation, heading, authority, source, sections}, with part and subpart on each section.
Pagination
search pages by number: per-page and page. next-page(page) reads meta, and collect(fetch, opts, limit) follows it.
hits ::ecfr/collect((o) { ::ecfr/search("beneficial ownership", o) }, {title: 31, date: date}, 250)
Errors
Non-2xx responses come back as an Err carrying ::ecfr::api/HttpError:
source:"ecfr"status,url,headers,bodyretry-after: seconds, parsed fromRetry-Afteron 429smessage: a one-line summary built from eCFR's own error text
Lookups that find nothing return null rather than a 404 Err: get-section, get-part, full, full-xml, structure and ancestry. get-title and latest-date return null for an unknown title. A blank section or part is an Err, because the query would otherwise ask for the whole title.
Two cases to watch for:
- A date past the title's current date is an Err, not null. The section may well exist. Today is usually past that date, so use
latest-dateor leavedateout. - An HTML page is an Err. The site redirects clients it flags as automated to a CAPTCHA page, and
::hot::httpfollows the redirect, so the page arrives as a 200. ThatHttpErrorhasstatus200.
Nothing retries inside a run. Use a handler or schedule retry policy for 429s and 5xx.
Notes and limits
- eCFR is not the official edition. NARA describes it as an unofficial editorial compilation of CFR material and Federal Register amendments, one that doesn't provide legal notice to the public or judicial notice to the courts. It is generally current within two business days. Keep that in mind when citing text from it.
- Ask for a part or section, not a title.
http.max-response-bytescaps a response at 16 MiB by default,compression.max-output-bytescaps decompressed output at 64 MiB, andfrom-xmlrefuses input over 64 MiB. Title 40 is about 153 MiB of XML, so it fails.sizeonstructureandancestrynodes gives a node's XML size in bytes, so check it first when unsure. For scale, Title 31 is about 10 MB, Chapter X about 750 KB and Part 1010 about 370 KB. - Compression is required for full text. The
fullendpoint answers 406 unless the request accepts compression, so every request sendsaccept-encoding: gzip. A body is only decoded whencontent-encodingsays gzip. - Dated calls may cost a second request. Without
opts.date,structure,ancestry,full-xml,full,get-partandget-sectionfirst readup_to_date_as_offrom the titles list. Passdatein a loop. - Early dates read as missing. A date before eCFR's history begins returns the same 404 as a missing section, so it gives null. 2000-01-01 does this for Title 31.
- Search covers every version by default. Without
date,searchalso returns superseded text. Passdatefor the text in force. - Parsed text is plain text. Superscripts become
^n, and a table becomes a line per row with|between cells. Images are dropped, so a formula or chart set as a figure is missing fromtext. Usefull-xmlwhen you need the source. authorityandsourcecome from the top node. They are set for a part but not for a chapter.- Scripted access goes through the API. The site's Request Access notice says programmatic access to eCFR.gov and FederalRegister.gov is limited to the developer APIs, because of aggressive automated scraping. Fetching the website's HTML pages from a script gets the CAPTCHA redirect.
- Rate limits. This package states no figure because none could be confirmed. Keep requests modest, and stop and back off on a 429 or a CAPTCHA redirect.
License
Apache-2.0 - see LICENSE