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:

What you need

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