--- name: govinfo-documents-scraper description: Search the U.S. Government Publishing Office's official repository and return one structured record per document via the Apify Actor arman-bd/govinfo-documents-scraper. Covers congressional bills, enacted public laws, the daily Congressional Record, committee hearings and reports, the Federal Register and federal court opinions, each row carrying the package id, title, congress, dates, issuing bodies and direct public links to the PDF and full text. Use when a task needs legislative tracking, regulatory monitoring, a legal research corpus or a document ingestion pipeline over federal publications. Not for bill status and vote tallies, state or local records, or the document text itself. --- # GovInfo Documents Scraper Apify Actor `arman-bd/govinfo-documents-scraper`. Give it collections, keywords or a Congress number, get one dataset record per document, each with public links to the PDF and the full text. Pass a free GovInfo API key to raise the hourly request budget. ## When to use it - Legislative tracking: new bills and enacted laws in the current Congress, on a schedule. - Keyword monitoring across federal publications, diffing `packageId` between runs. - Assembling a research corpus of court opinions or committee hearings with direct text links for a downstream ingest step. - Regulatory watch over the Federal Register within a date window. - Resolving a package id to its title, dates, issuing bodies and download links. ## When not to use it - Bill status, sponsors, cosponsors, amendments or roll-call votes. That is a different dataset and none of it is returned here. - The document body. You get links to the PDF and the HTML full text, not the text. - State, local or foreign government publications. Federal only. - Ad-hoc single-document lookups when you already hold the package id and one fetch of your own would do. ## Call it ```js import { ApifyClient } from 'apify-client'; const client = new ApifyClient({ token: process.env.APIFY_TOKEN }); const run = await client.actor('arman-bd/govinfo-documents-scraper').call({ collections: ['BILLS'], congress: 119, searchTerms: ['artificial intelligence'], fromDate: '2026-01-01', maxResults: 500, apiKey: process.env.GOVINFO_KEY, includeDetails: false, }); const { items } = await client.dataset(run.defaultDatasetId).listItems(); const { value: summary } = await client .keyValueStore(run.defaultKeyValueStoreId) .getRecord('RUN_SUMMARY'); if (summary.rateLimited) console.warn('budget exhausted, results are partial'); ``` One-shot over HTTP, when you want the rows back in the same request: ```bash curl -X POST "https://api.apify.com/v2/acts/arman-bd~govinfo-documents-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"collections":["BILLS","PLAW"],"congress":119,"searchTerms":["climate"],"fromDate":"2026-01-01","maxResults":200,"apiKey":"YOUR_GOVINFO_KEY"}' ``` The Actor is also exposed through Apify's MCP server as `arman-bd/govinfo-documents-scraper`, so an MCP-capable agent can call it with no extra wiring. ## Input | Field | Type | Required | Default | Notes | |---|---|---|---|---| | `collections` | string[] | no | `[]` | GovInfo collection codes, one sweep each: `BILLS`, `PLAW`, `CREC`, `CHRG`, `CRPT`, `CDOC`, `CPRT`, `FR`, `USCOURTS`, `USCODE`, `STATUTE`, `GAOREPORTS`, `BILLSUM`, `CFR`, `SERIALSET`, `CPD`, `BUDGET`, `ERP`. Uppercased and deduplicated. An unknown code stops the run before any request. | | `congress` | integer | no | `0` | A single Congress number. The 119th ran 2025 to 2026 and each earlier one subtracts two years. `0` means every Congress. Ignored by date-keyed collections. | | `searchTerms` | string[] | no | `[]` | Full-text terms combined with **OR**, so more terms widen the result set. A multi-word entry is searched as an exact phrase. Empty takes everything the other filters allow. | | `fromDate` | string | no | `""` | Lower bound on publication date, strictly `YYYY-MM-DD`. Any other format throws before the first request. No upper bound is available. | | `maxResults` | integer | no | `500` | Global cap across all sweeps, not per collection. `0` means no cap. Maximum 100000. | | `apiKey` | string | no | `DEMO_KEY` | Free GovInfo key, obtained by email from the U.S. government's public API signup. Raises the budget from 10 to 1000 requests an hour. Secret field. | | `includeDetails` | boolean | no | `false` | Adds `summary`, the official long title, and takes `congress`, `billType` and `billNumber` from the package record instead of deriving them. **One extra request per document.** | **At least one of `collections`, `searchTerms` or `congress` must be set**, otherwise the Actor refuses to start: the repository holds over three million documents and an unfiltered sweep is never the intent. **The budget, not the result count, is what you plan around.** Documents page a thousand at a time, so with `includeDetails` off a free key's hourly budget is worth roughly a million documents and `maxResults: 20000` costs twenty requests. Switch `includeDetails` on and every single document costs a request of its own, so the same 20000 documents become 20020 requests, which is twenty hours of a free key. Leave it off unless the long titles are the point of the run, and note that on the shared default key you have ten requests an hour in total. ## Output One record per document saved. | Field | Type | Notes | |---|---|---| | `packageId` | string | GovInfo's stable identifier, `BILLS-119s5239is`. The join key and the diff key. | | `collection` | string \| null | Collection code the document came from. | | `title` | string \| null | The popular short title as the search index reports it. | | `congress` | number \| null | Congress number, derived from `packageId`. `null` for date-keyed collections. | | `billNumber` | string \| null | Document number, `5239`. Derived from `packageId`. | | `billType` | string \| null | Document type letters, `s` for a Senate bill, `hr` for a House bill. | | `docVersion` | string \| null | Version suffix, `is` for introduced-in-Senate, `enr` for enrolled. | | `dateIssued` | string \| null | Publication date, `YYYY-MM-DD`. | | `lastModified` | string \| null | When the package was last revised, ISO 8601. Not the same as `dateIssued`. | | `governmentAuthor` | string[] | Issuing bodies, `["Congress", "Senate"]`. Empty array when none are listed. | | `pdfUrl` | string \| null | Direct public PDF link. `null` when no PDF is published for that package. | | `textUrl` | string \| null | Direct public full-text HTML link. `null` when no text rendition exists. | | `detailsUrl` | string | Human-readable page for the package. Always present. | | `summary` | string \| null | The official long title, only with `includeDetails`. See below. | | `scrapedAt` | string | Run timestamp, ISO 8601. | A real record, taken with `includeDetails: true`: ```json { "packageId": "BILLS-119s5239is", "collection": "BILLS", "title": "American Glove Act of 2026", "congress": 119, "billNumber": "5239", "billType": "s", "docVersion": "is", "dateIssued": "2026-08-04", "lastModified": "2026-08-05T13:37:47Z", "governmentAuthor": ["Congress", "Senate"], "pdfUrl": "https://www.govinfo.gov/content/pkg/BILLS-119s5239is/pdf/BILLS-119s5239is.pdf", "textUrl": "https://www.govinfo.gov/content/pkg/BILLS-119s5239is/html/BILLS-119s5239is.htm", "detailsUrl": "https://www.govinfo.gov/app/details/BILLS-119s5239is", "summary": "To ensure that nitrile glove procurements comply with the requirements of the Make PPE in America Act, and for other purposes.", "scrapedAt": "2026-08-06T12:00:00.000Z" } ``` ## RUN_SUMMARY Written to the run's key-value store under the key `RUN_SUMMARY`. **Read it.** This Actor can stop early for a reason the dataset cannot show you. ```json { "sweepsRequested": 2, "sweepsFailed": 0, "failures": [], "documentsSaved": 500, "requestsUsed": 2, "rateLimited": false, "rateLimit": { "limit": 1000, "remaining": 998, "usingDemoKey": false }, "filters": { "collections": ["BILLS", "PLAW"], "congress": 119, "searchTerms": ["climate"], "fromDate": "2026-01-01", "maxResults": 500, "includeDetails": false }, "finishedAt": "2026-08-06T12:00:06.114Z" } ``` Check `rateLimited` first: `true` means the budget ran out mid-run, everything already saved is in the dataset, and the rest of the sweeps never happened. `rateLimit.remaining` near zero on a completed run is the same warning one step earlier. `rateLimit.usingDemoKey: true` means no key was supplied, which caps the whole run at ten requests. `documentsSaved` equal to `filters.maxResults` means the cap bit, not the end of the results. `failures` carries one entry per sweep with a `collection` and an `error`. ## Behaviour to plan around - **`maxResults` is global and consumed in list order.** Sweeps run in the order you listed the collections, so a cap of 500 over `["CREC", "BILLS"]` can return 500 Congressional Record entries and no bills at all. Run one collection per job when you need guaranteed coverage of each. - **`searchTerms` widens, it does not narrow.** Terms are OR-combined. Adding a term always returns at least as many documents, never fewer. There is no AND, and there is no field-scoped search. - **`congress` silently empties date-keyed collections.** `FR`, `CREC` and `USCOURTS` package ids carry a date rather than a Congress number, so setting `congress` on them yields nothing. Their `congress` field also comes back `null`, which is correct, not a gap. - **`title` and `summary` are two different titles.** The search index returns the popular short title; the package record holds the official long title. With `includeDetails` off, `summary` is `null`. With it on, `summary` is still `null` when the two titles are identical, so `null` never means "the fetch failed". - **Congress, type and number are parsed out of the package id for free.** No extra request is spent on them. When `includeDetails` is on they are replaced by the package record's own values, which can differ in formatting. - **Budget exhaustion stops the run cleanly.** Everything saved so far stays in the dataset and `rateLimited` goes `true`. Re-run with the same `fromDate` after the hour rolls over to pick up the rest. - **A rejected key stops the run immediately** rather than repeating the same rejection once per collection. The Actor throws only if nothing was saved at all. - **One failing sweep never aborts the others.** The failure is recorded and the next collection starts. The Actor throws only when every sweep failed. - **Paging is cursor-based and self-terminating.** The Actor follows the cursor and stops when it stops advancing, so a repeated cursor cannot loop the run. ## Recipes **Weekly legislative tracking.** Bills and enacted laws in the current Congress since the last run. ```json { "collections": ["BILLS", "PLAW"], "congress": 119, "fromDate": "2026-07-01", "maxResults": 2000, "apiKey": "YOUR_FREE_KEY" } ``` Diff on `packageId` against the previous run; set `fromDate` to that run's date next time. **Keyword alerting, cheap enough to run hourly.** Newest first, capped small. ```json { "collections": ["BILLS"], "searchTerms": ["artificial intelligence", "algorithmic", "machine learning"], "fromDate": "2026-01-01", "maxResults": 500, "apiKey": "YOUR_FREE_KEY" } ``` One request per run at this cap. Alert on any `packageId` not seen before. **Legal research corpus.** Court opinions and hearings with direct text links, ready for an ingest step. ```json { "collections": ["USCOURTS", "CHRG"], "searchTerms": ["antitrust"], "fromDate": "2024-01-01", "maxResults": 20000, "apiKey": "YOUR_FREE_KEY" } ``` Fetch `textUrl` per record downstream. Run each collection as its own job so the global cap does not starve the second one. **Long titles for a shortlist.** Only worth it on a small result set, because each document costs a request. ```json { "collections": ["PLAW"], "congress": 119, "maxResults": 100, "includeDetails": true, "apiKey": "YOUR_FREE_KEY" } ``` Expect about 101 requests. Read `summary` for the official long title and fall back to `title` when it is `null`.