Calling Hong Kong Bus API for my morning commute

Calling Hong Kong Bus API for my morning commute
August 9, 2025

Calling Hong Kong Bus API for my morning commute

Final result - click here.

Hey everyone! Living in Hong Kong, my morning commute relies on catching the right bus from Tai Hong House to get to work. I frequently use Routes 720 and 720A, and waiting at the stop without knowing when the next bus arrives can be annoying. To make things easier, I built a simple web page that automatically shows real-time Estimated Time of Arrival (ETA) data for Routes 720 and 720A by default: https://www.vincent.taipei/morning-bus. It’s a straightforward tool that displays the next few buses, saving me from manually entering routes every time.

In this post, I’ll share my thought process for creating this page using the Hong Kong government’s open API and my Next.js setup. I also used Postman to find the correct bus stop ID. Here’s how I did it, keeping things simple and functional.

Step 1: Finding the Data – Exploring the Government API

The core of this project is real-time bus data. I found the perfect resource on Hong Kong’s open data portal: the Citybus real-time ETA dataset (https://data.gov.hk/en-data/dataset/ctb-eta-transport-realtime-eta). This API provides JSON data updated every minute, including:

  • Company ID: "CTB" for Citybus routes.
  • Endpoints: Supports queries for routes, stops, and ETAs (e.g., /v2/transport/citybus/eta/CTB/001313/720 for Route 720 at Tai Hong House).
  • Details: ETA times, destinations, and remarks (like delays).

The API is free, open, and doesn’t require keys, which made it easy to start. I noticed they’re pushing users to the V2 API, so I stuck with that to avoid issues.

Finding the Bus Stop ID with Postman

To use the API, I needed the stop ID for Tai Hong House. The government’s dataset includes a stop list endpoint (https://rt.data.gov.hk/v2/transport/citybus/stop), but manually sifting through thousands of stops wasn’t practical. Instead, I used Postman to explore the API and pinpoint the ID.

Here’s how I did it:

  1. Fetched All Stops: Sent a GET request to https://rt.data.gov.hk/v2/transport/citybus/stop in Postman. This returned a JSON list of all Citybus stops with IDs, names, and coordinates.
  2. Searched for Tai Hong House: Used Postman’s response viewer to search for “Tai Hong House.” I found the stop with ID 001313, which matched my location.
  3. Verified with ETA Endpoint: Tested the ETA endpoint (https://rt.data.gov.hk/v2/transport/citybus/eta/CTB/001313/720) to confirm it returned valid data for Route 720. This gave me confidence the ID was correct.

Postman was a lifesaver for quickly navigating the API’s structure without writing throwaway code.

Step 2: Creating a Relay API Endpoint in Next.js

Calling the government API directly from the frontend could lead to CORS issues or expose query details, so I set up a relay endpoint in my Next.js app. This endpoint fetches data from the Citybus API, processes it, and sends only what’s needed to the frontend.

This endpoint accepts company_id, stop_id, and route as query parameters, forwards them to the Citybus API, and returns the response. I kept it flexible to handle any route, though I default to CTB and 001313 for my stop.

Step 3: Building the Frontend Page

The page is a simple Next.js component that automatically queries ETAs for Routes 720 and 720A by default, so I don’t have to manually input them. It also supports checking other routes via a query parameter (e.g., /morning-bus?route=5B). I used Incremental Static Regeneration (ISR) to revalidate data every 60 seconds, with a client-side countdown for real-time refresh feedback.

Key features:

  • Default Routes: Automatically queries Routes 720 and 720A when visiting /morning-bus, so I don’t need to manually add ?route=720 or ?route=720A.
  • Client-Side Refresh: The AutoRefreshCountdown component runs client-side to show a countdown, enhancing the live feel without full page reloads.
  • Route Capitalization: The EtaBus component ensures route numbers are uppercase for consistency.

Things I Was Mindful Of

A few considerations shaped the project:

  • Hong Kong Timezone: The API returns ISO timestamps, so I used toLocaleTimeString with Asia/Hong_Kong to display times correctly.
  • Auto-Refresh: I used ISR (revalidate: 60) for server-side freshness and a client-side countdown for user feedback. This balances performance and real-time updates.
  • Route Flexibility: The query param support lets me check other routes easily, like /morning-bus?route=5B, while defaulting to 720 and 720A for convenience.
  • Error Handling: If no buses are scheduled, the page shows “No upcoming buses” to keep things clear.

Wrapping Up

This project was a quick win—built in less than an hour but now a daily go-to for my commute. By defaulting to Routes 720 and 720A, it’s ready to use the moment I open the page. It’s a great example of using open government data to solve a small but real problem. If you’re in Hong Kong, try tweaking the stop ID or route for your own needs. Let me know if you’ve built something similar for your commute!

Random Number: 0.5952533177822961