Laws API
The Data Services API also covers statutes, regulations, constitutions, and agency guidance — US federal plus all 50 states and DC — as structured, citable documents with verbatim text. Same API, same base URL, same bearer API key as the case-law endpoints; laws add three endpoints:
| Endpoint | Method | Purpose |
|---|---|---|
/api/v1/laws/search |
POST | Full-text search with jurisdiction/type/subtree filters |
/api/v1/laws/get |
POST | Retrieve one document by citation, register citation, path, or id |
/api/v1/laws/collections |
GET | Enumerate available codes/registers (filter values) |
Authentication is the same bearer API key as the rest of the Data Services API.
The document model
Laws are stored as trees: each collection (a code or register, e.g. cfr,
uscode, ca-ccr) is a hierarchy of documents — titles contain chapters
contain parts contain sections. Every document has:
id— UUID, the stable handle for follow-up requestsparent_id— its container (fetch it to zoom out)-
path— its ltree position within the collection (e.g.cfr.t21.cI.scH.p820.spA.s820_1); paths are unique within a collection only -
node_type—LEAF(a content section),PARENT(a container), orTOMBSTONE(a repealed/reserved placeholder) citation(canonical, e.g.21 C.F.R. § 820.1),number,title-
register_citation— Federal Register / state register cite, for regulations that carry one -
html— the verbatim text (containers usually have none); external linkhrefs are stripped (anchor text and in-page#anchors survive) -
url— the Midpage page for this exact version;source_url— the official source state("Federal"or a state name),country,collection_type
Currency: what "current" means
There is no stored is_current flag — currency is derived at request time. A document is current iff:
- it is not marked historical (superseded by the source),
-
its
effective_dateis not in the future — a future-effective amendment is stored as a version but is not yet the law, - no newer version is already in force — until a future amendment's effective date arrives, the older text remains current ("anchor" semantics), and
-
it is not a
TOMBSTONE— repeal/removal markers are lifecycle notes, never the law in force.
Both endpoints filter to current law by default. search takes
current_only: false, get takes include_historical: true to widen; every
returned document carries is_current either way. The derivation reacts to
the calendar automatically: on the amendment's effective day, the old version
stops being current and the new one starts, with no data change required.
Historical access requires an API key with historical entitlement: without
it, the widening flags and historical-id lookups return 403 and versions
comes back empty.
Versions
When a document has version history (statute amended, regulation revised),
get returns a versions array: every version's id,
effective_date, publication_date, source_as_of, plus
is_historical / is_current flags, oldest first, with is_requested
marking the one returned as document. To read an older (or future) version,
get it by its id — an id lookup always returns that exact version
(historical versions require full historical access).
Note on dates: effective_date is only populated when the source states it.
Several state codes publish session-year history rather than effective dates —
those versions have effective_date: null and order by publication /
observation dates instead.
Navigating a code
get on a container (node_type: "PARENT") returns children: its current
children in the code's authored order — navigation rows only (id, number,
citation, title, sort_order; no text), capped at 250 with the true
count in children_total. Typical drill-down:
get { citation: "21 C.F.R. Part 820" } → part container + section list
get { id: <child id from children[]> } → the section text
get { id: <parent_id> } → back up to the container
There is deliberately no children pagination: when a container has more than
250 children, walking the tree is the wrong tool — search directly for the
target section (query + collections + path_prefix) and get the hit.
Lookup methods on get
Provide exactly one of:
| Field | Use for | Notes |
|---|---|---|
citation |
canonical cites | insensitive to punctuation, spacing, and §/sec./section style ("42 USC 1983" matches "42 U.S.C. § 1983"); wording must still match the canonical form — no abbreviation aliasing |
register_citation |
FR / state register cites | regulations & rule documents |
path |
tree positions | pair with collection (paths unique per collection) |
id |
anything already seen | always unambiguous; returns that exact version regardless of currency |
The optional collection field narrows path/citation/register-citation
lookups and accepts either a collection id ("cfr") or a human-readable name
("Code of Federal Regulations"), resolved server-side — an unresolvable
value returns 400 with did-you-mean suggestions. Request bodies are strict:
unknown keys return 400 rather than being silently ignored.
If a citation matches several documents (rare, e.g. the same cite in two
collections), the response has status: "ambiguous" and matches — candidate summaries;
retry with one of the ids. A miss returns 404 with
status: "not_found" and a message saying why — including the case where
only non-current versions matched (retry with include_historical: true to
retrieve those).
Search filters
search narrows with states (full names or USPS abbreviations, "Federal"
for federal law — validated server-side, unrecognized values 400 with
did-you-mean suggestions), collection_types, collections (ids or
human-readable names, resolved the same way), path_prefix (a dot-separated
ltree prefix, e.g. "cfr.t21" for all of 21 CFR — malformed prefixes
400 rather than matching nothing), and inclusive
effective_date_from/to / publication_date_from/to ranges. Pagination is
page / page_size (max 50); results beyond the first 10,000 cannot be
paginated — narrow the query instead. Each hit carries the document's
identifying fields, dates, currency flags, highlighted snippets, and url
(the Midpage page for that exact version). A page can hold fewer than
page_size results; total is an estimate for the whole query.
State surveys
The search response includes state_counts: estimated per-state hit counts
for the whole query, not just the returned page — use them to scope a survey,
then verify each state with a filtered search. A 5-state survey is two steps:
// 1. Where is this regulated, and how much?
POST /api/v1/laws/search
{ "query": "telehealth prescribing controlled substances",
"collection_types": ["regulation"] }
// → state_counts: { "California": 12, "Texas": 8, ... }
// 2. Per state: top provisions, then `get` each by id for verbatim text.
POST /api/v1/laws/search
{ "query": "telehealth prescribing controlled substances",
"collection_types": ["regulation"], "states": ["California"] }
GET /api/v1/laws/collections lists every collection with state and
type, e.g. to confirm which states have a regulation collection before
surveying.
Relationship to the MCP laws tools
The MCP server (latest version) exposes the same data layer as two tools:
searchLaws (search + stateCounts, camelCase inputs, same collections /
states resolution) and analyzeLaw (lookup by citation /
registerCitation / path / id with the same children + versions model —
but analysis-only: a question is required, the document text is analyzed
internally, and the tool returns verified verbatim passages rather than raw
text; use this API's get when you need the html itself).