{
  "name": "ElectedConnect Voting Record API",
  "version": "v1",
  "description": "Read-only access to federal roll call votes by member of Congress, and to the bills behind them. Free, unauthenticated, and rate limited. No API key required.",
  "endpoints": [
    {
      "method": "GET",
      "path": "/api/v1/members",
      "description": "Every tracked federal member. Filter with ?chamber=house|senate and ?state=CA.",
      "example": "https://electedconnect.com/api/v1/members?chamber=senate&state=CA"
    },
    {
      "method": "GET",
      "path": "/api/v1/members/:bioguideId/votes",
      "description": "One member’s roll call votes, newest first. Paginate with ?limit=1-100 and ?before=<nextBefore from the previous page>. `position` is Yea, Nay, Present or NotVoting; `date` is a full ISO 8601 timestamp.",
      "example": "https://electedconnect.com/api/v1/members/P000197/votes?limit=20"
    },
    {
      "method": "GET",
      "path": "/api/v1/bills",
      "description": "Bills, newest action first. Filter with ?level=federal|state and ?state=CA, and size with ?limit=1-500. Covers federal bills and the state legislation we track. Does not include officialSummary — ask for a single bill for that.",
      "example": "https://electedconnect.com/api/v1/bills?level=federal&limit=50"
    },
    {
      "method": "GET",
      "path": "/api/v1/bills/:billKey",
      "description": "One bill and every recorded floor vote on it, newest first. Procedural and amendment votes are included, not only passage votes. Federal bills carry officialSummary: the CRS summary verbatim, quoted rather than generated.",
      "example": "https://electedconnect.com/api/v1/bills/us-119-s-5271"
    },
    {
      "method": "GET",
      "path": "/api/v1/races",
      "description": "The 2026 federal race corpus, folded from FEC candidate filings. Accepts an optional office=senate|house filter, though the unfiltered URL is the one the site itself requests and therefore the one that is warm in the CDN. Federal races only — the FEC does not regulate gubernatorial or state contests, so those are absent rather than empty.",
      "example": "https://electedconnect.com/api/v1/races"
    },
    {
      "method": "GET",
      "path": "/api/v1/races/:raceKey",
      "description": "One race and everyone who has filed for it, plus the other races in the same state. Keys look like pa-house-07-2026 and vt-house-00-2026; at-large is 00. The incumbent/challenger label on a candidate is the FEC’s own, as filed.",
      "example": "https://electedconnect.com/api/v1/races/ak-senate-2026"
    },
    {
      "method": "GET",
      "path": "/api/v1/rollcalls/recent",
      "description": "The most recent roll calls of both chambers, merged and newest first, with every bill they reference and the latest federal digest. One request for a whole view.",
      "example": "https://electedconnect.com/api/v1/rollcalls/recent"
    },
    {
      "method": "GET",
      "path": "/members",
      "description": "Human-readable index of every member with a voting record, grouped by state. Returns HTML, not JSON — use /api/v1/members for data.",
      "example": "https://electedconnect.com/members"
    },
    {
      "method": "GET",
      "path": "/members/:bioguideId",
      "description": "Human-readable page for a member’s voting record, with per-member Open Graph tags. Returns HTML, not JSON — use the endpoints above for data. The former /share/votes/:bioguideId redirects here.",
      "example": "https://electedconnect.com/members/P000197"
    },
    {
      "method": "GET",
      "path": "/races/:raceKey",
      "description": "Human-readable page for one 2026 federal race — who has filed, and the sitting member’s recent votes. Returns HTML, not JSON — use /api/v1/races for data.",
      "example": "https://electedconnect.com/races/ak-senate-2026"
    }
  ],
  "pagination": {
    "style": "cursor",
    "how": "Follow pagination.nextBefore into ?before= until pagination.hasMore is false.",
    "cursor": "Treat nextBefore as opaque and echo it back. It is a timestamp and the roll call the page stopped at, joined by \"|\" — a timestamp alone cannot resume correctly where several roll calls share one, and those rows would be skipped silently. A bare ISO date is still accepted, with that caveat.",
    "why": "Not offset-based — new roll calls land at the front of the collection, so an offset into a growing set skips or repeats rows mid-crawl."
  },
  "errors": {
    "shape": "{ \"error\": { \"code\": \"…\", \"message\": \"…\" }, \"docs\": \"…\" }",
    "codes": {
      "400": "invalid_bioguide_id, invalid_limit, invalid_before, invalid_chamber, invalid_state",
      "404": "member_not_found",
      "405": "method_not_allowed",
      "429": "rate_limited — 60 requests/minute per IP; see Retry-After",
      "502": "upstream_error",
      "503": "not_configured, budget_exhausted — the server is shedding load to protect its read budget; see Retry-After"
    }
  },
  "caching": {
    "note": "Responses are CDN-cached with stale-while-revalidate. Please do not defeat this with cache-busting query strings, including cursors you assembled yourself rather than echoing — the data changes a few times a day at most, and the cache is what keeps this endpoint free to run.",
    "browserSeconds": {
      "index": 3600,
      "members": 3600,
      "votes": 300
    },
    "cdnSeconds": {
      "index": 86400,
      "members": 21600,
      "votes": 900
    }
  },
  "bulkAccess": "Need a full export rather than a paginated crawl of every member? Ask instead of scraping — it is a better shape for both of us.",
  "licensing": "Underlying vote data is US federal government work and is in the public domain. Attribution to ElectedConnect is appreciated but not required.",
  "coverage": {
    "chambers": {
      "house": "Sourced from the Congress.gov /house-vote API, which is beta: it covers only the 118th and 119th Congresses, and only votes tied to a piece of legislation. Speaker elections and some procedural votes are absent.",
      "senate": "Parsed from senate.gov XML, as no Senate votes API exists. This is scraped rather than contracted and can change shape without notice."
    },
    "missingPositions": "Roll calls where the member has no recorded position are omitted, NOT reported as absences. A missing entry usually means the member was not yet seated or could not be matched to the roll call, so treating these as skipped votes would overstate absenteeism. Use `tallies.notVoting` for the chamber-wide count of members who did not vote.",
    "freshness": "Vote ingestion runs every four hours. Responses are cached at the CDN and may be up to 15 minutes stale.",
    "docs": "https://electedconnect.com/api/v1"
  },
  "docs": "https://electedconnect.com/api/v1"
}