Iowa Funnel

The Iowa Funnel API

A free, read-only JSON API for the same public records the site shows: bills, votes, legislators, committees, counties, cities and school districts, elections, campaign money totals and the state budget. For spreadsheets, every dataset is also a CSV download.

Get a free key

Keys are free. Sign in (no password needed) to make one.

Sign in to get a key

How it works

  • A free key. Send it in the X-Api-Key header (or as ?api_key=, which is easier to try but ends up in logs and browser history).
  • 1,000 requests an hour per key. Each response says how many are left (X-RateLimit-Remaining) and when the count starts over (X-RateLimit-Reset). Past the limit you get status 429.
  • Read-only, GET only. Any website may call it from the browser (CORS is open). Responses are cached for five minutes, so data can be a few minutes behind the site.
  • Every response has data, and meta with the source, as_of (when that source last loaded), and for lists page, per_page (up to 500), total and the next page’s address.
  • What’s left out: anything about the people who use Iowa Funnel (accounts, follows, saved addresses, emails), admin data, individual donors, payments to individuals, school administrators’ contacts and private court cases.
  • Terms: free; please credit “Iowa Funnel” and link to the page; no guarantee it’s complete, current or always up. See the terms.

Try it

curl -H "X-Api-Key: YOUR_KEY" "https://iowafunnel.com/api/v1/bills?ga=91&per_page=5"
const res = await fetch("https://iowafunnel.com/api/v1/bills?ga=91&per_page=5", { headers: { "X-Api-Key": "YOUR_KEY" } });
const { data, meta } = await res.json();
console.log(meta.total, data[0].number, data[0].title);
{
  "data": [{ "ga": 91, "number": "HF 2", "title": "...", "status": "...", "page": "https://iowafunnel.com/bills/91/HF2" }],
  "meta": { "source": { "name": "Iowa Legislature", "url": "https://www.legis.iowa.gov" },
            "as_of": "2026-11-13T19:03:41Z", "page": 1, "per_page": 5, "total": 2210,
            "next": "/api/v1/bills?ga=91&page=2&per_page=5" },
  "attribution": "Iowa Funnel (iowafunnel.com), from public records"
}

Endpoints

All start with https://iowafunnel.com/api/v1.

  • GET /bills
    Bills in a session or General Assembly, newest first. Options: ga, session, topic, status, page, per_page.
  • GET /bills/:ga/:number
    One bill: its title, status, committee and dates.
  • GET /bills/:ga/:number/actions
    Every step in a bill’s history.
  • GET /bills/:ga/:number/votes
    A bill’s House and Senate floor votes.
  • GET /votes
    Floor votes in a session, newest first. Options: ga, session, chamber, page, per_page.
  • GET /votes/:id
    One floor vote, with how each member voted.
  • GET /legislators
    Members now serving (or, with elect=1, the members-elect). Options: chamber, elect.
  • GET /legislators/:id
    One legislator: seats held by Assembly, and committees.
  • GET /committees
    House, Senate and joint committees.
  • GET /committees/:id
    One committee and its members.
  • GET /places/:type
    Counties, cities or school districts (type: counties, cities, schools), each with its newest money figures. Options: page, per_page.
  • GET /places/:type/:key
    One county (by slug, like polk), city (by state code, like 77G021) or school district (by code, like 1737), every figure by year.
  • GET /elections
    Election dates, newest first, with how many races each had.
  • GET /elections/:date/races
    Every federal, statewide, legislative and judicial race on a date, with candidates and votes. Options: page, per_page.
  • GET /races/:id
    One race (or ballot question) and its results.
  • GET /campaign-money
    Money raised, spent and on hand by candidates for Congress and statewide office (totals only).
  • GET /campaign-money/pacs
    Each PAC’s money raised, spent and given to other committees in a year. Options: year, page, per_page.
  • GET /budget
    General Fund budget lines by year (actual, enacted, estimated or the Governor’s). Options: fy, area, page, per_page.

Errors

Errors come back as { "error": { "status": 401, "message": "..." } }: 401 for a missing or revoked key, 404 when nothing’s at that address, 429 past the hourly limit, and 503 when the database is busy (try again shortly).