Sample prompts and parsing recipes for the /near/<city>/snapshot.json endpoint. Each city snapshot exposes a bestTonight.tieBreak object and a closestSites[].tieBreak array — the exact metrics our 7-level ranking ladder uses, so agents can re-rank, filter, or explain decisions to users.
/near/<city>/snapshot.json/near/snapshot.jsonsnapshotMeta.tieBreakVersion + snapshotMeta.changelog[]tieBreak shapeEvery entry in closestSites[] and the top-level bestTonight contains the same tieBreak block:
{
"verdict": "Excellent" | "Good" | "Fair" | "Poor" | "No-go",
"verdictRank": 5 | 4 | 3 | 2 | 1,
"windowHours": 0, // hours in the longest sustained clear window tonight
"windowMeanCloudPct": 12, // mean cloud % inside that window (null if no window)
"tonightMinCloudPct": 8, // min cloud % across the whole night
"tonightMeanCloudPct": 35, // mean cloud % across the whole night
"bortle": 2, // site Bortle scale (1 = darkest, 9 = inner city)
"distanceKm": 66.5
}
Sort priority (descending importance) — also published as tieBreakLadder:
| # | Field | Direction | Why |
|---|---|---|---|
| 1 | verdictRank | desc | Excellent skies always beat Poor. |
| 2 | windowHours | desc | Longer clear windows = more usable time. |
| 3 | windowMeanCloudPct | asc | Cleaner skies inside the window. |
| 4 | tonightMinCloudPct | asc | Best moment of the night. |
| 5 | tonightMeanCloudPct | asc | Overall sky quality. |
| 6 | bortle | asc | Darker sites win at parity. |
| 7 | distanceKm | asc | Closer wins last. |
Recipe: Fetch /near/toronto/snapshot.json, read bestTonight, narrate tonight.verdictReason and tieBreak.windowHours.
const snap = await fetch("https://ontarionightsky.com/near/toronto/snapshot.json")
.then(r => r.json());
const b = snap.bestTonight;
console.log(
`Best bet: ${b.name} (${b.distanceKm} km, Bortle ${b.bortle}).`,
`${b.tonight.verdict} — ${b.tonight.verdictReason}`,
b.tieBreak.windowHours
? `Clear window: ${b.tieBreak.windowHours}h @ ${b.tieBreak.windowMeanCloudPct}% cloud.`
: `No sustained clear window tonight.`
);
const snap = await fetch("https://ontarionightsky.com/near/ottawa/snapshot.json")
.then(r => r.json());
const usable = snap.closestSites.filter(s => s.tieBreak.windowHours >= 3);
usable.sort((a, b) =>
b.tieBreak.windowHours - a.tieBreak.windowHours ||
a.tieBreak.windowMeanCloudPct - b.tieBreak.windowMeanCloudPct
);
const snap = await fetch("https://ontarionightsky.com/near/hamilton/snapshot.json")
.then(r => r.json());
const candidates = snap.closestSites
.filter(s => s.tieBreak.windowHours > 0)
.sort((a, b) =>
a.tieBreak.bortle - b.tieBreak.bortle || // darker first
b.tieBreak.windowHours - a.tieBreak.windowHours
);
const pick = candidates[0];
Walk the ladder. The first field where they differ is the answer.
function explain(a, b) {
const ladder = [
["verdict", (x, y) => y.verdictRank - x.verdictRank],
["window length", (x, y) => y.windowHours - x.windowHours],
["cleaner window", (x, y) => x.windowMeanCloudPct - y.windowMeanCloudPct],
["lower min cloud", (x, y) => x.tonightMinCloudPct - y.tonightMinCloudPct],
["lower mean cloud", (x, y) => x.tonightMeanCloudPct - y.tonightMeanCloudPct],
["darker (Bortle)", (x, y) => x.bortle - y.bortle],
["closer", (x, y) => x.distanceKm - y.distanceKm],
];
for (const [reason, cmp] of ladder) {
const d = cmp(a.tieBreak, b.tieBreak);
if (d !== 0) return `${a.name} won on ${reason}.`;
}
return "Tied across all metrics.";
}
const agg = await fetch("https://ontarionightsky.com/near/snapshot.json")
.then(r => r.json());
// Each city's `best.verdict` reflects its top-ranked nearby site.
const ranked = agg.cities
.filter(c => c.best?.verdict)
.sort((a, b) => {
const order = { Excellent: 5, Good: 4, Fair: 3, Poor: 2, "No-go": 1 };
return (order[b.best.verdict] || 0) - (order[a.best.verdict] || 0);
});
// For full tie-break detail, fetch the per-city snapshot:
const detail = await fetch(ranked[0].json).then(r => r.json());
Before parsing, check snapshotMeta.tieBreakVersion. If it changes, re-validate your assumptions against snapshotMeta.changelog[]:
const EXPECTED_TIE_BREAK = "2.0.0";
if (snap.snapshotMeta.tieBreakVersion !== EXPECTED_TIE_BREAK) {
const recent = snap.snapshotMeta.changelog
.filter(c => c.type === "tieBreak")[0];
console.warn(`Ladder changed → ${recent.version}: ${recent.summary}`);
}
| Path | Type | Notes |
|---|---|---|
bestTonight.tieBreak | object | Metrics for the chosen site. |
closestSites[].tieBreak | object | Same shape, one per site. |
tieBreakLadder | string[] | Human-readable sort order. |
snapshotMeta.tieBreakVersion | semver | Bumps when the ladder changes. |
snapshotMeta.metricsVersion | semver | Bumps when fields are added/removed. |
snapshotMeta.changelog[] | array | {version, date, type, summary} |