One-tap nearest-bus-stop arrival times for Singapore, wired up as a 4-step iOS Shortcut.
No stop-picking menus. No confirmation dialogs. No local file dependencies. Tap the Shortcut → it locates you → it tells you what's coming and when.
Singapore's official LTA DataMall API is great for looking up
arrival times at a bus stop you already know the code for — but it has no "find stops near
this coordinate" endpoint. BusStops only supports paging through the full ~5,000+ stop list;
BusArrival only accepts a known BusStopCode. There is no radius/lat-lng filter anywhere in
the current API surface.
So "what bus is coming, near me, right now" — the one query everyone actually wants — has to be built on top, client-side or server-side. This repo does it server-side, so the phone-side Shortcut can stay dead simple.
flowchart LR
A[iOS Shortcut Get Current Location] --> B[Cloudflare Worker]
B --> C[LTA BusStops API - fetch all stops, cached in KV]
B --> D[Find nearest N stops - haversine distance]
D --> E[LTA BusArrival API per nearest stop]
E --> F[Plain-text response]
F --> G[Shortcut: Show Result]
- The Shortcut grabs your current GPS location — that's it, that's the entire client-side logic.
- The Worker fetches (or reads from cache) the full LTA bus stop list, computes distance from your location to every stop, and keeps the nearest few (covers both directions of a road by default).
- It queries live arrival times for those stops and returns one clean plain-text block.
- The Shortcut displays it. No parsing, no menus, no taps beyond the initial one.
- Zero-selection UX — no "which stop did you mean" prompts
- No local storage / Files dependency — nothing breaks if iCloud Drive or Face ID locks get in the way (this was the actual motivation for building this — other Shortcuts out there that rely on cached local files can repeatedly hit permission prompts)
- KV-backed caching — the ~5,000-stop list is cached for 7 days (coordinates rarely change), so most requests skip the expensive full re-fetch
- Shared-secret access token — the endpoint isn't wide open to anyone who finds the URL
- Single file, no build step — deploy straight from the Cloudflare dashboard, no
npm installrequired (though awrangler.tomlis included if you prefer the CLI)
You'll need a free LTA DataMall API key (an Account Key, emailed to you after registering) and a free Cloudflare account.
- Cloudflare dashboard → Workers & Pages → Create application → Start with Hello World! → Deploy
- Click Edit code, select all, replace with the contents of
worker/bus-nearest-worker.js, then Deploy - Worker → Settings → Variables and Secrets → add:
LTA_API_KEY(Secret) — your LTA Account KeyACCESS_TOKEN(Secret) — any string you make up; this gates the endpoint
- (Recommended) Enable caching — see Caching below
- Copy your Worker URL, e.g.
https://your-worker.your-subdomain.workers.dev
npm install -g wrangler
wrangler login
wrangler secret put LTA_API_KEY
wrangler secret put ACCESS_TOKEN
wrangler deployEdit wrangler.toml first if you already have a Worker deployed via the dashboard and want to
manage it from the CLI instead — set name to match your existing Worker's name so this updates
it in place rather than creating a duplicate.
Bus stop coordinates barely ever change, so re-fetching all ~5,000 of them on every single request is wasteful. Enable KV caching:
- Dashboard → Workers & Pages → KV → Create a namespace (e.g.
bus-stops-cache) - Your Worker → Settings → Bindings → Add binding → KV Namespace — variable name
BUS_STOPS_KV, bind it to the namespace you just created - Redeploy
Cached data expires automatically after 7 days. To force a refresh sooner, call the endpoint with
&refresh=1.
This binding is optional — the Worker falls back to a live fetch every request if it's absent, so nothing breaks if you skip this step.
GET /?lat={latitude}&lon={longitude}&token={your ACCESS_TOKEN}
Optional: &refresh=1 to bypass the stop-list cache for this request.
Returns text/plain, nearest stops first, one arrival line per bus service. Example:
【Blk 272 43161】(135m)
173: 10min, 27min
【Blk 254 43169】(166m)
No arrival info
401 if the token doesn't match. 400 if lat/lon are missing or invalid. 502 if the stop
list couldn't be loaded (usually a bad or missing LTA_API_KEY).
See docs/ios-shortcut-setup.md for the full step-by-step guide.
- Nearest stops are picked purely by straight-line distance (currently top 4, to reasonably cover both directions of a road). There's no reliable, documented pattern in LTA's bus stop codes for identifying "the stop across the road" directly — distance is the sturdiest general approach available.
- No built-in rate limiting beyond the access-token gate. Cloudflare's dashboard-level Rate Limiting Rules (or a simple KV-based counter) would be a reasonable addition if this endpoint is ever exposed more broadly.
MIT — see LICENSE.