FEC Campaign-Finance Delta, Concentration & Donor Graph API avatar

FEC Campaign-Finance Delta, Concentration & Donor Graph API

Pricing

from $4.40 / 1,000 results

Go to Apify Store
FEC Campaign-Finance Delta, Concentration & Donor Graph API

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

Kyle Maloney

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

0

Monthly active users

4 days ago

Last modified

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 maxResults capped the fetch, or OpenFEC will not vouch for the count, every concentration and graph row is stamped analysis_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: false on schedule_a / schedule_b and 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). So records_available and coverage_pct are deliberately null there, rather than filled with a number that is not the denominator. /filings does 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_excluded says how many were set aside, is_memo marks every row, and includeMemoRows: true restores 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

ModeWhat it emits
searchOne normalised row per matching receipt / disbursement / filing
deltaOnly 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.
concentrationA concentration_summary row carrying the HHI, then the top-N entities by summed amount
graphAn entity to committee edge list with per-edge concentration scores

Output fields

Every declared field, one row per record / entity / edge.

Transaction

FieldDescription
record_typereceipt, disbursement, filing, concentration, concentration_summary, or edge
record_idStable FEC id (sub_id, file number, or transaction id) — the delta key
committee_id / committee_nameReceiving / filing committee
committee_type / committee_designation / committee_state / committee_party / committee_treasurerCommittee structure
candidate_id / candidate_nameLinked candidate, when FEC ties one
contributor_name / contributor_employer / contributor_occupationDonor identity (receipts)
contributor_city / contributor_zip / stateDonor location
contributor_aggregate_ytdWhat this donor has given this committee so far in the cycle — the contribution-limit number
entity_typeINDIVIDUAL, ORG, PAC, …
amount / dateTransaction amount and date
recipient_or_payeeReceiving committee (receipts) or vendor (disbursements)
transaction_type / election_typeFEC receipt type and election
conduit_committee_id / conduit_committee_nameThe 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_memoFEC memo entry (restates money already on a parent line)
filing_form / report_type / line_number_label / two_year_periodWhere in the filing it sits
memo / source_url / retrieved_atFree text, the FEC document, and when we read it

Analytics (concentration / graph)

FieldDescription
entity / counterpartyThe rolled-up entity, and the committee on the other side of an edge
total_amount / txn_countSummed amount and transaction count
concentration_scoreSquared percentage share; on the summary row it is the HHI
analysis_basiscomplete or sample — read this before quoting an HHI
analysis_rows_usedRows that actually fed the totals
analysis_rows_missing_amountRows excluded because FEC published no amount
analysis_memo_rows_excludedMemo rows set aside to avoid double-counting

Coverage & provenance (every row)

FieldDescription
fec_statusok or partial (OpenFEC stopped answering mid-run)
api_versionThe OpenFEC version that answered
records_fetchedRows this run actually retrieved
records_availableThe matching total, only when OpenFEC vouches for it
records_available_estimateOpenFEC's count when it is flagged inexact — an order of magnitude, not a denominator
records_available_is_exactWhether OpenFEC vouched for the count
coverage_pct / coverage_completeCoverage, null when the count is not exact
pages_fetched / stopped_early_reasonexhausted, max_results, seek_stalled, or upstream_error: …
results_truncatedmaxResults 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_treasurerAny filings query, or a receipts query (the committee sub-object).
candidate_id, candidate_nameA candidate committee - pass candidateId, or a principal campaign committee id. Party and hybrid PACs have none.
conduit_committee_id, conduit_committee_nameAn 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_reasonOnly 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. /filings uses page/offset; schedule_a and schedule_b use seek pagination and require the whole last_indexes object echoed back — sending only last_index returns HTTP 422. This actor forwards every key verbatim, and detects a cursor that stops advancing rather than looping.
  • per_page caps at 100.
  • An unscoped schedule_a / schedule_b query is rejected unless it has a committee, candidate, contributor, state filter, or a single two_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=C00694323 alone returns HTTP 504 {"message":"Query timed out"}; the same query with a two_year_transaction_period returns HTTP 200. This actor adds a cycle scope to any seek query with no sinceDate/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's load_date so 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_name is null on a live schedule_a row; the committee name lives in the committee sub-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.

  • 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.