Google Civics API is the best solution. A single API hit gets you both Elected Officials and Districts.
You can get Districts by GPS coords (lat/lng) like so:
public async Task<GoogleCivicsResult> FetchAndParseUSDistrictAsync(
decimal lat, decimal lng, string address = null)
{
string googleApiKey = App.GoogleApiKey;
string reqAddr = Uri.EscapeDataString(address ?? lat + "," + lng);
string url = "https://www.googleapis.com/civicinfo/v2/representatives?alt=json&key={0}&address={1}";
url = String.Format(url, googleApiKey, reqAddr);
var req = new HttpWebRequestAsync(url);
var data = await req.GetStringAsync();
var result = JsonConvert.DeserializeObject<GoogleCivicsResult>(data);
return result;
}
public class GoogleCivicsResult
{
public Address normalizedInput { get; set; }
public Dictionary<string, Division> divisions { get; set; }
public Office[] offices { get; set; }
public Official[] officials { get; set; }
}
public class Address
{
public string line1 { get; set; }
public string line2 { get; set; }
public string city { get; set; }
public string state { get; set; }
public string zip { get; set; }
}
public class Division
{
public string name { get; set; }
public int[] officeIndices { get; set; }
public Office[] offices { get; set; }
}
public class Office
{
// President of the United States
// United States Senate
// United States House of Representatives FL-06
// Governor
// Lieutenant Governor
// Attorney General
// Sheriff
// County Judge
// Council Chair
public string name { get; set; }
// ocd-division/country:us/state:fl/cd:6
// ocd-division/country:us/state:fl
// ocd-division/country:us/state:fl/county:volusia
public string divisionId { get; set; }
// administrativeArea1
public string[] levels { get; set; }
// legislatorUpperBody
// legislatorLowerBody
// headOfGovernment
// deputyHeadOfGovernment
public string[] roles { get; set; }
public int[] officialIndices { get; set; }
}
public class Official
{
public string name { get; set; }
public Address[] address { get; set; }
public string party { get; set; }
public string[] phones { get; set; }
public string[] urls { get; set; }
public string photoUrl { get; set; }
public Social[] channels { get; set; }
public string[] emails { get; set; }
}
public class Social
{
public string type { get; set; }
public string id { get; set; }
}
https://developers.google.com/civic-information/docs/v2/representatives/representativeInfoByAddress
You can get similar for state-level info using OpenStates:
"http://openstates.org/api/v1/legislators/geo/?apikey={0}&lat={1}&long={2}";
Addenda about Google Civics API Going Down Frequently
The Google Civics API is vague on whether it supports lat/lng, and every few months or so, it will just stop supporting lat/lng for a month or so, until someone reports it and Google notices, or more specifically, the one employee there probably working on this in their volunteer time notices ("Chelan") and looks into fixing it:
https://groups.google.com/d/msg/google-civicinfo-api/0td48h7JxYA/Zhij3yQiCgAJ
If your service expects Civics API data it's going to crash, and if it requires Civics API data to be useful, it may not crash but it will be useless for months at a time. The most recent failure was the entire voting period for most states in 2018, so, that's quite the downtime.
You can play defense by explicitly reverse geocoding:
if (!(navigator && navigator.geolocation))
return geoFail();
navigator.geolocation.getCurrentPosition(function (position) {
var coords = position.coords;
var lat = coords.latitude;
var lng = coords.longitude;
if (!lat || !lng)
return geoFail();
var geocoder = new google.maps.Geocoder;
geocoder.geocode({ 'location': { lat: lat, lng: lng } },
function (r, status) {
if (status !== 'OK' || !r || !r.length) {
loadReps(lat, lng);
return;
}
var address = r[0].formatted_address;
$('#address').val(address);
loadReps(lat, lng, address);
});
}, geoFail);
So here, we go through the usual HTML5 ask for user's location, and if they say No/Block we show them the "Couldn't get your location" message (with geoFail). Then we've got the lat/lng, but, Google Civics API is erratic so we'll go ask Google explicitly for the address. That comes back as r[0].formatted_address, which in some answers Google recommends you show to the user to confirm the location they're looking up. So we put that in the address textbox and send it off via loadReps(lat, lng, address)
.
Note that it's possible the geocode call could fail (including because of a flaky internet connection), but, sometimes Google Civics API is fully operational, so, no need to deny the user a result yet in this scenario; the backend I wrote in this case can accept lat/lng and optional address, and tolerate the address missing. Then it tries to talk to Google Civics API, and, if it gets nothing, it needs to survive that and tell the user the Civics API is broken, sorry.
Historical Answer
The Congress API from Sunlight Foundation* is Federal-only - no state-level, if that matters to you. And [Sunlight Foundation is shutting down, because they don't want to be duplicating the efforts of GovTrack.
GovTrack's API* is more complete. Their /role
is closest to your question:
https://www.govtrack.us/developers/api#endpoint_role
It tells you all the current Senators and House members, and when their next election is. So you'd have a filtering burden.
For state-level stuff you want OpenStates.org, like:
http://openstates.org/api/v1/legislators/geo/?apikey={0}&lat={1}&long={2}
https://openstates.org/
*Bad news, everything is shutting down all of the time. Sunlight Foundation shutdown their API to avoid duplicating Govtrack's work. Govtrack is shutting down theirs now, to avoid duplicating ProPublica's work. But Propublica's doesn't seem to give you district from location at all:
https://projects.propublica.org/api-docs/congress-api/
So wait you might say, that makes no sense, because ProPublica's Represent site, which is transferred from NYTimes' shutdown Represent project, does let you map an address and get the district from the GPS coords. And they link to their API from that page. Well fooled you, cuz that page does not use their API. In fact, it uses the Sunlight Foundation's district finding API.
What a mess.