# GovInfo Scraper: Bills, Laws & Congressional Records (`arman-bd/govinfo-documents-scraper`) Actor

Scrape GovInfo for bills, public laws, congressional reports, hearings and the Federal Register archive. Package metadata plus direct PDF and text links.

- **URL**: https://apify.com/arman-bd/govinfo-documents-scraper.md
- **Developed by:** [Arman Hossain](https://apify.com/arman-bd) (community)
- **Categories:** Business, Developer tools, MCP servers
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.56 / 1,000 document scrapeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## GovInfo Scraper: Bills, Laws & Congressional Records

![GovInfo Scraper: Bills, laws, hearings and the Federal Register, with direct PDF and full-text links, straight from the GPO's own API](https://api.apify.com/v2/key-value-stores/ZQOcNAOHrIgTacAmy/records/govinfo-documents-scraper.jpg)

**GovInfo Scraper** searches the U.S. Government Publishing Office's official repository and returns one structured row per document, congressional bills, enacted public laws, the daily Congressional Record, committee hearings and reports, the Federal Register, and federal court opinions.

Every row carries the package metadata plus **direct, public links to the PDF and the full text**, so you can pipe results straight into a document ingestion pipeline. Plain HTTP: **no browser, no proxies, no login.**

**Agent skill: [SKILL.md](https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/govinfo-documents-scraper.md)**

```
https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/govinfo-documents-scraper.md
```

### What you get

| Output field | Meaning |
|---|---|
| `packageId` | GovInfo's stable identifier, e.g. `BILLS-119s5239is` |
| `collection` | Collection code the document came from (`BILLS`, `PLAW`, `CREC`, …) |
| `title` | Document title, the popular short title for bills |
| `congress` | Congress number, e.g. `119` |
| `billNumber`, `billType`, `docVersion` | `5239`, `s` (senate bill), `is` (introduced in senate) |
| `dateIssued`, `lastModified` | Publication date, and when GovInfo last revised the package |
| `governmentAuthor` | Issuing bodies, e.g. `["Congress", "Senate"]` |
| `pdfUrl` | Direct PDF on www.govinfo.gov |
| `textUrl` | Direct full-text HTML on www.govinfo.gov |
| `detailsUrl` | Human-readable GovInfo page for the package |
| `summary` | The official long title, when `includeDetails` is on |
| `scrapedAt` | Run timestamp |

A `RUN_SUMMARY` record in the key-value store holds per-collection counts, remaining API budget, the filters used, and any collection that failed.

### Which source this uses, and why

GovInfo exposes more than one search surface. Two of them are undocumented routes
behind the website's own navigation: one returns only facet counts with no document
records, the other is internal, undocumented and rejects anything it does not expect.
This Actor uses neither.

It reads the documented public API published by the Government Publishing Office,
which returns real document records with package IDs, titles, dates and download
links. That route needs an API key, which is free and arrives by email in seconds,
and it carries a far higher request budget than the undocumented ones. `DEMO_KEY`
works out of the box so the Run button does something useful immediately.

### Common use cases

**Legislative tracking for policy teams.** Watch bills and enacted laws in the current Congress and diff on `packageId` between runs.

```json
{
 "collections": ["BILLS", "PLAW"],
 "congress": 119,
 "fromDate": "2026-07-01",
 "maxResults": 2000,
 "apiKey": "YOUR_FREE_KEY"
}
```

**Build a bill-monitoring alert product.** Keyword-scoped, newest first, cheap enough to run hourly.

```json
{
 "collections": ["BILLS"],
 "searchTerms": ["artificial intelligence", "algorithmic", "machine learning"],
 "fromDate": "2026-01-01",
 "maxResults": 500,
 "apiKey": "YOUR_FREE_KEY"
}
```

**Legal research corpora.** Federal court opinions and committee hearings with direct text links, ready for a downstream ingest step.

```json
{
 "collections": ["USCOURTS", "CHRG"],
 "searchTerms": ["antitrust"],
 "fromDate": "2024-01-01",
 "maxResults": 20000,
 "apiKey": "YOUR_FREE_KEY"
}
```

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `collections` | array | `[]` | GovInfo collection codes. Each is a separate sweep. |
| `congress` | integer | `0` | Single Congress number. `119` = 2025-2026. `0` = all. |
| `searchTerms` | array | `[]` | Full-text terms, OR-combined. Multi-word entries become exact phrases. |
| `fromDate` | string | `""` | `YYYY-MM-DD` lower bound on publication date. |
| `maxResults` | integer | `500` | Global cap across collections. `0` = no cap. |
| `apiKey` | string | `DEMO_KEY` | Free key from api.data.gov/signup. Stored as a secret. |
| `includeDetails` | boolean | `false` | Adds `summary` (official long title). **+1 request per document.** |

**You need at least one of `collections`, `searchTerms` or `congress`.** GovInfo holds over three million documents; an entirely unfiltered search is never what anyone wants, so the Actor refuses it with a clear message rather than starting one.

**Which combinations make sense.** `collections` + `fromDate` is the workhorse for scheduled monitoring, results come back newest first, so `maxResults` gives you the most recent N. `searchTerms` narrows within that, and terms are OR'd, so more terms means more results, not fewer. `congress` only applies to congressionally-numbered collections (`BILLS`, `PLAW`, `CHRG`, `CRPT`, `CDOC`); date-keyed ones like `FR` and `USCOURTS` will simply return nothing if you set it. `includeDetails` is the one expensive option, see the note under Limits.

### Output example

```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"
}
```

Note the split between `title` and `summary`: GovInfo's search index returns the *popular* short title (`American Glove Act of 2026`), while the package record holds the *official* long title. With `includeDetails: false` you get the short title and `summary` is `null`.

`RUN_SUMMARY` in the key-value store:

```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"
}
```

### Limits and behaviour

- **`DEMO_KEY` is capped at 10 requests per hour.** That is enough to see the Actor work and not much else. A free key from [api.data.gov/signup](https://api.data.gov/signup) raises it to **1000 requests/hour**, arrives by email immediately, and asks for nothing but an address. Get one before your first real run.
- **Documents page at 1000 per request.** So on a free key, one hour of budget is roughly **a million documents**, as long as `includeDetails` is off.
- **`includeDetails` costs one request per document.** 500 documents with details on is 500 requests, half your hourly budget, for a field you may not need. It is off by default for exactly that reason.
- **Congress, bill type and bill number come free.** GovInfo encodes them in the package ID (`BILLS-119s5239is` → 119th Congress, senate bill 5239, introduced-in-senate version), so the Actor parses them out rather than spending a request. Date-keyed collections such as `CREC-2026-08-05` have no congress in the ID and report `null`.
- **The budget is read from every response.** rate-limit headers and rate-limit headers are logged after each collection and written into `RUN_SUMMARY.rateLimit`. GovInfo does not send a reset timestamp; the window is hourly.
- **Exhaustion stops the run cleanly.** On a 429 the Actor stops, keeps everything already saved, sets `RUN_SUMMARY.rateLimited: true`, and tells you where to get a bigger key.
- **A bad key fails fast.** A 401/403 stops the run immediately rather than repeating the same rejection once per collection.
- **One failing collection never aborts the run.** Errors land in `RUN_SUMMARY.failures` and the next collection starts. The Actor only throws if *every* sweep failed.
- **Transient errors are retried.** 5xx gets three attempts with linear backoff.
- **Pagination is cursor-based.** GovInfo uses an `offsetMark` cursor rather than page numbers, which is what makes deep result sets reachable at all. The Actor follows it and stops when the cursor stops advancing.
- **Public-record data only.** Everything here is published by the U.S. Government Publishing Office for reuse; no authentication protects it and no commercial licence is required.

### Finding a collection code

The code is the uppercase token in every GovInfo URL. Open a document on govinfo.gov and read the package ID:

| Package ID you see | Collection code | What it is |
|---|---|---|
| `BILLS-119s5239is` | `BILLS` | Congressional bills |
| `PLAW-119publ102` | `PLAW` | Enacted public and private laws |
| `CREC-2026-08-05` | `CREC` | Daily Congressional Record |
| `CHRG-119hhrg58123` | `CHRG` | Committee hearings |
| `USCOURTS-ca9-20-12345` | `USCOURTS` | Federal court opinions |

The complete list of 42 collections is at `https://api.govinfo.gov/collections?api_key=DEMO_KEY`.

### API example

```bash
curl -X POST "https://api.apify.com/v2/acts/arman-bd~govinfo-documents-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
 "collections": ["BILLS", "PLAW"],
 "congress": 119,
 "searchTerms": ["climate", "clean energy"],
 "fromDate": "2026-01-01",
 "maxResults": 200,
 "apiKey": "YOUR_GOVINFO_KEY"
 }'
```

### JavaScript example

```js
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('arman-bd/govinfo-documents-scraper').call({
 collections: ['BILLS'],
 congress: 119,
 searchTerms: ['artificial intelligence'],
 fromDate: '2026-01-01',
 apiKey: 'YOUR_GOVINFO_KEY',
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const doc of items) {
 console.log(`${doc.dateIssued} ${doc.packageId.padEnd(22)} ${doc.title}`);
 console.log(` text: ${doc.textUrl}`);
}
```

### FAQ

**Do I need a proxy?** No. Proxy configuration is not required to run this Actor.

**Do I need an account on GovInfo?** No. You supply no credentials.

**Can I run it without a key at all?** Yes, on `DEMO_KEY`, which is what the Actor uses by default. It is shared across every anonymous caller and capped at 10 requests/hour, so treat it as a trial, not a plan.

**Why doesn't it use the `wssearch` endpoint?** Because it is not a document search. `wssearch/rb/{collection}` returns the browse navigation tree, Congress numbers and counts, no documents, and answers 405 to POST. `wssearch/search` is POST-only, undocumented, and rejects requests it does not recognise with an unhelpful 400. The documented `api.govinfo.gov/search` returns real records and is what a production Actor should depend on.

**Why is `summary` null?** Because `includeDetails` is off. GovInfo's search index does not return an abstract; the official long title lives on the package record, which costs an extra request per document to fetch.

**Why did `congress` come back null?** The document is from a date-keyed collection (`FR`, `CREC`, `USCOURTS`) whose package IDs carry a date instead of a Congress number.

**What happens if GovInfo is unavailable?** 5xx responses are retried three times, then that collection is recorded in `RUN_SUMMARY.failures` and the run continues with the next one.

**Can I schedule it?** Yes, it is designed for it. Set `fromDate` to the previous run's date and diff on `packageId`.

**Can I integrate it with something else?** Yes, Apify API, client libraries, webhooks, scheduled runs, dataset exports (JSON/CSV/Excel) or MCP. Output is structured JSON.

# Actor input Schema

## `collections` (type: `array`):

GovInfo collection codes. Each one is searched separately so a bad code cannot hide the others' results. BILLS is congressional bills, PLAW is enacted public laws, CREC is the daily Congressional Record, CHRG is committee hearings, USCOURTS is federal court opinions.

## `congress` (type: `integer`):

Restrict to a single Congress. The 119th ran 2025-2026, the 118th 2023-2024, and each one back subtracts two years. Leave at 0 for every Congress. Ignored by date-keyed collections such as FR and USCOURTS.

## `searchTerms` (type: `array`):

Full-text terms, combined with OR. a document matching any one of them is kept. Multi-word entries are searched as exact phrases, so "clean energy" matches the phrase rather than the two words apart. Leave empty to take everything in the collection.

## `fromDate` (type: `string`):

Lower bound on the publication date, as YYYY-MM-DD. Results always come back newest first, so this is what makes a scheduled run pick up only what is new.

## `maxResults` (type: `integer`):

Stop after this many documents across all collections, taken in the order you listed them. Documents page at 1000 per request, so 500 costs a single API request. Set 0 for no cap.

## `apiKey` (type: `string`):

Free key from api.data.gov/signup. it arrives by email in seconds and raises the budget from 10 to 1000 requests per hour. Leave this blank to fall back to the shared DEMO_KEY, which works but is capped at 10 requests/hour across every anonymous caller.

## `includeDetails` (type: `boolean`):

Adds the official long title as 'summary' and takes congress, billType and billNumber from GovInfo rather than deriving them from the package ID. This costs one extra API request PER DOCUMENT. 500 documents means 500 requests, which is half a free key's hourly budget. Leave off unless you need the long titles.

## Actor input object example

```json
{
  "collections": [
    "BILLS",
    "PLAW"
  ],
  "congress": 119,
  "searchTerms": [
    "climate",
    "clean energy"
  ],
  "fromDate": "2026-01-01",
  "maxResults": 500,
  "includeDetails": false
}
```

# Actor output Schema

## `items` (type: `string`):

Every record the run produced.

## `runsummary` (type: `string`):

The RUN_SUMMARY record from the run's key-value store.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "collections": [
        "BILLS"
    ],
    "fromDate": "2026-01-01"
};

// Run the Actor and wait for it to finish
const run = await client.actor("arman-bd/govinfo-documents-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "collections": ["BILLS"],
    "fromDate": "2026-01-01",
}

# Run the Actor and wait for it to finish
run = client.actor("arman-bd/govinfo-documents-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "collections": [
    "BILLS"
  ],
  "fromDate": "2026-01-01"
}' |
apify call arman-bd/govinfo-documents-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,arman-bd/govinfo-documents-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/I5KQS5ZSuI0ammoAA/builds/I07EQhvdiG3amwEab/openapi.json
