# FEC Campaign Finance API - Candidates, Donors & PAC Scraper (`captainhandsome/fec-campaign-finance-search`) Actor

Search FEC campaign finance data: federal candidates, committees and PACs, and individual contributions by state, employer, occupation or amount, including Schedule A donor lookups. An FEC data scraper over the official API with no FEC key needed.

- **URL**: https://apify.com/captainhandsome/fec-campaign-finance-search.md
- **Developed by:** [Joseph McRell](https://apify.com/captainhandsome) (community)
- **Categories:** Business, News
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.10 / 1,000 results

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?

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

## FEC Campaign Finance Search (Candidates, Donors, PACs)

Search the US Federal Election Commission's official register of federal candidates, committees and PACs, and individual campaign contributions, and get flat spreadsheet-ready rows back. No API key from you, no login and no scraping: this reads the FEC's own API, which is free and public because federal law requires campaign finance filings to be disclosed. Set `max_items` before each run to keep output and cost predictable.

### What data can I extract?

Three searches, selected with `data_type`. A run returns one record type, because candidates, committees and contributions have genuinely different fields and mixing them would hand you half-empty rows.

**Candidates** — `name`, `party`, `office`, `state`, `district`, `status`, `incumbent_or_challenger`, `election_years`, `active_through`, `has_raised_funds`, `first_file_date`, `last_file_date`, `url`.

**Committees and PACs** — `name`, `committee_type`, `designation`, `organization_type`, `party`, `state`, `treasurer_name`, `filing_frequency`, `candidate_ids`, `first_file_date`, `last_file_date`, `url`.

**Contributions** — who gave, how much, and to whom: `contributor_name`, `contributor_city`, `contributor_state`, `contributor_zip`, `contributor_employer`, `contributor_occupation`, `entity_type`, `is_individual`, `amount`, `date`, `receipt_type`, `committee_id`, `committee_name`, `candidate_name`, `candidate_office`, `two_year_period`.

Contributor street addresses are deliberately excluded. The FEC publishes them, and disclosure is the entire point of the system, but city, state, ZIP, employer and occupation are what make the data analytically useful — they let you map giving by geography, industry and profession. The street line adds nothing to that analysis and is the most sensitive part of the record.

Every row is written to the default dataset and can be downloaded as JSON, CSV, Excel or XML, or pulled through the Apify API.

#### All 131 columns

**Identity** — `affiliated_committee_name`, `agent_name`, `agent_title`, `candidate_id`, `candidate_ids`, `candidate_name`, `candidate_office`, `candidate_office_district`, `candidate_office_state`, `committee_id`, `committee_name`, `conduit_committee_id`, `conduit_committee_name`, `contributor_first_name`, `contributor_id`, `contributor_last_name`, `contributor_middle_name`, `contributor_name`, `district_number`, `donor_committee_name`, `id`, `image_number`, `individual_contributions`, `individual_itemized_contributions`, `individual_unitemized_contributions`, `is_individual`, `line_number_label`, `name`, `principal_committee_id`, `principal_committee_ids`, `principal_committee_name`, `sponsor_candidate_ids`, `sponsor_candidate_names`, `transaction_id`, `treasurer_name`

**Status** — `active_through`, `committee_type`, `committee_type_code`, `designation`, `designation_code`, `election_type`, `election_type_code`, `entity_type`, `entity_type_code`, `federal_funds_flag`, `form_type`, `has_raised_funds`, `inactive_election_years`, `is_inactive`, `last_report_type`, `organization_type`, `organization_type_code`, `party_type`, `receipt_type`, `receipt_type_code`, `record_type`, `report_type`, `status`

**Dates** — `coverage_end_date`, `coverage_start_date`, `date`, `election_years`, `fec_election_year`, `first_f1_date`, `first_file_date`, `last_f1_date`, `last_f2_date`, `last_file_date`, `last_report_year`, `load_date`, `report_year`, `two_year_period`

**Location** — `address_city`, `address_state`, `address_street`, `address_zip`, `committee_city`, `committee_state`, `committee_zip`, `conduit_committee_city`, `conduit_committee_state`, `contributor_city`, `contributor_state`, `contributor_zip`, `district`, `state`, `state_full`, `treasurer_city`, `treasurer_state`

**Money** — `amount`, `cash_on_hand`, `contribution_refunds`, `debts_owed`, `disbursements`, `independent_expenditures`, `net_contributions`, `net_operating_expenditures`, `operating_expenditures`, `other_political_committee_contributions`, `political_party_committee_contributions`, `receipts`

**People** — `agent_phone`, `contributor_aggregate_ytd`, `contributor_employer`, `contributor_occupation`, `contributor_prefix`, `contributor_suffix`, `transfers_from_other_authorized_committee`, `treasurer_phone`

**Contact** — `email`, `fax`, `pdf_url`, `url`, `website`

**Counts and measures** — `memoed_subtotal`

**Other detail** — `amendment_indicator`, `cycles`, `filing_form`, `filing_frequency`, `financials_cycle`, `increased_limit`, `incumbent_challenge_code`, `incumbent_or_challenger`, `leadership_pac`, `lobbyist_registrant_pac`, `memo_code`, `memo_text`, `office`, `office_code`, `party`, `party_code`

### Input example

```json
{
  "data_type": "candidates",
  "state": "CA",
  "party": "DEM",
  "office": "H",
  "max_items": 50
}
```

| Field | Applies to | Notes |
|---|---|---|
| `data_type` | all | `candidates`, `committees` or `contributions` |
| `name` | all | Candidate, committee or contributor name, depending on `data_type` |
| `state` | all | Two-letter code. For contributions this is the **contributor's** state, not the recipient's |
| `party`, `office`, `election_year` | candidates | `party` takes the three-letter code, `office` is H, S or P |
| `committee_type` | committees | FEC committee type code, for example `N` for a PAC or `H` for a House campaign committee |
| `committee_id`, `employer`, `occupation`, `min_amount` | contributions | `committee_id` narrows to one recipient |
| `two_year_period` | contributions | Election cycle; `2024` covers 2023 and 2024. Defaults to 2024 |
| `max_items` | all | Hard ceiling on rows, and therefore on spend. Defaults to 100 |

The default input is deliberately small. Unsupported parameters are rejected rather than silently ignored.

### Output example

Real rows from this Actor, unedited. A candidate:

```json
{
  "record_type": "candidate",
  "id": "H2CA30291",
  "name": "AAZAMI, SHERVIN",
  "party": "DEMOCRATIC PARTY",
  "office": "House",
  "state": "CA",
  "district": "32",
  "status": "N",
  "incumbent_or_challenger": "Challenger",
  "active_through": 2024,
  "first_file_date": "2021-01-16",
  "last_file_date": "2022-07-15",
  "has_raised_funds": true,
  "election_years": "2022, 2024",
  "url": "https://www.fec.gov/data/candidate/H2CA30291/"
}
```

And a contribution:

```json
{
  "record_type": "contribution",
  "id": "4121220241075839591",
  "contributor_name": "MURPHY, PAUL",
  "contributor_city": "SANTA CLARA",
  "contributor_state": "CA",
  "contributor_zip": "95054",
  "contributor_employer": "APPLIED MATERIALS INC",
  "contributor_occupation": "MANAGING DIRECTOR OF ENGINEERING",
  "entity_type": "INDIVIDUAL",
  "is_individual": true,
  "amount": 120,
  "date": null,
  "receipt_type": "CONTRIBUTION",
  "committee_id": "C00406892",
  "committee_name": "APPLIED MATERIALS, INC. POLITICAL ACTION COMMITTEE",
  "candidate_name": null,
  "candidate_office": null,
  "two_year_period": 2024
}
```

That `date: null` is not a bug and it is not rare — recurring payroll-deduction and memo lines routinely file without a receipt date. `candidate_name` and `candidate_office` are null here for the same honest reason: they are only populated on conduit rows, where a contribution passed through ActBlue, WinRed or a similar platform earmarked for a named candidate. Fields the FEC does not supply stay explicitly null rather than being filled in with a plausible guess.

### Common use cases

- Mapping employer and industry giving from `contributor_employer` and `contributor_occupation`
- Journalism and opposition research on who funds a particular committee
- Building a full candidate roster for a state, party or cycle before an election
- Lobbying disclosure and political-exposure checks against a list of names
- Tracking a single recipient over time by passing its `committee_id` back in
- Academic and quantitative study of campaign finance patterns

### Use with AI agents and MCP

Apify's MCP server can discover and call this Actor from an AI workflow. Example intent:

> Find contributions of $5,000 or more from Texas donors in the 2024 cycle and tell me which committees received them.

Use the JSON from **Input example** as the tool arguments, changing `data_type` to `contributions`. The strict input schema rejects unsupported parameters instead of ignoring them, and the dataset schema documents every field — including which ones are routinely null and why, which matters because an agent that reads a null `candidate_name` as a failure will retry a run that actually succeeded.

### Pricing and cost control

Output is billed per result at **$0.003 per result** (about $3.00 per 1,000 results), plus a $0.0005 Actor-start charge billed once per gigabyte of memory at run start. Use `max_items` to cap both output volume and charges. The price shown on the Apify Store listing is authoritative.

So 10 rows cost $0.03, 100 rows cost $0.30, 1,000 rows cost $3.00, and 20,000 rows — the schema maximum — cost $60. Apify platform charges shown separately on your account are not included in those figures.

There is no subscription and no minimum. `max_items` is a hard ceiling on rows returned, so the most a run can cost you is a number you fix before you start it. Begin with 10 rows, read them, then scale deliberately.

### Reliability

**Contributions do not page the way you expect, and getting it wrong fails silently.** Schedule A — the individual contribution file — holds tens of millions of rows and page numbers stop advancing on it: ask for page 200 and you get page one back, with a 200 status and no error. Continuation is by keyset instead: you hand back the previous row's index and receipt date. Candidates and committees, in the same API, use ordinary page numbers. A wrapper that uses page numbers everywhere re-reads page one forever and returns the same 100 donors over and over without ever raising an error. This Actor uses the correct pagination for each endpoint.

The other reliability problem is the key. The FEC API is free but keyed, and the shared demo key that everything falls back to allows ten requests an hour — roughly a thousand contribution rows before it stops. There is no key field in the input: the Actor takes its key from the platform side, so there is nothing for you to register or paste in. It does not truncate quietly either. The first line of the run log names which key the run is using, and a rate-limited or failed request ends the run with the status code logged rather than handing you a short dataset that looks complete.

Rate limiting and upstream errors stop the run and are logged with the status code, partial results are kept rather than discarded, and year fields that the FEC returns as strings on one endpoint and integers on another are coerced at write time so the dataset schema always holds.

### Limitations and responsible use

- **One record type per run.** Candidates, committees and contributions cannot be mixed in a single run.
- **No financial totals or summaries.** Candidate receipts, disbursements, cash on hand and independent expenditures live on separate FEC endpoints and are not returned here.
- **A contribution row is one filing line, not one donor.** Somebody giving monthly appears as twelve rows. There is no de-duplication and names are not normalised, so `SMITH, JOHN` and `SMITH, JOHN A` stay separate.
- **`receipt_type` matters when you sum.** Memo entries record a share of a joint fundraising contribution and will double-count if you add them to the parent line.
- **`two_year_period` is mandatory for contributions** and is the FEC's own cycle grouping, not a calendar year.
- **Employer and occupation are self-reported** by the donor, frequently blank or abbreviated, and verified by nobody.
- **`status` and `filing_frequency` are raw single-letter codes.** The API returns null for its own expanded labels, so this Actor passes the codes through rather than inventing meanings for them.
- **Filings are amended.** A contribution can be revised or removed after it first appears, so a figure pulled today may not match one pulled next month.

This is public disclosure data that federal law requires to be published, and the FEC provides the API free for programmatic use. The Actor uses that official API — it does not scrape the website and there is no rate-limit evasion or access circumvention involved. Use it lawfully, respect the source terms, and treat named individuals in the contribution file as people rather than as leads.

### FAQ

#### Does it need my own API key?

No. There is no key field in the input and nothing for you to register at api.data.gov. Check the first line of the run log if a dataset comes back shorter than you asked for: it names the key the run used, so a rate-limited run is visible rather than silent.

#### Why is `date` null on some contributions?

Because the filer did not report one. Recurring payroll-deduction and memo lines frequently omit the receipt date, and the Actor leaves the field null rather than substituting a date from elsewhere in the record.

#### Why are `candidate_name` and `candidate_office` usually empty?

They are only filled in on conduit rows, where the contribution was earmarked for a named candidate through a platform such as ActBlue. A contribution made directly to a committee carries no candidate attribution on the line.

#### Can I get contributor street addresses?

No. They are excluded by design. City, state and ZIP are returned, which is enough for geographic analysis.

#### How do I control cost?

Set `max_items` to the most rows you want. It is a hard ceiling on both output and charges.

#### Can I export the results?

Yes. Download the default dataset as JSON, CSV, Excel or XML, or retrieve it through the Apify API.

#### Can an AI agent call it?

Yes. The Actor has a strict input schema and a fully documented output schema, and can be called through Apify's MCP tooling.

# Actor input Schema

## `data_type` (type: `string`):

Candidates, committees and PACs, or individual contributions.

## `name` (type: `string`):

Candidate, committee or contributor name depending on what you are searching.

## `state` (type: `string`):

Two-letter state code. For contributions this is the CONTRIBUTOR's state.

## `party` (type: `string`):

Three-letter party code, e.g. DEM, REP, LIB. Candidates only.

## `office` (type: `string`):

Office sought. Candidates only.

## `election_year` (type: `integer`):

Candidates only.

## `committee_type` (type: `string`):

FEC committee type code, e.g. N for PAC. Committees only.

## `committee_id` (type: `string`):

Limit contributions to one recipient committee, e.g. C00887760.

## `employer` (type: `string`):

Contributions only.

## `occupation` (type: `string`):

Contributions only.

## `min_amount` (type: `integer`):

Contributions only.

## `two_year_period` (type: `integer`):

FEC election cycle for contributions, e.g. 2024 covers 2023-2024.

## `include_details` (type: `boolean`):

Adds money and contact columns from the FEC's detail endpoints. Candidates gain receipts, disbursements, cash on hand, debts and the Form 2 mailing address at a cost of one extra request per 100 rows. Committees gain the same financial summary plus street address, email, website, fax and treasurer contact, at a cost of two extra requests per row - a 100-row committee run therefore makes about 200 extra calls and takes noticeably longer. No effect on contributions, whose recipient committee is already included in every row.

## `max_items` (type: `integer`):

Hard cap on rows returned. Also caps your spend.

## Actor input object example

```json
{
  "data_type": "candidates",
  "state": "CA",
  "two_year_period": 2024,
  "include_details": false,
  "max_items": 100
}
```

# Actor output Schema

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

One flat row per candidate, committee or contribution.

# 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 = {
    "state": "CA"
};

// Run the Actor and wait for it to finish
const run = await client.actor("captainhandsome/fec-campaign-finance-search").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 = { "state": "CA" }

# Run the Actor and wait for it to finish
run = client.actor("captainhandsome/fec-campaign-finance-search").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 '{
  "state": "CA"
}' |
apify call captainhandsome/fec-campaign-finance-search --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,captainhandsome/fec-campaign-finance-search"
        }
    }
}

```

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/awl3tKR8D927tTc4e/builds/6Ca65IKGl3fyDPJw0/openapi.json
