NY Campaign Finance Filer Status Delta avatar

NY Campaign Finance Filer Status Delta

Pricing

$10.00 / 1,000 filer status changeds

Go to Apify Store
NY Campaign Finance Filer Status Delta

NY Campaign Finance Filer Status Delta

Watches named New York campaign finance filers (candidates, committees, PACs) in the official NYSBOE filer registry and alerts only on a genuine status change: active to terminated, or reactivated. For journalists, political consultants, and compliance/watchdog teams. Free when nothing changes.

Pricing

$10.00 / 1,000 filer status changeds

Rating

0.0

(0)

Developer

Radu Furtuna

Radu Furtuna

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

8 days ago

Last modified

Share

Durable monitor for the official New York State Board of Elections "Campaign Finance Filer Data: Beginning 1974" registry, published as open data on data.ny.gov. Watch specific candidates, committees, and PACs by their FILER_ID number and get notified only when their registry status genuinely changes — active to terminated, or reactivated. No API key needed, no login, no captcha.

Source

https://data.ny.gov/resource/7x2g-h32p.json — the Socrata Open Data API for "Campaign Finance Filer Data: Beginning 1974" (dataset id 7x2g-h32p), attributed to the New York State Board of Elections. Confirmed live 13.09.2026: 64,656 rows, last modified today — an actively maintained registry, not a static snapshot. filer_id is the dataset's unique, permanent NYSBOE ID number for one filer — not their name, which is never unique. filer_status observed live (13.09.2026) is genuinely binary: ACTIVE (10,639 rows) and TERMINATED (54,017 rows) — real transitions (a committee closes down, or reopens for a later cycle), not a static roster. Confirmed live: filer_id is unique per row (a

$group=filer_id& $having=count(*)>1
query returned zero results across all 64,656 rows) — this is a one-row-per-filer snapshot table, not a filing-history table.

How it works

  1. Each watch names exactly one filer by filerId (its NYSBOE FILER_ID number — look it up at publicreporting.elections.ny.gov if you only have a candidate/committee name). One watch = one point query = one HTTP request per run.
  2. The first check of a new watch establishes a baseline (no charge). Every later check compares the current filer_status against the durable record of what it was last time.
  3. Billing is tied only to the filer_status field — not to the filer's name, treasurer, address, or any other field, all of which can change routinely (a committee moves office, changes treasurer) without the filer's active/terminated status changing. A status that changes and later reverts (e.g. terminated, then reactivated for a new cycle) bills every genuine transition, never silently deduplicated against an earlier occurrence of the same status value.
  4. Genuinely new (first found) or status-changed filers are pushed to the dataset and billed once each (filer-status-changed); a check that finds nothing new costs nothing beyond the fixed platform run cost.

Input

{
"monitorId": "my-filer-watch",
"watches": [
{ "watchId": "friends-of-paul-whalen", "filerId": "100" }
],
"notifyOn": "new_alerts",
"webhookUrl": "https://example.com/webhook"
}

Add more filers later under the same monitorId — each watch keeps its own independent history. A watchId is permanently bound to the filerId it first saw; pointing the same watchId at a different FILER_ID later fails the run instead of silently mixing histories.

socrataAppToken is optional — data.ny.gov does not require a key for this dataset, but a free Socrata app token from your own account raises the anonymous request-rate ceiling if you run many watches across many monitors.

Output row (per change)

watchId, filerId, changeType ("new"|"status_changed"), status, previousStatus, filerName, complianceTypeDesc, filerTypeDesc, committeeTypeDesc, officeDesc, district, countyDesc, monitorId, runId, discoveredAt, eventId, billed

Billing

Pay-per-event: filer-status-changed — charged only for a watch's first found status (baseline is free) or a genuine filer_status transition since the previous check. Failed/blocked checks are never charged.

Important — read before relying on this for any journalism/compliance/watchdog decision

This registry is a publication of NYSBOE filer status, not a certification of an active campaign's current standing, and not a record of actual transactions. It does not cover the individual receipts and expenditures a filer discloses (a separate NYSBOE dataset, "Campaign Finance Disclosure Reports Data") — this actor watches only the filer-level ACTIVE/TERMINATED status, not transaction-level filings. The public dataset reflects what NYSBOE has published, which can lag real-world events. This actor is an informational monitor of CHANGES to that public status field — it is NOT a legal or financial finding, and NOT a substitute for direct verification at publicreporting.elections.ny.gov before citing or acting on any single entry, especially in published journalism, opposition research, or compliance work.

Delivery guarantee: at-most-once (we would rather lose an alert than bill you twice)

