Este guia está disponível em inglês.
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.
Em resumo
- O que este guia realiza
- Assemble a portfolio dashboard where each panel is one rollup call, and a per-location table joins them.
- APIs utilizadas
- get
/api/v1/listings/summaryObter um resumo de listagens - get
/api/v1/reviews/summaryObter resumo de avaliações - get
/api/v1/seo/rollupObter o resumo de classificação de todos os locais - get
/api/v1/aeo/rollupObter o resumo AEO de todos os locais - get
/api/v1/analytics/summaryObter um resumo de análise de perfil - get
/api/v1/listings/duplicates/rollupObter o resumo de listagens duplicadas - get
/api/v1/connections/summaryObter um resumo de contas conectadas - get
/api/v1/clients/summaryObter resumo do cliente
- get
- Referência
- Listagens publicadasReviewsSEOAEOAnálise de perfilDuplicate ListingsConnectionsClients
- Pré-requisitos
- An API key
- A client with locations already set up and reporting
- Casos de uso típicos
- 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:
| Panel | Call | Gives you |
|---|---|---|
| Listing health | GET /api/v1/listings/summary | Synced-versus-total publishers, issues, a fixFirst list |
| Reviews | GET /api/v1/reviews/summary | Rating, volume, response rate, best and worst locations |
| Local rank | GET /api/v1/seo/rollup | Average rank and top-three percentage per location |
| AI visibility | GET /api/v1/aeo/rollup | Per-engine AEO scores and systemic fixes |
| Profile analytics | GET /api/v1/analytics/summary | Views, calls, directions, a per-location leaderboard |
| Duplicates | GET /api/v1/listings/duplicates/rollup | Potential duplicates by band and publisher |
| Connections | GET /api/v1/connections/summary | How 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.