--- name: sg-transport description: "Singapore transport - bus arrival times, MRT service alerts, carpark availability, traffic. Uses LTA DataMall." metadata: { "openclaw": { "emoji": "๐Ÿš‡" } } --- # Singapore Transport Live bus, MRT and traffic data from LTA DataMall. ## Before anything works: a key Unlike the weather API, LTA needs one. It is free. 1. Register at https://datamall.lta.gov.sg/content/datamall/en/request-for-api.html 2. The AccountKey arrives by email 3. Store it as `LTA_ACCOUNT_KEY` โ€” never paste it into a message or a filename Every request sends it as the `AccountKey` header. ## What it does - **Bus arrivals** โ€” next three buses at a stop, with crowding - **Train alerts** โ€” live disruptions and advisories - **Carpark availability** โ€” free lots, useful before driving to a mall - **Travel times** โ€” expressway estimates ## How to run it ```bash # Next buses at a stop. Bus stop codes are the 5 digits on the pole. curl -s -H "AccountKey: $LTA_ACCOUNT_KEY" \ "https://datamall2.mytransport.sg/ltaodataservice/v3/BusArrival?BusStopCode=83139" \ | python3 -c " import json,sys,datetime d=json.load(sys.stdin) now=datetime.datetime.now(datetime.timezone.utc) for svc in d.get('Services',[]): mins=[] for k in ('NextBus','NextBus2','NextBus3'): t=svc.get(k,{}).get('EstimatedArrival') if not t: continue eta=datetime.datetime.fromisoformat(t) mins.append(max(0,round((eta-now).total_seconds()/60))) load={'SEA':'seats','SDA':'standing','LSD':'packed'}.get(svc['NextBus'].get('Load'),'') print(f\"Bus {svc['ServiceNo']:5} {', '.join(str(m)+' min' for m in mins) or 'no data':30} {load}\") " # Train disruptions right now curl -s -H "AccountKey: $LTA_ACCOUNT_KEY" \ "https://datamall2.mytransport.sg/ltaodataservice/TrainServiceAlerts" \ | python3 -c " import json,sys d=json.load(sys.stdin)['value'] print('Normal service' if d['Status']==1 else 'DISRUPTION') for m in d.get('Message',[]): print('-', m['Content']) " # Carpark availability, filtered by name curl -s -H "AccountKey: $LTA_ACCOUNT_KEY" \ "https://datamall2.mytransport.sg/ltaodataservice/CarParkAvailabilityv2" \ | python3 -c " import json,sys want='VIVOCITY' # change this for c in json.load(sys.stdin)['value']: if want.lower() in c['Development'].lower(): print(f\"{c['Development']:40} {c['AvailableLots']:5} lots\") " ``` ## Working notes - **Ask for the bus stop code** rather than guessing from a road name. It is printed on the pole and on the app; guessing sends someone to the wrong stop. - Arrival times come as absolute timestamps; convert to "in N minutes", which is what the person actually wants to know. - Crowding codes: `SEA` seats available ยท `SDA` standing only ยท `LSD` limited standing. Translate them โ€” the codes mean nothing to a rider. - Responses cap at 500 records; the carpark and bus-stop lists are paged with `?$skip=500`. - If the key is missing or rejected, say which and point at the registration page. Do not silently return nothing.