GuideGuides/Construire avec Synup/Multi-location dashboard

Ce guide est disponible en anglais.

How do I build a multi-location marketing dashboard with the API?

How to Build a Multi-Location Marketing Dashboard

Build a multi-location marketing dashboard from the API's rollup endpoints, one call per panel, covering listings, reviews, rank, AI visibility and duplicates.

En bref

Ce que ce guide permet
Assemble a portfolio dashboard where each panel is one rollup call, and a per-location table joins them.
API utilisées
Référence
Fiches publiéesReviewsSEOAEOStatistiques de profilFiches en doubleConnexionsClients
Prérequis
  • An API key
  • A client with locations already set up and reporting
Cas d'usage typiques
  • One screen for a brand with hundreds of locations
  • An agency overview across a client's locations
  • A prioritised "fix first" operational view

A multi-location dashboard is a fan-in. Each panel on the screen is one rollup call that already aggregates every location, so you are not looping over locations in the browser, you are placing a handful of summary calls and joining them by location id. Build it panel by panel.

One call per panel

Every domain has a rollup that returns the whole client at once. Wire each to a panel:

PanelCallGives you
Listing healthGET /api/v1/listings/summarySynced-versus-total publishers, issues, a fixFirst list
ReviewsGET /api/v1/reviews/summaryRating, volume, response rate, best and worst locations
Local rankGET /api/v1/seo/rollupAverage rank and top-three percentage per location
AI visibilityGET /api/v1/aeo/rollupPer-engine AEO scores and systemic fixes
Profile analyticsGET /api/v1/analytics/summaryViews, calls, directions, a per-location leaderboard
DuplicatesGET /api/v1/listings/duplicates/rollupPotential duplicates by band and publisher
ConnectionsGET /api/v1/connections/summaryHow many locations are connected to Google and Facebook

The single header number for the whole client is GET /api/v1/clients/summary: locations, reviews and rank in one small response.

Load the header, then the panels

# Header
curl "https://ai.synup.com/api/v1/clients/summary?clientId=CLIENT_ID" \
  -H "Authorization: Bearer $SYNUP_API_KEY"

# Panels, in parallel
curl "https://ai.synup.com/api/v1/listings/summary?clientId=CLIENT_ID&perPage=200" -H "Authorization: Bearer $SYNUP_API_KEY"
curl "https://ai.synup.com/api/v1/reviews/summary?clientId=CLIENT_ID"            -H "Authorization: Bearer $SYNUP_API_KEY"
curl "https://ai.synup.com/api/v1/seo/rollup?clientId=CLIENT_ID"                 -H "Authorization: Bearer $SYNUP_API_KEY"
curl "https://ai.synup.com/api/v1/aeo/rollup?clientId=CLIENT_ID"                 -H "Authorization: Bearer $SYNUP_API_KEY"
const API = "https://ai.synup.com/api/v1";
const headers = { Authorization: `Bearer ${process.env.SYNUP_API_KEY}` };

async function get(path) {
  const res = await fetch(`${API}${path}`, { headers });
  if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).error}`);
  return (await res.json()).data;
}

async function dashboard(clientId) {
  const q = `clientId=${clientId}`;
  const [header, listings, reviews, seo, aeo] = await Promise.all([
    get(`/clients/summary?${q}`),
    get(`/listings/summary?${q}&perPage=200`),
    get(`/reviews/summary?${q}`),
    get(`/seo/rollup?${q}`),
    get(`/aeo/rollup?${q}`),
  ]);
  return { header, listings, reviews, seo, aeo };
}
import os, requests
from concurrent.futures import ThreadPoolExecutor

API = "https://ai.synup.com/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['SYNUP_API_KEY']}"}

def get(path):
    r = requests.get(f"{API}{path}", headers=HEADERS, timeout=30)
    r.raise_for_status()
    return r.json()["data"]

def dashboard(client_id):
    q = f"clientId={client_id}"
    paths = [f"/clients/summary?{q}", f"/listings/summary?{q}&perPage=200",
             f"/reviews/summary?{q}", f"/seo/rollup?{q}", f"/aeo/rollup?{q}"]
    with ThreadPoolExecutor(max_workers=5) as pool:
        header, listings, reviews, seo, aeo = pool.map(get, paths)
    return {"header": header, "listings": listings, "reviews": reviews, "seo": seo, "aeo": aeo}

Join by location id, and lead with what to fix

Each rollup's per-location rows carry the same locationId, so a per-location table is a join across the panels: listing health, review response rate and average rank in one row. The summaries do the prioritising for you. getListingsSummary returns an insights.fixFirst list and a headline; getReviewsSummary names the best and worst locations; getAeoRollup returns systemicFixes that apply across many locations at once. Put those at the top. A dashboard that opens on "here are the three things to fix" beats one that opens on a wall of green.

Next

To make each customer's dashboard their own tenant, that is the white-label platform guide. Operating a single large account (bulk updates, tagging, sync at scale) is managing thousands of locations, and the per-domain detail behind each panel lives in the listings, reviews, rank and AEO guides.