Places Feedback

Overview

The Feedback API provides the ability for your end users to directly provide feedback about Foursquare POIs.

The API includes three endpoints that provide the following functionality:

  • Propose edits to an existing POI’s information such as address, phone number, hours of operation and more.
  • Provide Feedback about a problematic POI due to a variety of reasons such as closed, mislocated and more.
  • Monitor the status of the feedback items provided by your users.

Used together, these endpoints ensure that the POIs delivered to your end users accurately reflect the real world around them at all times.

Authentication

As the Feedback API ingests input from your end users (vs your application directly), these endpoints require the use of a service account on behalf of your application.

Once you generate a Service API Key within your Developer Console, you can authenticate against the API by passing this Service Account API Key through an Authorization header parameter like so Authorization: Bearer <token>

NOTE: A Service Account API key must be used to successfully access these endpoints. Using a Places API key to authenticate will result in an error.

Endpoints

${value.text.secondary}
`; } async function selectItem({ target }) { if (target.tagName === "BUTTON") { const link = target.dataset.object; const addressDetail = await fetchAddressDetails(link); const { location = {} } = addressDetail; const { address = "", country = "", postcode = "", locality = "", region = "", } = location; addressInput.value = address; address2Input.value = ""; countryInput.value = country; postcodeInput.value = postcode; cityInput.value = locality; regionInput.value = region; // generate new session token after a complete search sessionToken = generateRandomSessionToken(); address2Input && address2Input.focus(); dropDownField.style.display = "none"; } } async function fetchAddressDetails(link) { try { const results = await fetch(`https://api.foursquare.com${link}`, { method: "get", headers: new Headers({ Accept: "application/json", Authorization: fsqAPIToken, }), }); const data = await results.json(); return data; } catch (err) { logError(err); } } function highlightedNameElement(textObject) { if (!textObject) return ""; const { primary, highlight } = textObject; if (highlight && highlight.length) { let beginning = 0; let hightligtedWords = ""; for (let i = 0; i < highlight.length; i++) { const { start, length } = highlight[i]; hightligtedWords += primary.substr(beginning, start - beginning); hightligtedWords += "" + primary.substr(start, length) + ""; beginning = start + length; } hightligtedWords += primary.substr(beginning); return hightligtedWords; } return primary; } function debounce(func, timeout = 300) { let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => { func.apply(this, args); }, timeout); }; } } localAddressAutoFillJs(); } if (state && state.params && state.params.slug === "local-search-map") { function loadLocalMapSearchJs() { mapboxgl.accessToken = "pk.eyJ1IjoiZm91cnNxdWFyZSIsImEiOiJjbDNqNXdrN20wN3JtM2JvMWFqZGxoaGljIn0.uSxJ2t7E96TrBFsn3cXT_g"; const fsqAPIToken = "fsq3bgqdcpLAJFkodk8gisc2F+NenA7gK/zI97A9nKQAXIw="; let userLat = 40.7128; let userLng = -74.006; let sessionToken = generateRandomSessionToken(); const inputField = document.getElementById("explorer-search"); const dropDownField = document.getElementById("explorer-dropdown"); const ulField = document.getElementById("explorer-suggestions"); const errorField = document.getElementById("explorer-error"); const notFoundField = document.getElementById("explorer-not-found"); const onChangeAutoComplete = debounce(changeAutoComplete); inputField.addEventListener("input", onChangeAutoComplete); ulField.addEventListener("click", selectItem); function success(pos) { const { latitude, longitude } = pos.coords; userLat = latitude; userLng = longitude; flyToLocation(userLat, userLng); } function logError(err) { console.warn(`ERROR(${err.code}): ${err.message}`); } navigator.geolocation.getCurrentPosition(success, logError, { enableHighAccuracy: true, timeout: 5000, maximumAge: 0, }); const map = new mapboxgl.Map({ container: "map", style: "mapbox://styles/mapbox/light-v10", center: [userLng, userLat], zoom: 12, }); map.addControl(new mapboxgl.GeolocateControl()); map.addControl(new mapboxgl.NavigationControl()); let currentMarker; /* Generate a random string with 32 characters. Session Token is a user-generated token to identify a session for billing purposes. Learn more about session tokens. https://docs.foursquare.com/reference/session-tokens */ function generateRandomSessionToken(length = 32) { let result = ""; const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; for (let i = 0; i < length; i++) { result += characters[Math.floor(Math.random() * characters.length)]; } return result; } let isFetching = false; async function changeAutoComplete({ target }) { const { value: inputSearch = "" } = target; ulField.innerHTML = ""; notFoundField.style.display = "none"; errorField.style.display = "none"; if (inputSearch.length && !isFetching) { try { isFetching = true; const results = await autoComplete(inputSearch); if (results && results.length) { results.forEach((value) => { addItem(value); }); } else { notFoundField.innerHTML = `Foursquare can't find ${inputSearch}. Make sure your search is spelled correctly. Don't see the place you're looking for?.`; notFoundField.style.display = "block"; } } catch (err) { errorField.style.display = "block"; logError(err); } finally { isFetching = false; dropDownField.style.display = "block"; } } else { dropDownField.style.display = "none"; } } async function autoComplete(query) { const { lng, lat } = map.getCenter(); userLat = lat; userLng = lng; try { const searchParams = new URLSearchParams({ query, types: "place", ll: `${userLat},${userLng}`, radius: 50000, session_token: sessionToken, }).toString(); const searchResults = await fetch( `https://api.foursquare.com/v3/autocomplete?${searchParams}`, { method: "get", headers: new Headers({ Accept: "application/json", Authorization: fsqAPIToken, }), } ); const data = await searchResults.json(); return data.results; } catch (error) { throw error; } } function addItem(value) { const placeDetail = value[value.type]; if (!placeDetail || !placeDetail.geocodes || !placeDetail.geocodes.main) return; const { latitude, longitude } = placeDetail.geocodes.main; const fsqId = placeDetail.fsq_id; const dataObject = JSON.stringify({ latitude, longitude, fsqId }); ulField.innerHTML += `
  • ${highlightedNameElement(value.text)}
    ${value.text.secondary}
  • `; } async function selectItem({ target }) { if (target.tagName === "LI") { const valueObject = JSON.parse(target.dataset.object); const { latitude, longitude, fsqId } = valueObject; const placeDetail = await fetchPlacesDetails(fsqId); addMarkerAndPopup(latitude, longitude, placeDetail); flyToLocation(latitude, longitude); // generate new session token after a complete search sessionToken = generateRandomSessionToken(); const name = target.dataset.name; inputField.value = target.children[0].textContent; dropDownField.style.display = "none"; } } async function fetchPlacesDetails(fsqId) { try { const searchParams = new URLSearchParams({ fields: "fsq_id,name,geocodes,location,photos,rating", session_token: sessionToken, }).toString(); const results = await fetch( `https://api.foursquare.com/v3/places/${fsqId}?${searchParams}`, { method: "get", headers: new Headers({ Accept: "application/json", Authorization: fsqAPIToken, }), } ); const data = await results.json(); return data; } catch (err) { logError(err); } } function createPopup(placeDetail) { const { location = {}, name = "", photos = [], rating } = placeDetail; let photoUrl = "https://files.readme.io/c163d6e-placeholder.svg"; if (photos.length && photos[0]) { photoUrl = `${photos[0].prefix}56${photos[0].suffix}`; } const popupHTML = `
    photo of ${name}
    ${name}
    ${location.address}
    ${ rating ? `
    ${rating}
    ` : `
    ` }
    `; const markerHeight = 35; const markerRadius = 14; const linearOffset = 8; const verticalOffset = 8; const popupOffsets = { top: [0, verticalOffset], "top-left": [0, verticalOffset], "top-right": [0, verticalOffset], bottom: [0, -(markerHeight + verticalOffset)], "bottom-left": [ 0, (markerHeight + verticalOffset - markerRadius + linearOffset) * -1, ], "bottom-right": [ 0, (markerHeight + verticalOffset - markerRadius + linearOffset) * -1, ], left: [ markerRadius + linearOffset, (markerHeight - markerRadius) * -1, ], right: [ -(markerRadius + linearOffset), (markerHeight - markerRadius) * -1, ], }; return new mapboxgl.Popup({ offset: popupOffsets, closeButton: false, }).setHTML(popupHTML); } function addMarkerAndPopup(lat, lng, placeDetail) { if (currentMarker) currentMarker.remove(); currentMarker = new mapboxgl.Marker({ color: "#3333FF", }) .setLngLat([lng, lat]) .setPopup(createPopup(placeDetail)) .addTo(map); currentMarker.togglePopup(); } function flyToLocation(lat, lng) { map.flyTo({ center: [lng, lat], }); } function highlightedNameElement(textObject) { if (!textObject) return ""; const { primary, highlight } = textObject; if (highlight && highlight.length) { let beginning = 0; let hightligtedWords = ""; for (let i = 0; i < highlight.length; i++) { const { start, length } = highlight[i]; hightligtedWords += primary.substr(beginning, start - beginning); hightligtedWords += "" + primary.substr(start, length) + ""; beginning = start + length; } hightligtedWords += primary.substr(beginning); return hightligtedWords; } return primary; } function debounce(func, timeout = 300) { let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => { func.apply(this, args); }, timeout); }; } } loadLocalMapSearchJs(); } }); $(window).on("pageLoad", function (e, state) { const openSearchPopup = () => { const headerSearch = document.getElementById("hub-search-results"); if (headerSearch) { headerSearch.classList.add("hub-search-results-active"); document .getElementsByClassName("Input Input_md SearchBox-InputUQZAW9QXMe-c")[0] ?.focus(); } }; const triggerSearchButton = document.getElementsByClassName( "landing-page-trigger-search-button" )[0]; if (triggerSearchButton) { triggerSearchButton.addEventListener("click", openSearchPopup); } }); // START: Add Segment Tracking to Try It button // TODO: Verify this runs on route changes, so button gets instrumented on every page $(window).on("load", function (e, state) { var tryItBtn = document.querySelector(".rm-TryIt"); // in case the button has not yet renedered if (tryItBtn === undefined) return; tryItBtn.addEventListener("click", () => { console.log("Try It button Clicked (project)"); analytics.track("Try It button Clicked"); }); }); // END: Add Segment Tracking to Try It button