Recipe: replicate the Summary plugin from raw API calls
Rebuild the “nearest place per category, with travel time” widget from the REST API alone, so you control every pixel.
Why bother
The JS Summary plugin is great if its default table layout works for you. It doesn’t if you need:
- A completely different visual layout (cards, hex tiles, your own iconography).
- Server-side rendering — the listing page should ship with the summary HTML for SEO.
- Conditional rendering — only show certain categories, or merge them.
- Integration with a non-HTML output (PDF, e-mail, app).
What you need
- The property’s latitude and longitude.
- A Yatmo license key (header-based; server-side).
- One call to
/summaryper property — cached server-side for 7 days, so subsequent renders are cheap.
Fetch the data
Most integrations will do this server-side and embed the result in the listing page’s HTML.
curl -H 'LicenseKey: YOUR_KEY' \
'https://be.yatmo.com/summary?latitude=50.846714&longitude=4.352514&language=EN' \
> summary.json
// Node.js / server-side
const summary = await (await fetch(
`https://be.yatmo.com/summary?latitude=${lat}&longitude=${lng}&language=EN`,
{ headers: { LicenseKey: process.env.YATMO_KEY } }
)).json();
$ch = curl_init("https://be.yatmo.com/summary?latitude=$lat&longitude=$lng&language=EN");
curl_setopt($ch, CURLOPT_HTTPHEADER, ['LicenseKey: ' . getenv('YATMO_KEY')]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$summary = json_decode(curl_exec($ch), true);
curl_close($ch);
using var http = new HttpClient();
http.DefaultRequestHeaders.Add("LicenseKey", Environment.GetEnvironmentVariable("YATMO_KEY"));
var json = await http.GetStringAsync($"https://be.yatmo.com/summary?latitude={lat}&longitude={lng}&language=EN");
var summary = JsonSerializer.Deserialize<JsonElement>(json);
Render it your way
Walk the response and keep, per sub-category, the nearest place in the mode that suits it.
The response uses short field names: sc sub-categories, d places,
td travel data, tm the travel mode (1 driving,
2 walking, 3 cycling, 4 transit), hti whether
that mode has travel information. The labels l, ptdsl and
ttsl are already translated: show them, never compare them.
Plain JavaScript, runnable in Node or a browser, with the missing cases handled:
const MODE = { DRIVING: 1, WALKING: 2, CYCLING: 3, TRANSIT: 4 };
// The travel entry for one mode, or null when that mode is absent or has no information.
function travelIn(place, mode) {
return (place.td ?? []).find(t => t.tm === mode && t.hti) ?? null;
}
// One row per sub-category: the nearest place in the chosen mode, or nothing.
function summaryRows(summary, modeFor) {
const rows = [];
for (const category of summary.AvailableCategoriesAroundPosition ?? []) {
const mode = modeFor(Number(category.ct)); // e.g. walk to schools, drive to motorways
for (const sub of category.sc ?? []) {
let best = null;
for (const place of sub.d ?? []) { // not guaranteed sorted: compare yourself
const t = travelIn(place, mode);
if (t && (best === null || t.ptdd < best.t.ptdd)) best = { place, t };
}
if (best === null) continue; // empty sub-category, or no data in that mode
rows.push({
categoryLabel: sub.l, // translated, e.g. 'Nurseries'
placeName: best.place.n,
distanceLabel: best.t.ptdsl, // e.g. '257 m'
timeLabel: best.t.ttsl // e.g. '3 min'
});
}
}
return rows;
}
// One mode for every category here; return MODE.DRIVING for the ones you reach by car.
const rows = summaryRows(summary, ct => MODE.WALKING);
A place can lack a mode entirely, and an entry can exist with hti: false: both mean
“no information in that mode”. travelIn returns null for both, and the
row is skipped rather than shown with an empty or invented time. If you fall back to another
mode, say so on screen.
Going to production
- Cache the response on your side for 24h+. The endpoint is already cached server-side for 7 days, but caching at your end saves a hop.
- Handle sparse areas. Some coordinates (rural roads, water edges) return little data. Skip categories whose
scis empty, as the code above does. - Pick the travel mode per category, and say which one you show: “3 min on foot” and “3 min by car” are not the same promise.