These endpoints allow you to create queries and connections to your databases.
Data connectors let you perform queries against your databases directly from Studio without saving or caching them to Studio. Studio supports several popular databases; learn more here .
Create a query-based dataset without executing the query.
List all data connectors added to the authorized account.
List all data connectors added to your organization.
${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 = ``;
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
// CUSTOM LAUNCH TOP NAV CODE - README
document.addEventListener('DOMContentLoaded', () => {
setTimeout(() => {
document.querySelectorAll('.Header-leftADQdGVqx1wqU a[href]').forEach(a => {
a.addEventListener('click', e => {
e.preventDefault();
window.location.href = a.href;
});
});
}, 100);
});
// CUSTOM LAUNCH SIDEBAR CODE (FINAL project: inject Places first, then Users under Welcome)
// Runs after a short delay to avoid flicker or unwanted auto-expansion.
document.addEventListener("DOMContentLoaded", function () {
// Delay entire injection by 1 second
setTimeout(() => {
(async function() {
// Normalize path (strip trailing slash)
const path = window.location.pathname.replace(/\/$/, '');
// Only act on the FINAL project paths
if (!path.startsWith('/developer/reference/')) {
return;
}
// Pages to pull from: Places API overview and Users API overview
// **Places first**, then Users
const toInject = [
{
otherPageUrl: '/fsq-developers-places/reference/places-api-overview',
targetSectionText: 'Places API'
},
{
otherPageUrl: '/fsq-developers-users/reference/users-api-overview',
targetSectionText: 'Users API'
}
];
// If versioned (e.g. /v2023-05-01/ in URL), insert version segment into otherPageUrl
const versionMatch = window.location.pathname.match(/\/(v\d{4}-\d{2}-\d{2})\//);
function applyVersion(url) {
if (versionMatch) {
const version = versionMatch[1];
return url.replace('/reference/', `/${version}/reference/`);
}
return url;
}
// Fetch & cache a single section's HTML from another page
async function fetchSectionHtml(otherPageUrl, targetSectionText) {
const versionedUrl = applyVersion(otherPageUrl);
const cacheKey = `crossSection:${versionedUrl}:${targetSectionText}`;
const cached = sessionStorage.getItem(cacheKey);
if (cached) {
return cached;
}
try {
const resp = await fetch(versionedUrl, { credentials: 'same-origin' });
if (!resp.ok) {
console.warn('[CrossInject] Fetch failed', resp.status, versionedUrl);
return null;
}
const htmlText = await resp.text();
// Parse to DOM
const parser = new DOMParser();
const doc = parser.parseFromString(htmlText, 'text/html');
// Find the sidebar section
const sidebarSelector = '.rm-Sidebar';
const otherSidebar = doc.querySelector(sidebarSelector);
if (!otherSidebar) {
console.warn('[CrossInject] Sidebar not found for', versionedUrl);
return null;
}
// Locate the
whose includes targetSectionText
let foundSection = null;
otherSidebar.querySelectorAll('section').forEach(sec => {
if (foundSection) return;
const h2 = sec.querySelector('h2');
if (h2 && h2.textContent.trim().includes(targetSectionText)) {
foundSection = sec;
}
});
if (!foundSection) {
console.warn('[CrossInject] Section not found:', targetSectionText, 'in', versionedUrl);
return null;
}
const sectionHtml = foundSection.outerHTML;
try {
sessionStorage.setItem(cacheKey, sectionHtml);
} catch (_) {}
return sectionHtml;
} catch (err) {
console.error('[CrossInject] Error fetching/parsing:', err);
return null;
}
}
// Wait until the current page's sidebar is present
function whenSidebarReady(fn) {
const interval = setInterval(() => {
const sidebarNav = document.querySelector('.rm-Sidebar');
if (sidebarNav) {
clearInterval(interval);
fn(sidebarNav);
}
}, 100);
// Stop polling after 5s
setTimeout(() => clearInterval(interval), 5000);
}
// Collapse helper for a section element
function collapseSection(sec) {
const h2 = sec.querySelector('h2');
const ul = sec.querySelector('ul');
if (h2) {
h2.classList.remove('section-expanded');
h2.classList.add('section-collapsed');
h2.setAttribute('aria-expanded', 'false');
}
if (ul) {
ul.classList.add('section-collapsed');
ul.classList.remove('section-list-expanded');
}
}
// Expand helper
function expandSection(sec) {
const h2 = sec.querySelector('h2');
const ul = sec.querySelector('ul');
if (h2) {
h2.classList.remove('section-collapsed');
h2.classList.add('section-expanded');
h2.setAttribute('aria-expanded', 'true');
}
if (ul) {
ul.classList.remove('section-collapsed');
ul.classList.add('section-list-expanded');
}
}
// Fetch all needed sections in parallel
const results = await Promise.all(
toInject.map(item =>
fetchSectionHtml(item.otherPageUrl, item.targetSectionText)
.then(html => ({ ...item, sectionHtml: html }))
)
);
const validSections = results.filter(r => r.sectionHtml);
if (validSections.length === 0) {
return;
}
// Once our sidebar is ready, inject under "Welcome"
whenSidebarReady(sidebarNav => {
const contentDiv = sidebarNav.querySelector('.hub-sidebar-content');
if (!contentDiv) {
console.warn('[CrossInject] .hub-sidebar-content not found; abort insertion.');
return;
}
// Find "Welcome" section
const welcomeSection = Array.from(contentDiv.querySelectorAll('section')).find(sec => {
const h2 = sec.querySelector('h2');
return h2 && h2.textContent.trim() === 'Welcome';
});
let insertAfter = welcomeSection;
validSections.forEach(({ targetSectionText, sectionHtml }) => {
const wrapper = document.createElement('div');
wrapper.innerHTML = sectionHtml;
const originalSectionNode = wrapper.firstElementChild;
if (!originalSectionNode) {
console.warn('[CrossInject] No section node for', targetSectionText);
return;
}
const sectionClone = originalSectionNode.cloneNode(true);
// Adapt heading & toggle as needed:
const injectedH2 = sectionClone.querySelector('h2');
const injectedUL = sectionClone.querySelector('ul');
if (injectedH2 && injectedUL) {
// Copy classes from an existing template if present
const templateH2 = sidebarNav.querySelector('.hub-sidebar-content section h2');
if (templateH2) {
injectedH2.className = templateH2.className;
} else {
injectedH2.classList.add('Sidebar-headingTRQyOa2pk0gh', 'rm-Sidebar-heading');
}
injectedH2.setAttribute('tabindex', '0');
// Remove existing chevrons, clone from template or fallback
Array.from(injectedH2.querySelectorAll('.icon-chevron')).forEach(el => el.remove());
if (templateH2) {
templateH2.querySelectorAll('.icon-chevron').forEach(iconSpan => {
injectedH2.appendChild(iconSpan.cloneNode(true));
});
} else {
const fallbackSpan = document.createElement('span');
fallbackSpan.className = 'icon-chevron';
injectedH2.appendChild(fallbackSpan);
}
// Initially collapsed
collapseSection(sectionClone);
// Toggle handler
const toggleFn = () => {
const isCollapsed = injectedUL.classList.contains('section-collapsed');
if (isCollapsed) {
expandSection(sectionClone);
} else {
collapseSection(sectionClone);
}
};
injectedH2.style.cursor = 'pointer';
injectedH2.addEventListener('click', toggleFn);
injectedH2.addEventListener('keydown', e => {
if (e.key === 'Enter' || e.key === ' ' || e.key === 'Spacebar') {
e.preventDefault();
toggleFn();
}
});
}
// Nested toggles
sectionClone.querySelectorAll('a.Sidebar-link_parent').forEach(aParent => {
let btn = aParent.querySelector('button');
if (!btn && aParent.nextElementSibling && aParent.nextElementSibling.tagName === 'BUTTON') {
btn = aParent.nextElementSibling;
}
let subUl = null;
if (btn) {
const maybeUl = btn.parentElement.nextElementSibling;
if (maybeUl && maybeUl.classList.contains('subpages')) subUl = maybeUl;
}
if (!subUl) {
const parentLi = aParent.closest('li');
if (parentLi) {
const maybe = parentLi.querySelector('ul.subpages');
if (maybe) subUl = maybe;
}
}
if (btn && subUl) {
// collapsed by default
btn.setAttribute('aria-expanded', 'false');
subUl.classList.add('section-collapsed');
subUl.classList.remove('section-list-expanded');
btn.style.cursor = 'pointer';
btn.addEventListener('click', e => {
e.preventDefault();
const expanded = btn.getAttribute('aria-expanded') === 'true';
if (expanded) {
btn.setAttribute('aria-expanded', 'false');
subUl.classList.add('section-collapsed');
subUl.classList.remove('section-list-expanded');
} else {
btn.setAttribute('aria-expanded', 'true');
subUl.classList.remove('section-collapsed');
subUl.classList.add('section-list-expanded');
}
});
}
});
// Insert right after Welcome (Places first, then Users)
if (insertAfter && insertAfter.parentElement === contentDiv) {
insertAfter.insertAdjacentElement('afterend', sectionClone);
insertAfter = sectionClone;
} else {
contentDiv.appendChild(sectionClone);
}
console.log(`[CrossInject] Injected "${targetSectionText}" after Welcome.`);
});
// After injecting both, auto-expand whichever matches current path
function normalizeHref(href) {
try {
const url = new URL(href, window.location.origin);
return url.pathname.replace(/\/$/, '');
} catch {
return href.replace(/\/$/, '');
}
}
const normPath = window.location.pathname.replace(/\/$/, '');
// Clear any previous active marks:
contentDiv.querySelectorAll('a[aria-current]').forEach(a => {
a.removeAttribute('aria-current');
a.classList.remove('active');
});
// Find matches among injected links:
const allLinks = Array.from(contentDiv.querySelectorAll('a'));
allLinks.forEach(a => {
const href = a.getAttribute('href');
if (!href) return;
if (normalizeHref(href) === normPath) {
// mark active
a.setAttribute('aria-current', 'page');
a.classList.add('active');
// expand its parent section
let sec = a.closest('section');
if (sec) expandSection(sec);
// expand ancestors
let anc = a.parentElement;
while (anc && anc !== contentDiv) {
if (anc.tagName === 'UL' && anc.classList.contains('subpages')) {
anc.classList.remove('section-collapsed');
anc.classList.add('section-list-expanded');
const parentLi = anc.closest('li');
if (parentLi) {
const btn = parentLi.querySelector('button[aria-expanded]');
if (btn) btn.setAttribute('aria-expanded', 'true');
}
}
if (anc.tagName === 'SECTION') {
expandSection(anc);
}
anc = anc.parentElement;
}
}
});
// If none matched exactly, all remain collapsed.
});
})();
}, 600);
});