Each computed change is delivered to the dataset and charged at most once, for as long as the monitor's claim log exists (see the boundary below). Before any irreversible step (writing the row, charging the event) the run takes an atomic claim on that exact change, using the only atomic primitive the Apify platform offers: a request queue's unique-key insert. Exactly one run can win that claim for a given change. The claim log is never consumed, deleted or rotated by this actor; it is a permanent record of what was already attempted, and coverage.claimJournalSize reports its size each run so you can watch it grow (the platform's counter is eventually consistent, so treat it as a lagging estimate, not an exact count).

Where that guarantee ends — the honest boundary. The claim log lives in a named request queue (<prefix>-<monitorId>-claims) in your own account. The at-most-once guarantee holds as long as that queue keeps existing. If you — or any process holding your account credentials — delete, rename or re-create it from the Console or API, the log starts empty and previously delivered changes can be delivered and charged again. That is the unavoidable boundary of any durable storage, not a loophole in the protocol. For the same reason, the actor's storage prefix and internal claim namespace are frozen after release: changing either would create a fresh, empty log with exactly the same effect.

The response the platform returns for each claim is interpreted strictly: only a real boolean false grants the right to write and charge, only a real boolean true denies it, and anything else — a missing field, null, 0, an empty string, a changed SDK response shape — aborts the run's delivery for that item with claim_protocol_error before any row or charge. An answer we do not fully understand is never read as "you may charge".

One thing we deliberately do not claim: the monitor's lease makes overlapping runs a fail-closed exception rather than a fact of life, but between the moment a run verifies it still holds the lease and the moment the dataset write or charge actually lands there is an unavoidable time gap (the platform offers no fencing token for datasets or billing). So "a run that lost the lease can never write another row" would be an overstatement. What actually protects your money is the claim above: the key is already taken, so even a ghost run cannot charge for the same change twice.

The honest consequence, stated plainly: if a run dies after taking the claim but before finishing, that one change is lost. It is recorded as dataset_unknown or charge_unknown and it is not re-delivered on the next run — the next run moves on to the filer's next status transition. We deliberately chose possible loss of one alert over the possibility of charging you twice for the same event. This is at-most-once delivery, not exactly-once; any actor that claims exactly-once over a store without compare-and-swap is overstating what the platform can do.

Practically this only happens if the Apify run is killed mid-delivery (platform abort, timeout, migration). Every such case is visible: the run's coverage and run_summary report it, and run_summary.eventsBilled plus Apify's own billing ledger remain the source of truth for what you actually paid for.

Honest limits

  • The durable dataset is a delivery-attempt log, not a guaranteed mirror of the default dataset. Each row is written to the durable dataset first, then mirrored to the run's default dataset before billing proceeds for that row. If the durable write succeeds but the default-dataset mirror write fails (e.g. transient Apify storage error), the item is marked dataset_unknown, billing for it is permanently blocked (fail-closed — we never charge for a row we can't confirm was delivered), and the run is not retried into re-creating that exact row. The durable dataset can therefore end up with a small number of orphan rows that were never mirrored and never billed. The default dataset is the canonical log of rows successfully written to this run's output (see its run_summary row) — but a default-dataset row does not by itself prove the row was billed: the row is written before Actor.charge() runs, so if charging then fails or comes back charge_unknown, the row is present but not confirmably paid. run_summary.eventsBilled and Apify's own billing ledger are the source of truth for confirmed payment, not the presence of a row in either dataset.
  • One watch = one filer, one request. There is no bulk/roster mode — to track a slate of candidates or committees, add one watch per filer (up to 30 per run). This keeps the network cost fixed and predictable, and keeps each filer's history independently auditable.
  • A "not found" result on the very first check of a watch is reported honestly, not as an error — coverage.watches[].matched: false. This almost always means the filerId was mistyped; it costs nothing and is safe to correct and retry under the same watchId.
  • A filerId that WAS matched on a previous check but is NOT found on a later one is treated as source_access_limited for that watch this run — no baseline/history update, no billing. We have not verified live whether NYSBOE ever removes a filer_id from this snapshot (unlike some append-only NY DOS filing-history datasets, this is a status snapshot, not a filing log) — so this is the honest fallback rather than an unverified claim that a disappearance is always benign or always a real deletion.
  • Billing tracks only the filer_status field, deliberately excluding filer name, treasurer, address, office sought, and other context fields (a routine record update — new treasurer, moved office — is not a status change). Those fields are still delivered in every row for context.
  • We don't invent data: if the API ever returns something other than a bare JSON array, 2+ rows for the same filer_id (violating the dataset's own uniqueness contract — confirmed live that every filer_id is currently unique), a row missing filer_id/filer_name/filer_status, or a row whose filer_id doesn't exactly match the one requested (even after canonicalizing leading zeros/whitespace the same way input is canonicalized), the run reports it honestly (source_access_limited) instead of guessing which record it actually found.

Author: OmniCoder (https://t.me/OmniCoder)