eCFR: Search US Federal Regulations, Parts & Amendments
Pricing
from $1.40 / 1,000 regulation row returneds
eCFR: Search US Federal Regulations, Parts & Amendments
Search the Code of Federal Regulations by phrase, title, part or agency: sections with citation, headings, excerpt, effective window and change types. Other modes list a title's parts and sections, amendment dates, titles and agencies. For compliance teams, regulatory lawyers and AI agents.
Pricing
from $1.40 / 1,000 regulation row returneds
Rating
0.0
(0)
Developer
Samat Makatov
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
4 days ago
Last modified
Categories
Share
Search the Code of Federal Regulations by phrase, title, part or agency and get sections as rows: citation, headings, matched excerpt, effective window and change types. Further modes list the parts and sections inside a title, amendment dates per section, and the title and agency dictionaries.
Every row comes from the government's own eCFR JSON API — no key, no proxy, no browser, no login. You get the
citation (21 CFR 820.10), the part and section headings, the matched passage, the dates a version
started and ends, whether it is in force, and the link to the official page; the amendment modes add the
amendment and issue dates of every recorded change. Built for compliance and quality teams, regulatory lawyers,
policy analysts and agents that need a citation with a link rather than a web page. It returns metadata, structure and
excerpts — not the full text of a section; see Limits & FAQ for why.
Need it tailored to your workflow? Missing a field, a filter, another source or a different output format? Open an issue and describe what you need — we read every request and adapt the actor.
Use cases
- Watch the parts your product is regulated under. Run
searchwithlookbackDays: 7andonlyNew: trueon a schedule and the dataset holds only the sections that actually moved this week — with the date the new text took effect. - Prove when a rule last changed, for an audit.
versionson a title and part gives one row per recorded amendment withamendmentDate,issueDateand whether the change was substantive, so a finding gets a dated citation. - Build a table of contents for a RAG index.
structureon a title or part writes every subpart and section with its heading, the range it covers andsizeBytes, so you know how long each node is before you fetch anything. - Scope a topic before you read.
countsturns a phrase into a map of the code — how many matching sections sit in each title, chapter and part — for one request instead of thousands of paid rows. - Give an agent a citation tool. One
run-sync-get-dataset-itemscall answers "which section says this" with the citation, the heading and a link a human can open. - Keep a dated copy of the code for a contract.
asOfDatereads the code as it stood on a past day, and every row carries that day, so a quote in an agreement stays checkable later.
Input
Nothing is required. A run started from the Console uses the prefilled input (query medical device, cfrTitle 21)
and writes 50 matching sections of title 21. Prefill is a Console feature: an API call with {} searches the whole
code without a phrase and writes the first 50 sections in force, so pass query (and cfrTitle) in API calls.
| Field | Type | Default | Allowed values / notes |
|---|---|---|---|
mode | string | search | search, structure, versions, counts, titles, agencies — see Modes |
query | string | — (prefill medical device) | Words to find in the regulation text; used by search and counts. Quote a phrase ("design controls") to keep the words together. Empty in search returns whatever the other filters allow |
order | string | relevance | relevance, newest, oldest, hierarchy — see Sort orders |
asOfDate | string | — | YYYY-MM-DD. Empty = the newest day the source has published (usually 1–3 days behind today). A future day is clamped down, not refused |
includeHistory | boolean | false | false = one row per section, the version in force on asOfDate. true = also the superseded and not-yet-effective versions |
changedAfter | string | — | YYYY-MM-DD. Keep only sections whose text was touched after this day |
changedBefore | string | — | YYYY-MM-DD, the other end of the change window |
lookbackDays | integer | — | 1–3650. Relative change window counted in UTC from the day the run starts. Ignored when changedAfter is set |
cfrTitle | integer | — (prefill 21) | 1–50, see CFR titles. Optional in search and counts, required by structure and versions, ignored by titles and agencies |
cfrChapter | string | — | Roman numeral as the code prints it (I, III, XII). Needs cfrTitle |
cfrPart | string | — | Part number, e.g. 820 in title 21 or 141 in title 40. Needs cfrTitle |
cfrSubpart | string | — | Subpart letter (A, C, UUUUU). Needs cfrPart |
cfrSection | string | — | A single section written as the citation does: 820.30, 141.23. The part is taken from it when cfrPart is empty |
agencySlugs | string[] | — | Agency slugs, combined with OR — see Agency slugs. A full name or another letter case becomes the slug; an unknown slug stops the run with the closest slugs as suggestions |
nodeTypes | string[] | ["part","section"] | structure only: which levels become rows — chapter, subchapter, part, subpart, subjectGroup, section, appendix |
substantiveOnly | boolean | false | versions only: keep only amendments the source marks substantive |
includeRemoved | boolean | false | versions only: also write sections an amendment deleted, with removed: true |
onlyNew | boolean | false | Write only rows no earlier run of the same watch list delivered — see onlyNew |
maxItems | integer | 50 | 1–5000, the found: false row included. The search index serves at most 10 000 hits per query, however deep you page |
fields | string[] | all | Keep only these fields, in this order, e.g. ["citation","sectionName","startsOn","url"]. mode, nodeType and found are always kept (first, unless you place them). Another letter case or an obvious typo of a field name is corrected and reported; a name no mode writes is skipped; a list with no field of the chosen mode stops the run with the fields of that mode |
Reference
Modes
| Mode | Question it answers | Needs | Endpoint behind it |
|---|---|---|---|
search | Which sections of the code talk about this? | nothing | the search index over the codified text |
structure | What is inside this title or part? | cfrTitle | the title's tree for a given day |
versions | When was this last amended, and how often? | cfrTitle | the recorded amendment log |
counts | Where in the code is this topic regulated? | nothing | hit counts per title, chapter and part |
titles | Which titles exist and how current are they? | nothing | the title dictionary |
agencies | Which agency owns which chapter, and what is its slug? | nothing | the agency dictionary |
structure and versions read one title, so they are the two modes that need cfrTitle. titles and agencies need
no input at all — run them once, keep the values, and use them in the filters of the other modes.
Sort orders
| Value | Effect |
|---|---|
relevance | Best match for the phrase first (search); in versions it sorts like newest |
newest | Newest version first in search, newest amendment first in versions — the setting for a change feed |
oldest | Oldest first, for reading a section's history forward |
hierarchy | The order the code prints, by title, part and section — the setting for exporting a part; in versions, by section number |
structure always follows the code's own order, titles its numbering and agencies its alphabet.
CFR titles
The 50 titles are the top level of the code. The ones people ask for most:
| Title | Name | Typical use |
|---|---|---|
| 7 | Agriculture | food and farm programs |
| 12 | Banks and Banking | bank and credit-union rules |
| 14 | Aeronautics and Space | aviation |
| 17 | Commodity and Securities Exchanges | securities and derivatives |
| 21 | Food and Drugs | food, drugs, medical devices |
| 26 | Internal Revenue | tax regulations |
| 29 | Labor | employment and workplace safety |
| 40 | Protection of Environment | air, water, waste, chemicals |
| 45 | Public Welfare | health privacy, research ethics |
| 47 | Telecommunication | spectrum and telecom |
| 48 | Federal Acquisition Regulations System | government contracting |
| 49 | Transportation | rail, road, pipeline, hazmat |
Run { "mode": "titles" } for the full list with names and the day each title is current through — that is also the
honest answer to "how fresh is the text I am quoting".
Agency slugs
agencySlugs filters search by the chapters an agency owns, which is broader than one title and narrower than the
whole code. A few common ones:
| Slug | Agency | Chapters it owns include |
|---|---|---|
environmental-protection-agency | EPA | 40 CFR I, IV, VII |
food-and-drug-administration | FDA | 21 CFR I |
securities-and-exchange-commission | SEC | 17 CFR II |
occupational-safety-and-health-administration | OSHA | 29 CFR XVII |
federal-communications-commission | FCC | 47 CFR I |
internal-revenue-service | IRS | 26 CFR I |
federal-aviation-administration | FAA | 14 CFR I |
agriculture-department | USDA | 7 CFR XVI, XX, XXI |
Run { "mode": "agencies" } for all of them with the chapters each one writes and the sub-agencies below it. A full
name (Environmental Protection Agency) or another letter case is turned into the slug. A slug the dictionary does not
hold stops the run with the closest slugs as suggestions (did you mean "agriculture-department"?) — a near miss is
never swapped for a different agency, and never answered with a silent empty run.
How citations and links are built
citation is assembled from the hierarchy of the node: 21 CFR 820.10 for a section, 40 CFR part 141 for a part,
21 CFR part 820 subpart A for a subpart, 40 CFR part 58, Appendix E to Part 58 for an appendix,
21 CFR chapter I, subchapter Hurl is the official page of the node, e.g.
https://www.ecfr.gov/current/title-21/chapter-I/subchapter-H/part-820/subpart-A/section-820.10. An appendix is linked
through its part and a subject group has no page of its own. Historical rows link the current page and are dated by
startsOn / endsOn.
onlyNew watch lists
With onlyNew: true the actor keeps the key of every row it wrote in the key-value store ecfr-regulations-state of
your Apify account and writes only rows whose key is not there yet.
| Row key | search: citation + the day the version took effect (16 CFR 0.8@2026-09-24); versions: citation + amendment date (two log entries of one amendment count as one); other modes: level + citation |
| One list per | mode, query, cfrTitle, cfrChapter, cfrPart, cfrSubpart, cfrSection, agencySlugs, includeHistory; in versions also substantiveOnly and includeRemoved. order, maxItems, fields, asOfDate and the change window do not start a new list |
| What is remembered | only rows really written — a run stopped by your spending limit or the timeout leaves the rest for the next run |
| Nothing new | no row is written and only the start is charged; the status says how many matches were delivered before |
| Nothing matches at all | the found: false row is written once per list, not on every run |
| Size | the newest 50 000 keys per list |
Examples
Which codified FDA rules mention a topic
{ "mode": "search", "query": "quality management system", "cfrTitle": 21, "order": "relevance", "maxItems": 20 }
One agency's sections across the whole code, in the order the code prints them
{ "mode": "search", "query": "pfas", "agencySlugs": ["environmental-protection-agency"], "order": "hierarchy", "maxItems": 20 }
A weekly watch list of sections that changed (only what is new since the last run)
{ "mode": "search", "query": "safety", "lookbackDays": 7, "order": "newest", "onlyNew": true, "maxItems": 50 }
The table of contents of one part, for a RAG index
{ "mode": "structure", "cfrTitle": 21, "cfrPart": "820", "nodeTypes": ["subpart", "section"], "maxItems": 40 }
Every recorded amendment of a part, newest first
{ "mode": "versions", "cfrTitle": 40, "cfrPart": "141", "order": "newest", "substantiveOnly": true, "maxItems": 30 }
Where a phrase is regulated, before exporting any sections
{ "mode": "counts", "query": "artificial intelligence", "maxItems": 30 }
How a single section used to read
{ "mode": "search", "cfrTitle": 21, "cfrSection": "820.30", "includeHistory": true, "order": "oldest", "maxItems": 20 }
Output
One row per section version, tree node, amendment or dictionary entry. The first row of a real run with the default
input (mode: "search", query: "medical device", cfrTitle: 21, 50 rows in six seconds):
{"mode": "search","nodeType": "section","citation": "21 CFR 892.2020","titleNumber": 21,"titleName": "Food and Drugs","chapter": "I","subchapter": "H","part": "892","subpart": "B","subjectGroup": null,"section": "892.2020","appendix": null,"identifier": "892.2020","heading": "Medical image communications device.","hierarchyHeading": "§ 892.2020","chapterName": "Food and Drug Administration, Department of Health and Human Services","subchapterName": "Medical Devices","partName": "Radiology Devices","subpartName": "Diagnostic Devices","sectionName": "Medical image communications device.","excerpt": "Identification. A medical image communications device provides electronic transfer of medical image… image data between medical devices. It may include a physical communications medium, modems… image review software functionality for medical image processing and manipulation, such","score": 51.984,"startsOn": "2021-04-19","endsOn": null,"inForce": true,"changeTypes": ["effective"],"structureIndex": 82397,"reserved": false,"removed": false,"asOfDate": "2026-09-24","found": true,"url": "https://www.ecfr.gov/current/title-21/chapter-I/subchapter-H/part-892/subpart-B/section-892.2020","fetchedAt": "2026-09-27T20:58:29.940Z"}
Filled in every mode: mode, nodeType, citation, titleNumber, url, asOfDate, fetchedAt, found
(citation, titleNumber and url are null on an agency row that owns no chapter of the code; citation is null on
the single found: false row a run writes when nothing matched). mode, nodeType and found stay in every row even
when fields narrows it, so a narrowed dataset still tells which mode wrote a row and which row is the found: false
one. The found: false row is a dataset row like any other: it counts towards maxItems and is billed as a row.
| Field | Type | Meaning |
|---|---|---|
mode | string | The mode that produced the row |
nodeType | string | Level of the code: section, appendix, part, subpart, chapter, subchapter, subtitle, subjectGroup, title, agency, or notFound |
citation | string | The citation as it is written in practice, e.g. 21 CFR 820.10 |
titleNumber / titleName | number / string | CFR title of the row |
chapter, subchapter, part, subpart, subjectGroup, section, appendix | string | The hierarchy, level by level; null where the node has no such level |
identifier | string | The node's own identifier (820.10, A, I) |
heading / hierarchyHeading | string | The node's heading and its printed label (§ 820.10) |
chapterName, subchapterName, partName, subpartName, sectionName | string | Headings of the levels above and of the node itself |
excerpt | string | search: the passage the index matched, with the markup removed |
score | number | search: relevance of the match |
startsOn / endsOn | string | search: the day this version took effect and the day it stopped (null = still current) |
inForce | boolean | search: whether the version was in force on asOfDate (computed here, not read off endsOn) |
changeTypes | string[] | search: what kind of change produced this version, e.g. effective, initial |
structureIndex | number | The source's position index of the node |
reserved / removed | boolean | The node is reserved, or was removed by an amendment |
amendmentDate / issueDate / versionDate | string | versions: when the amendment was made, the issue it was published under, and the version's own date |
name / substantive | string / boolean | versions: the printed section name, and whether the change was substantive |
sizeBytes | number | structure: size of that node's text in the source's own XML — how long a part is before you read it |
descendantRange | string | structure: the sections a node covers, e.g. 141.1 – 141.905 |
volumes / receivedOn / childCount | array / string / number | structure: printed volumes, when the source received the text (UTC), and how many children the node has |
latestAmendedOn, latestIssueDate, upToDateAsOf | string | titles: last amendment, its issue date, and the day the title is current through |
slug, shortName, displayName, parentSlug, childSlugs | string / string[] | agencies: the slug the filter accepts, the names, the parent department and the sub-agencies |
cfrReferences / cfrTitles | array | agencies: the {title, chapter} pairs the agency writes, and the title numbers |
hitCount / maxScore / level | number / number / string | counts: matching sections under the node, best score, and the source's own level name |
asOfDate | string | The day the code was read as of — the same for every row of a run |
found | boolean | false on the one row a run writes when nothing matched |
url | string | Official page of the node |
fetchedAt | string | Fetch time, ISO 8601 UTC |
Dataset views: Sections (search), Title structure, Amendments, Dictionaries & hit counts. A SUMMARY
record in the run's key-value store repeats the filters, the resolved asOfDate, whether it was clamped, the total
number of matches, the request count and every warning.
Use it from code / agents
curl -X POST "https://api.apify.com/v2/acts/yadroo~ecfr-regulations/run-sync-get-dataset-items?token=$APIFY_TOKEN" \-H "Content-Type: application/json" \-d '{"mode":"search","query":"quality management system","cfrTitle":21,"maxItems":20}'
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: process.env.APIFY_TOKEN });const run = await client.actor('yadroo/ecfr-regulations').call({ mode: 'versions', cfrTitle: 40, cfrPart: '141', maxItems: 30 });const { items } = await client.dataset(run.defaultDatasetId).listItems();
from apify_client import ApifyClientclient = ApifyClient(os.environ["APIFY_TOKEN"])run = client.actor("yadroo/ecfr-regulations").call(run_input={"mode": "counts", "query": "artificial intelligence", "maxItems": 30})items = client.dataset(run["defaultDatasetId"]).list_items().items
MCP: add https://mcp.apify.com to Claude / Cursor / any MCP client and call the yadroo/ecfr-regulations tool with the same JSON input.
For agents: use counts first, search second. A phrase like "artificial intelligence" matches sections in a dozen
titles. One counts run costs a few dozen rows and tells the agent which titles and parts to look in; the follow-up
search then carries cfrTitle and cfrPart and pays for the sections that matter instead of paging blindly. Pass
fields to trim the row to what the model actually reads — ["citation","sectionName","excerpt","url"] is usually enough.
Pricing
Pay per event: $0.001 per run start + $0.002 per dataset row. The start event is charged on every run. A run that
matches nothing writes one found: false row saying so, and that row is billed as a row ($0.002); an onlyNew run
whose matches were all delivered before writes no row and costs only the start. Bronze −10 %, Silver −20 %, Gold and
above −30 % on the row price; the start event is the same on every plan; platform usage is included.
Your maximum cost per run is respected: the actor reads how many rows it still pays for, writes exactly that many and ends with "Stopped at your spending limit: N rows delivered" — never a row it cannot charge, never a silent cut.
| Run | Rows | Cost at list price |
|---|---|---|
| The default input (50 sections for a phrase in one title) | 50 | $0.001 + 50 × $0.002 = $0.101 |
A part's table of contents (structure, one part) | 12 | $0.001 + 12 × $0.002 = $0.025 |
A topic map before searching (counts) | 30 | $0.001 + 30 × $0.002 = $0.061 |
A weekly watch that finds two changed sections (onlyNew) | 2 | $0.001 + 2 × $0.002 = $0.005 |
There is no separate compute charge. The runs behind this README finished in a few seconds each — no browser; a search
is two requests, a part's structure three. The actor asks for 512 MB because its heaviest job parses a whole title's tree:
title 40 is 9.4 MB of JSON and 30 311 nodes, and walking every one of them needs well under half that memory. Passing
cfrPart or cfrChapter cuts the subtree before it is walked, which is why exporting one part is the cheap case.
Limits & FAQ
- No full section text, and that is the source's rule, not a shortcut.
robots.txtof the eCFR disallows the two endpoints that serve rendered and raw section text for every crawler, so this actor does not read them. You get the citation, the full hierarchy, the headings, the matched excerpt, the version window, the amendment dates and the link to the official page — open the link (or quote the citation) when you need the wording itself. - The code is one to three days behind today. The source publishes the code up to a processed day, which every row
reports as
asOfDate;mode: "titles"shows it per title asupToDateAsOf. A date the source has not reached is clamped down to the newest available day, logged as a warning and flagged inSUMMARY.asOfDateClamped— it is not an error. - 10 000 hits per query. The search index refuses to page past that offset. A run that reaches the ceiling says so
in the status message and in
SUMMARY.resultWindowHit; narrow it withcfrTitle,cfrPart,agencySlugsor a change window. - Only the JSON API is read. The site's HTML pages answer a redirect to a different domain when they are requested
from a data-centre address, so this actor never fetches a page — it talks to
www.ecfr.gov/apionly. Links in the rows are for you to open in a browser. - A missing node gives a row, not silence. A part or section that does not exist on the chosen day, a reserved
title, or a filter combination with no data yields exactly one row with
found: falseand a heading that says what was missing. A section that a rewrite removed (21 CFR 820.30 after the February 2026 rewrite of part 820) has no current page, butmode: "versions"still has its amendment history. - Superseded versions need
includeHistory. By default a section appears once, as it stood onasOfDate. WithincludeHistory: trueyou get one row per version, each with its ownstartsOn,endsOnandinForce— which is how you diff a section over time. inForceis computed, not copied. A dated hit can still carry a non-nullendsOnwhen a later version already exists, so "in force" is worked out fromstartsOn,endsOnandasOfDaterather than read off the source.- No published rate limit, so the actor stays polite. Requests are sequential with a short pause, 429 and 5xx
answers (the amendment log occasionally returns 502) are retried with backoff, and a run makes at most 40 requests.
A search writes each page as it arrives, so if the cap (or a source failure after the first rows) ever stops it, the
rows already read are kept and the status says why and what to narrow. An
onlyNewsearch reads 1000 hits per page, so even a watch that has to page past thousands of delivered rows needs at most ten pages. - Spending limit and timeout. The run stops at the last row your maximum cost per run pays for ("Stopped at your spending limit: N rows delivered") and stops starting requests shortly before its timeout ("Stopped before the run timeout: N rows saved") — both end as a succeeded run with the rows written so far.
- Empty fields are the source's.
subpart,subchapterandsubjectGroupexist only where the code uses them;excerptandscoreonly insearch;sizeBytesanddescendantRangeonly instructure; one agency in the dictionary owns no chapter of the code at all. onlyNewkeeps one list per filter combination. Changing the query, the title or the part starts a new list, so the first run of a changed watch writes everything it finds — details in onlyNew watch lists.- Licensing. The Code of Federal Regulations is a work of the US government and in the public domain; the actor adds the structure, the citations and the links.
Made by Yadroo. Sibling actors: federal-register-documents (the daily rules and notices behind these amendments), sec-edgar-filings, openfda-records, cisa-kev-vulnerabilities, bls-release-calendar.