FEC Campaign-Finance Delta, Concentration & Donor Graph API
Pricing
from $4.40 / 1,000 results
FEC Campaign-Finance Delta, Concentration & Donor Graph API
Track FEC filings & receipts: new-filing delta feed, donor/vendor concentration (HHI) analytics, and a contributor-committee graph. OpenFEC source.
Pricing
from $4.40 / 1,000 results
Rating
0.0
(0)
Developer
Kyle Maloney
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
0
Monthly active users
4 days ago
Last modified
Categories
Share
Turns the FEC's OpenFEC API into four things the raw feed does not give you: a new-activity delta, donor / vendor concentration with an HHI, a contributor to committee graph, and a normalised search feed — across itemized receipts, disbursements and report filings.
A research and journalism tool built on public FEC disclosures. It reproduces filed data and arithmetic over it. It is not legal or compliance advice, and an HHI is a descriptive statistic, not a finding about anyone's conduct. Amended and memo filings are part of normal FEC practice.
Who it's for
- Political-intel and opposition-research desks watching a committee for new money since the last check.
- Journalists looking for donor concentration behind a PAC, or the vendors a campaign actually pays.
- PAC compliance and treasurers reconciling itemized activity.
- Academics and civic-tech needing a reproducible donor graph.
- AI agents — every row states its own coverage and, on analytic rows, whether the statistic describes the whole population or the retrieved sample.
The honesty this actor is built around
An HHI is a ratio over a population. OpenFEC's itemized tables are enormous
— verified live on 2026-08-01, a single committee-scoped schedule_a query
reported 71,487,069 matching rows — and no run reads all of them. So:
- When
maxResultscapped the fetch, or OpenFEC will not vouch for the count, every concentration and graph row is stampedanalysis_basis: "sample". It is a true statistic about the most recent N transactions, and it is labelled as such rather than presented as the committee's donor concentration. - OpenFEC returns
is_count_exact: falseonschedule_a/schedule_band the count it publishes is a partition-scale estimate, not the filtered total (30,287,372 and 71,487,069 on two different committee-scoped queries). Sorecords_availableandcoverage_pctare deliberatelynullthere, rather than filled with a number that is not the denominator./filingsdoes return an exact count (53 for the same committee) and coverage is reported. - FEC memo entries are excluded from totals by default. A memo row
restates money already reported on a parent line; summing it double-counts.
analysis_memo_rows_excludedsays how many were set aside,is_memomarks every row, andincludeMemoRows: truerestores them if you want the detail. - A transaction with no published amount contributes nothing, and is
counted in
analysis_rows_missing_amount. It is never treated as$0.
Example input
{"apiKey": "DEMO_KEY","mode": "concentration","dataType": "receipts","committeeId": "C00694323","concentrationBy": "employer","topN": 100,"maxResults": 2000}
DEMO_KEY is prefilled and works for a demo. api.data.gov caps it at roughly
30 requests/hour shared across every user of that key, so bring your own
free key from https://api.open.fec.gov/developers for real runs. If the key
is refused, the run fails loudly instead of returning an empty dataset.
Modes
| Mode | What it emits |
|---|---|
search | One normalised row per matching receipt / disbursement / filing |
delta | Only rows not seen in a previous run of the same query scope, and/or on or after sinceDate. State lives in a named key-value store keyed by your filters, so it survives across scheduled runs and one scope cannot overwrite another's baseline. |
concentration | A concentration_summary row carrying the HHI, then the top-N entities by summed amount |
graph | An entity to committee edge list with per-edge concentration scores |
Output fields
Every declared field, one row per record / entity / edge.
Transaction
| Field | Description |
|---|---|
record_type | receipt, disbursement, filing, concentration, concentration_summary, or edge |
record_id | Stable FEC id (sub_id, file number, or transaction id) — the delta key |
committee_id / committee_name | Receiving / filing committee |
committee_type / committee_designation / committee_state / committee_party / committee_treasurer | Committee structure |
candidate_id / candidate_name | Linked candidate, when FEC ties one |
contributor_name / contributor_employer / contributor_occupation | Donor identity (receipts) |
contributor_city / contributor_zip / state | Donor location |
contributor_aggregate_ytd | What this donor has given this committee so far in the cycle — the contribution-limit number |
entity_type | INDIVIDUAL, ORG, PAC, … |
amount / date | Transaction amount and date |
recipient_or_payee | Receiving committee (receipts) or vendor (disbursements) |
transaction_type / election_type | FEC receipt type and election |
conduit_committee_id / conduit_committee_name | The conduit that processed it — most small-dollar money moves through ActBlue or WinRed, and this is the difference between who gave and who processed |
is_memo | FEC memo entry (restates money already on a parent line) |
filing_form / report_type / line_number_label / two_year_period | Where in the filing it sits |
memo / source_url / retrieved_at | Free text, the FEC document, and when we read it |
Analytics (concentration / graph)
| Field | Description |
|---|---|
entity / counterparty | The rolled-up entity, and the committee on the other side of an edge |
total_amount / txn_count | Summed amount and transaction count |
concentration_score | Squared percentage share; on the summary row it is the HHI |
analysis_basis | complete or sample — read this before quoting an HHI |
analysis_rows_used | Rows that actually fed the totals |
analysis_rows_missing_amount | Rows excluded because FEC published no amount |
analysis_memo_rows_excluded | Memo rows set aside to avoid double-counting |
Coverage & provenance (every row)
| Field | Description |
|---|---|
fec_status | ok or partial (OpenFEC stopped answering mid-run) |
api_version | The OpenFEC version that answered |
records_fetched | Rows this run actually retrieved |
records_available | The matching total, only when OpenFEC vouches for it |
records_available_estimate | OpenFEC's count when it is flagged inexact — an order of magnitude, not a denominator |
records_available_is_exact | Whether OpenFEC vouched for the count |
coverage_pct / coverage_complete | Coverage, null when the count is not exact |
pages_fetched / stopped_early_reason | exhausted, max_results, seek_stalled, or upstream_error: … |
results_truncated | maxResults cut the fetch. Rows come newest first, so a truncated run is the most recent slice, not a ranked one |
Populating every column
A column that is null on your run is usually null because your query did not reach it, not because it is broken. Verified live inputs, 2026-08-01:
| Column(s) | Input that populates it |
|---|---|
records_available, coverage_pct, coverage_complete | {"dataType": "filings", "committeeId": "C00010603"} - /filings returns an exact count (2,320 DNC filings, coverage 2.59% at maxResults 60). Null on receipts/disbursements by design. |
counterparty | {"mode": "graph"} - only edge rows have two ends (verified: 40/40 populated). |
entity, total_amount, txn_count, concentration_score, analysis_* | {"mode": "concentration"} or {"mode": "graph"}. |
committee_party, committee_type, committee_state, committee_treasurer | Any filings query, or a receipts query (the committee sub-object). |
candidate_id, candidate_name | A candidate committee - pass candidateId, or a principal campaign committee id. Party and hybrid PACs have none. |
conduit_committee_id, conduit_committee_name | An earmarked contribution whose filer names the conduit. Populated by the mapper and pinned by an offline fixture, but not reproduced on any live committee sampled on 2026-08-01 - a conduit committee reports its own receipts without naming itself, and the downstream recipients sampled did not populate it either. Treat as sparse. |
is_memo = true, analysis_memo_rows_excluded | {"committeeId": "C00744946"} - 44 of 50 sampled rows were memo entries on 2026-08-01. v1.0 summed all of them, roughly doubling that committee total. |
fec_status = partial, stopped_early_reason | Only when OpenFEC stops answering mid-run. A null here is good news. |
Use as an MCP tool
Available over mcp.apify.com. An agent should read analysis_basis before
quoting any HHI, and fec_status / coverage_complete before describing a
result as a complete picture. A run that cannot substantiate its own coverage
fails rather than returning a plausible number.
Source & verified behaviour
- OpenFEC,
https://api.open.fec.gov/v1, with a free api.data.gov key. Endpoints:/schedules/schedule_a(receipts),/schedules/schedule_b(disbursements),/filings. - Two different pagination schemes.
/filingsuses page/offset;schedule_aandschedule_buse seek pagination and require the wholelast_indexesobject echoed back — sending onlylast_indexreturns HTTP 422. This actor forwards every key verbatim, and detects a cursor that stops advancing rather than looping. per_pagecaps at 100.- An unscoped
schedule_a/schedule_bquery is rejected unless it has a committee, candidate, contributor, state filter, or a singletwo_year_transaction_period. When you supply none, the actor scopes to the current two-year cycle so the query returns real data. - An itemized query unbounded in TIME times out, even when it is scoped to
a committee. Verified 2026-08-01:
committee_id=C00694323alone returns HTTP 504{"message":"Query timed out"}; the same query with atwo_year_transaction_periodreturns HTTP 200. This actor adds a cycle scope to any seek query with nosinceDate/untilDate, and leaves date-bounded queries exactly as you asked for them. Transient 504{"message":"Query timed out"}. 429 / 502 / 503 / 504 are retried with exponential backoff so a transient blip does not silently shorten your dataset; a 401 / 403 fails immediately. - Many itemized rows carry a null
contribution_receipt_date; the actor falls back to FEC'sload_dateso those rows are not invisible to a date-windowed delta. - FEC two-year cycles are named for the even year that ends them — 2025 and 2026 activity both sit in period 2026.
committee_nameis null on a liveschedule_arow; the committee name lives in thecommitteesub-object (verified:committee.name = "WINRED").
FAQ
Why does analysis_basis say sample? Because the HHI was computed over
the rows this run retrieved, not the whole population — either maxResults
capped the fetch or OpenFEC could not give an exact count. It is a real
statistic about that sample. Raise maxResults or narrow the filters for a
population figure.
Why are records_available and coverage_pct null? Because OpenFEC
returned is_count_exact: false for that query. The count it publishes on
schedule_a / schedule_b is a partition-scale estimate in the tens of
millions, not the filtered total, so deriving a percentage from it would be
worse than saying nothing. /filings gives an exact count and coverage is
reported there.
Why is my total lower than I expected? Memo entries are excluded by
default — they restate money already reported on a parent line, so including
them double-counts. See analysis_memo_rows_excluded, and set
includeMemoRows: true if you want them.
Does delta mode really remember across runs? Yes — state lives in a
named key-value store (fec-campaign-finance-delta-state), keyed by your
query scope. The first run for a scope establishes the baseline and says so in
the log. (Earlier versions used the default store, which is per-run.)
Do I need my own key? For anything real, yes. DEMO_KEY is shared across
every user of it and capped at roughly 30 requests/hour. A key set as the
FEC_API_KEY secret env var is preferred over the DEMO_KEY placeholder.
Pricing
Pay per result: one billable result = one transaction, entity, or edge row. Tiered discounts apply on paid Apify plans. A run that cannot substantiate its own coverage emits nothing and bills nothing for results.
Related actors
- FDIC Deposit Market Share & HHI Rollup — the same concentration maths on bank deposit markets.
- RIA Registration Delta Monitor and PCAOB Auditor Engagement Monitor — adjacent regulatory-disclosure delta feeds.