# E-Rate Form 470 RFP Scraper (USAC School & Library Bids) (`jserle/erate-form-470-rfp-feed`) Actor

E-Rate Form 470 search and RFP feed from USAC open data: one record per posting with the school, district or library, every service request, allowable contract date, contacts, consultant and links to attached RFP documents. Filter by funding year, state, category or service type. $0.004/record.

- **URL**: https://apify.com/jserle/erate-form-470-rfp-feed.md
- **Developed by:** [JS Tech Solutions](https://apify.com/jserle) (community)
- **Categories:** Lead generation, Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$4.00 / 1,000 form 470 postings

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## E-Rate Form 470 RFP Scraper (USAC School & Library Bids)

Pulls every E-Rate FCC Form 470 posting from USAC open data as **one clean JSON record per Form 470**: the school, district, library or consortium buying, every service request it lists, the allowable contract date, the applicant, technical and consultant contacts, and a direct link to every attached RFP document. Filter by funding year, state, service category, service type, applicant name, posting date, or RFP-only.

Every US school district, school, library and consortium that wants an E-Rate discount on connectivity has to post a Form 470 first, then wait 28 days before it can sign anything. That posting is the buying signal. USAC publishes all of it, but at a grain nobody can use: the source emits one row per service request per attached RFP document, so a single Form 470 with four service requests and three attachments arrives as twelve near-identical rows. 1.6 million rows, one usable feed hiding inside. This Actor gives you back the form.

### Who uses this data

- **ISPs, fiber and broadband providers** chasing Category 1 contracts. Filter to your states, `Category 1`, and `rfpOnly`, and you get the postings with a real written spec you can bid.
- **Network, Wi-Fi, switching and AV vendors and their resellers** working Category 2, where `manufacturer` and `function` tell you whether the district already specced your gear or a competitor's.
- **E-Rate consultants and RFP-response shops** that need the document itself, not a notification. Every attachment is a direct URL, and the consultant of record is on every posting.
- **EdTech, managed-service and gov-contracting BD teams** building territory lists: applicant type, entity name, state and contacts on every posting.
- **Market analysts** tracking E-Rate demand by year, category, service type and geography.

### What you get

One dataset item per Form 470 posting, newest certification first. Real item, descriptions trimmed:

```json
{
  "source": "https://opendata.usac.org/resource/jt8s-3q52.json",
  "attribution": "Universal Service Administrative Company",
  "sourceRecordUrl": "https://opendata.usac.org/resource/jt8s-3q52.json?application_number=270000411",
  "fetchedAt": "2026-08-23T11:39:14.800Z",
  "applicationNumber": "270000411",
  "formNickname": "FO27-47001-C1 Internet Access",
  "fundingYear": 2027,
  "status": "Certified",
  "certifiedDate": "2026-08-20T15:31:24",
  "allowableContractDate": "2026-09-17",
  "formPdfUrl": "https://publicdata.usac.org/EPC/Prd/Form470/270000411/17013307/22418036-USAC_FCC_FORM_470_APPLICATION_270000411_CERTIFIED.pdf",
  "billedEntityNumber": "17013307",
  "billedEntityName": "Futuro Academy Incorporated",
  "applicantType": "School",
  "billedEntityState": "NV",
  "billedEntityAddress": { "line1": "920 N LAMB BLVD", "city": "LAS VEGAS", "state": "NV", "zip": "89110" },
  "contact": { "name": "Ignacio Prado", "email": "director@futuroacademy.org", "phone": "702-509-1612" },
  "technicalContact": { "name": "Ignacio Prado", "title": "Executive Director", "email": "director@futuroacademy.org" },
  "consultants": [
    { "name": "Funds for Learning", "registrationNumber": "16024808", "email": "jharrington@fundsforlearning.com" }
  ],
  "serviceCategories": ["Category 1"],
  "serviceTypes": ["Data Transmission and/or Internet Access"],
  "serviceRequestCount": 1,
  "serviceRequests": [
    {
      "serviceRequestId": 1,
      "serviceCategory": "Category 1",
      "serviceType": "Data Transmission and/or Internet Access",
      "function": "Internet Access and Data Transmission Service",
      "quantity": 1, "unit": "Each",
      "minimumCapacity": "1024.00 Mbps", "maximumCapacity": "5120.00 Mbps",
      "installationAndInitialConfiguration": true, "manufacturer": null,
      "rfpAttachmentCount": 1,
      "rfpDocuments": [
        { "url": "https://publicdata.usac.org/EPC/Prd/Form470/270000411/17013307/22417667-FO27_RFP_Internet%20Access.pdf",
          "uploadDate": "2026-08-20T15:08:25" }
      ]
    }
  ],
  "hasRfpDocuments": true,
  "rfpDocumentCount": 1,
  "rfpDocuments": [ { "url": "https://publicdata.usac.org/EPC/.../FO27_RFP_Internet%20Access.pdf", "uploadDate": "2026-08-20T15:08:25" } ],
  "categoryOneDescription": "Futuro Academy Incorporated is seeking bids for the following services: Internet Access wi ...",
  "stateOrLocalRestrictions": true,
  "stateOrLocalRestrictionsDescription": "Bid responses must be emailed to Ignacio Prado at director@futuroacademy.org ...",
  "sourceRowCount": 1
}
```

Also present: `sourceDatasetPage`, `formVersion`, `lastModifiedDate`, `billedEntityFccRegistrationNumber`, `billedEntityEmail`, `billedEntityPhone`, `websiteUrl`, `numberOfEligibleEntities`, `authorizedPerson`, `categoryTwoDescription`, `installment`, `statewideState`, `allPublicSchoolDistricts`, `allNonPublicSchools`, `allLibraries`, and `otherFunction` / `otherManufacturer` / `entities` / `maintenanceAndTechnicalSupport` per service request.

Postings are deduplicated on `applicationNumber`. `allowableContractDate` is a plain ISO date. `certifiedDate`, `lastModifiedDate` and RFP `uploadDate` are ISO 8601 wall-clock times exactly as USAC publishes them; the source carries **no** timezone offset for these, so none is invented. Missing values are `null`, never an empty string.

### Input

Everything is optional. Run it with no input and you get the 100 newest postings for the current funding year.

| Field | Type | Default | Notes |
| --- | --- | --- | --- |
| `fundingYear` | string | `current` | `2027`, `current`, or `all`. Funding year N runs 1 July N-1 to 30 June N. |
| `state` | string\[] | - | Two-letter billed-entity states, exact match, e.g. `["NY","NJ"]`. |
| `serviceCategory` | enum | `any` | `Category 1` (broadband to the building) or `Category 2` (inside it). |
| `serviceType` | string | - | Substring, e.g. `internet`, `Internal Connections`, `Basic Maintenance`. |
| `keyword` | string | - | Substring on the billed entity name, e.g. `unified`, `Public Library`. |
| `postedFrom` / `postedTo` | ISO date | - | Form 470 certification date range, the date the bidding clock started. |
| `rfpOnly` | boolean | `false` | Only postings that actually carry an RFP document. |
| `maxResults` | integer | `100` | 1 to 50000. This is your cost cap: you pay per delivered posting. |
| `includeRaw` | boolean | `false` | Attach `raw`, the untouched source rows behind the posting. |

Service-level filters (`serviceCategory`, `serviceType`, `rfpOnly`) select a posting that has **at least one** matching service request, and the delivered posting still carries **all** of its service requests. A district that attached an RFP to its fiber request but not to its cabling request comes back whole.

`current` is the E-Rate funding year in force today. A funding year opens on 1 July with almost nothing in it, so `current` automatically widens to include the previous funding year when the current one cannot fill your `maxResults`; the run summary records whether that happened.

#### Example inputs

**Category 1 internet and WAN RFPs in your states** (ISP and fiber bid desk):

```json
{
  "state": ["NY", "NJ", "PA"],
  "serviceCategory": "Category 1",
  "rfpOnly": true,
  "maxResults": 200
}
```

**New Category 2 postings this week** (network and Wi-Fi vendors, daily alert):

```json
{
  "serviceCategory": "Category 2",
  "postedFrom": "2026-08-17",
  "maxResults": 500
}
```

**Every library posting for the funding year** (territory list):

```json
{
  "fundingYear": "2027",
  "keyword": "Library",
  "maxResults": 1000
}
```

### Pricing

**$0.004 per record; you pay only for records delivered.** One charge per posting, nothing else; the actor pays for its own source requests. `maxResults` is your bill: 100 postings is $0.40. Filters are applied before delivery, so a narrow search costs less than a broad one, and a search that matches nothing costs nothing. A broad, unfiltered run that came back empty is treated as a broken read and fails the run rather than reporting an empty feed.

### Data source, refresh and licence

All data comes from the **Universal Service Administrative Company (USAC)** open data portal, dataset **E-Rate FCC Form 470 Tool Data** (`jt8s-3q52`):

- Dataset page: <https://opendata.usac.org/E-Rate/E-Rate-FCC-Form-470-Tool-Data/jt8s-3q52>
- API endpoint read by this Actor: `https://opendata.usac.org/resource/jt8s-3q52.json`

**Refresh:** USAC publishes an update frequency of **Daily** for this dataset. Run the Actor on a schedule and you track USAC's own refresh; the run summary under the `OUTPUT` key records the exact filters, the SODA `$where` used, row and posting counts, and `fetchedAt`.

**Licence:** USAC publishes the dataset under the licence **"Public Domain"** and asks for the attribution **"Universal Service Administrative Company"**, which is carried on every record in the `attribution` field. `opendata.usac.org/robots.txt` allows `/resource/` and asks for `Crawl-delay: 1`; this Actor sends a descriptive User-Agent, makes one request at a time, and never less than a second apart. No login or API key is involved.

**This Actor is not affiliated with or endorsed by USAC or the FCC.** Buyers are responsible for their own use of the data.

### Limitations

- **A Form 470 is a request, not an award.** It tells you who is buying and when they can sign. Who won is a different USAC dataset (Form 471 / funding commitments).
- **Canceled forms are delivered, not hidden.** `status` is `Certified` or `Canceled`; USAC publishes both. Filter on `status` downstream if you only want live ones. A canceled form often requested nothing at all, in which case `serviceRequests` is `[]` and `serviceRequestCount` is `0`; the posting is still delivered, with its applicant block intact.
- **The 28-day clock is yours to compute.** `allowableContractDate` is the earliest date the applicant may sign, as published. This Actor does not infer a bid deadline; applicants set their own, usually inside the RFP document or in `stateOrLocalRestrictionsDescription`.
- **RFP attachments are hosted by USAC, not mirrored here.** Records carry the URL; the file itself stays at `publicdata.usac.org`.
- **Fields are as published.** USAC's own completeness notes say not-required fields are frequently blank; those arrive as `null`. `rfpAttachmentCount` is USAC's own count and can disagree with the number of distinct document URLs on the record.
- **No per-form web page.** USAC publishes no public HTML deep link for an individual Form 470, so records link to the certified form PDF and to the source rows on the API instead.

### FAQ

#### How do I search E-Rate Form 470 postings by state?

Set `state` to one or more two-letter codes. Add `serviceCategory`, `serviceType` or `rfpOnly` to keep only the postings you can bid, and `postedFrom` to keep only the ones certified since your last run.

#### What is the difference between Category 1 and Category 2 in E-Rate?

Category 1 is connectivity to the building (data transmission and internet access). Category 2 is inside the building: internal connections, managed internal broadband services and basic maintenance. `serviceCategory` filters on either, and every service request carries its own category.

#### How do I find Form 470s that have an RFP attached?

Set `rfpOnly: true`. Each delivered posting then carries at least one document URL in `rfpDocuments`, with its upload date, and the full list per service request.

#### When can an E-Rate applicant sign a contract after posting a Form 470?

No earlier than the `allowableContractDate`, 28 days after the form is certified. Bid due dates are set by the applicant, usually in the RFP document or in `stateOrLocalRestrictionsDescription`.

#### How often is the USAC Form 470 data updated?

USAC lists the dataset's update frequency as Daily. A scheduled daily run with `postedFrom` set to yesterday gives you a new-postings feed.

#### Does this show who won the contract?

No. Awards and funding commitments are on the Form 471 side of USAC open data. This dataset is the request side only.

### Related datasets

Other datasets by the same author, all pay-per-record with source, refresh cadence and licence stated on each listing:

**Nursing home and long-term care data**

- [Nursing Home Deficiency Citations (CMS) Scraper](https://apify.com/jserle/nursing-home-deficiency-citations)
- [Nursing Home Fines & Civil Money Penalties (CMS) Scraper](https://apify.com/jserle/nursing-home-penalties-fines)
- [Nursing Home Directory & Ownership (CMS Form 671) Scraper](https://apify.com/jserle/ltc-facility-characteristics)
- [Nursing Home Daily Nurse Staffing (CMS PBJ) Scraper](https://apify.com/jserle/cms-nursing-home-staffing-daily)
- [Nursing Home Change of Ownership (CMS SNF CHOW) Scraper](https://apify.com/jserle/snf-change-of-ownership-tracker)
- [New York Nursing Home Bed Availability (NYSDOH) Scraper](https://apify.com/jserle/ny-nursing-home-bed-census)

**Medicare provider enrollment data**

- [Medicare Revoked Providers List (CMS Revocations) Scraper](https://apify.com/jserle/medicare-revoked-providers)
- [Medicare Opt-Out Providers List (CMS Affidavits) Scraper](https://apify.com/jserle/medicare-opt-out-physicians)
- [Medicare Revalidation Due Date List (CMS) Scraper](https://apify.com/jserle/medicare-revalidation-due-leads)

**Public procurement and RFP feeds**

- [NYC PASSPort Procurement Scraper (RFPs, Bids & Contracts)](https://apify.com/jserle/nyc-passport-procurement)

**Developer and AI test data**

- [Synthetic Invoice OCR Ground Truth Dataset Generator](https://apify.com/jserle/ocr-ground-truth-document-factory)

# Actor input Schema

## `fundingYear` (type: `string`):

An E-Rate funding year such as 2027, or `current`, or `all`. E-Rate funding year N runs 1 July N-1 to 30 June N, so `current` is 2027 for any date from 1 July 2026 onward. A funding year opens with almost no postings, so `current` automatically widens to include the previous funding year when the current one does not yet hold enough postings to fill your maxResults.

## `state` (type: `array`):

Two-letter USPS codes of the applicant's billed entity, matched exactly. Example: \["NY", "NJ", "CT"]. Leave empty for every state and territory.

## `serviceCategory` (type: `string`):

Category 1 is connectivity to the building (internet access, data transmission, leased lit and dark fiber). Category 2 is connectivity inside the building (switches, access points, cabling, firewalls, basic maintenance, managed broadband).

## `serviceType` (type: `string`):

Case-insensitive substring on the requested service type. The source uses exactly five: Data Transmission and/or Internet Access, Internet Access and/or Telecommunications, Internal Connections, Basic Maintenance of Internal Connections, Managed Internal Broadband Services. `internet` matches the first two.

## `keyword` (type: `string`):

Case-insensitive substring on the billed entity name, which is the school district, school, library, library system or consortium doing the buying. Example: unified, or Public Library.

## `postedFrom` (type: `string`):

ISO date, for example 2026-07-01. Keeps postings certified on or after this date. The Form 470 certification date is the date the posting went live and starts the 28-day competitive bidding clock.

## `postedTo` (type: `string`):

ISO date, for example 2026-08-31. Keeps postings certified on or before the end of this day.

## `rfpOnly` (type: `boolean`):

Keep only Form 470s that have at least one Request for Proposal document attached. These are the postings with a real written spec, bid form or contract draft you can download, rather than a bare service request.

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

How many postings to deliver, newest first. You are charged per delivered posting, so this is also your cost cap.

## `includeRaw` (type: `boolean`):

Attach a `raw` array holding every source row that makes up the posting, exactly as USAC's API returned it. The source publishes one row per service request per attached RFP document, so this is verbose. Useful for auditing the normalization.

## Actor input object example

```json
{
  "fundingYear": "current",
  "serviceCategory": "any",
  "rfpOnly": false,
  "maxResults": 100,
  "includeRaw": false
}
```

# Actor output Schema

## `results` (type: `string`):

One item per FCC Form 470 posting delivered by this run.

## `summary` (type: `string`):

Resolved filters, row and posting counts, delivered records and whether the charge limit stopped the run.

# 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 = {
    "fundingYear": "current",
    "serviceCategory": "any",
    "maxResults": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("jserle/erate-form-470-rfp-feed").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 = {
    "fundingYear": "current",
    "serviceCategory": "any",
    "maxResults": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("jserle/erate-form-470-rfp-feed").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 '{
  "fundingYear": "current",
  "serviceCategory": "any",
  "maxResults": 100
}' |
apify call jserle/erate-form-470-rfp-feed --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,jserle/erate-form-470-rfp-feed"
        }
    }
}

```

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/lnBhRbDlxhxTtM7Qc/builds/K9FuVJuNPW9cDXKIU/openapi.json
