TfNSW Trip Planner — Python Client
A clean, idiomatic Python library for the Transport for NSW Trip Planning APIs.
Installation
pip install requests # only external dependency
Copy the tfnsw_trip_planner/ package into your project, then import it:
from tfnsw_trip_planner import TripPlannerClient
Getting an API Key
The API key is free. You get one from the TfNSW Open Data portal:
- Create an account at https://opendata.transport.nsw.gov.au/data/user/register (or log in if you already have one).
- Create an Application: from your account menu open Applications → Create Application, give it a name, and select the API products you need. At minimum add Trip Planner. For live vehicle positions (example 11) also add Public Transport – Realtime Vehicle Positions.
- Copy the API key shown for that application. The same key authenticates every endpoint in this library.
Note: Each API product must be added to your application separately. If a call returns
401/403, the most common cause is that the relevant product (e.g. Realtime Vehicle Positions) isn't enabled on your key yet.
Pass the key when constructing the client:
client = TripPlannerClient(api_key="YOUR_API_KEY")
Avoid hard-coding the key in source — read it from the environment instead:
import os
client = TripPlannerClient(api_key=os.environ["TFNSW_API_KEY"])
Quick Start
from tfnsw_trip_planner import TripPlannerClient
client = TripPlannerClient(api_key="YOUR_API_KEY")
Don't have a key yet? See Getting an API Key above.
Examples
1. Find a Stop
locations = client.find_stop("Circular Quay")
for loc in locations:
print(loc.name, loc.id, loc.coord)
# Get the best single match
best = client.best_stop("Domestic Airport")
print(best.id, best.name)
2. Plan a Trip
from datetime import datetime
journeys = client.plan_trip(
origin_id="10101331", # Domestic Airport Station
destination_id="10102027", # Manly Wharf
)
for journey in journeys:
print(journey)
# Journey(legs=3, duration=61min, summary='Train → Walk → Bus')
print(f" Depart : {journey.departure_time}")
print(f" Arrive : {journey.arrival_time}")
print(f" Route : {journey.summary}")
fare = journey.fare_summary
if fare:
print(f" Cost : ${fare.price_total:.2f} ({fare.status.value})")
3. Arrive By a Specific Time
from datetime import datetime
# Plan a trip that arrives by 6 PM today
arrive_by = datetime.now().replace(hour=18, minute=0)
journeys = client.plan_trip(
origin_id="10101331",
destination_id="10102027",
when=arrive_by,
arrive_by=True,
)
4. Directions from GPS Location
journeys = client.plan_trip_from_coordinate(
latitude=-33.884080,
longitude=151.206290,
destination_id="10102027",
)
5. Wheelchair-Accessible Trips Only
journeys = client.plan_trip(
origin_id="10101331",
destination_id="10102027",
wheelchair=True,
)
for journey in journeys:
for leg in journey.legs:
print(f" {leg.transportation.number}")
print(f" Low-floor vehicle : {leg.low_floor_vehicle}")
print(f" Wheelchair accessible : {leg.wheelchair_accessible_vehicle}")
print(f" Origin accessible : {leg.origin.wheelchair_access}")
print(f" Destination accessible: {leg.destination.wheelchair_access}")
6. Cycling Trip
from tfnsw_trip_planner import CyclingProfile
journeys = client.plan_cycling_trip(
origin_id="10101331",
destination_id="10102027",
profile=CyclingProfile.MODERATE,
bike_only=True,
)
7. Upcoming Departures (Departure Board)
departures = client.get_departures("10101331") # Domestic Airport
for event in departures:
mins = event.minutes_until_departure
rt = "⚡" if event.is_realtime else "🕐"
print(f"{rt} {mins:>3}m {event.transportation.number:>8} → {event.transportation.destination_name}")
# From a specific platform only
departures = client.get_departures("10101331", platform_id="202091")
8. Travel in Cars (Train Car Guidance)
departures = client.get_departures("10101331")
for event in departures:
for tic in event.travel_in_cars():
print(
f"Train has {tic.number_of_cars} cars. "
f"Board cars {tic.from_car}–{tic.to_car} ({tic.message})"
)
9. Service Alerts
alerts = client.get_alerts()
for alert in alerts:
print(alert.subtitle)
print(f" Affected stops: {len(alert.affected_stops)}")
print(f" Affected lines: {len(alert.affected_lines)}")
# Alerts for a specific stop
alerts = client.get_alerts(stop_id="10111010") # Central Station
10. Nearby Stops / Opal Resellers
# Stops within 500m
nearby = client.find_nearby(latitude=-33.884080, longitude=151.206290, radius_m=500)
for loc in nearby:
print(loc.name, loc.properties.get("distance"), "m")
# Opal resellers within 1km
resellers = client.find_opal_resellers(latitude=-33.884080, longitude=151.206290)
for r in resellers:
print(r.name, r.coord)
11. Live Vehicle Positions (GTFS-Realtime)
Get the exact GPS location of vehicles in real time (latitude/longitude, bearing, speed) — as opposed to the real-time timing estimates returned by the trip/departure endpoints.
This uses the separate GTFS-Realtime Vehicle Positions feed. You must subscribe to that API product on the Open Data portal (the same API key is used), and install the optional dependency:
pip install tfnsw-trip-planner[realtime]
# mode is appended to the vehiclepos/ endpoint — see client.VEHICLE_POSITION_MODES
buses = client.vehicle_positions("buses")
for v in buses[:5]:
print(f"{v.vehicle_id} route={v.route_id} "
f"{v.latitude:.5f},{v.longitude:.5f} "
f"bearing={v.bearing} @ {v.timestamp}")
# Other feeds: "nswtrains", "metro", "ferries/sydneyferries",
# "lightrail/cbdandsoutheast", "lightrail/newcastle", ...
ferries = client.vehicle_positions("ferries/sydneyferries")
Filtering by route. Each feed returns every vehicle for that mode (the
buses feed is ~1,000 vehicles), so filter client-side. route_id is formatted
<prefix>_<routeShortName> (e.g. 2510_992), so match on the suffix:
buses = client.vehicle_positions("buses")
on_992 = [b for b in buses if b.route_id and b.route_id.split("_")[-1] == "992"]
Vehicles without a current GPS fix report
latitude/longitudeof0.0— filter those out if you're plotting positions.
Using as a Context Manager
with TripPlannerClient(api_key="YOUR_KEY") as client:
journeys = client.plan_trip("10101331", "10102027")
API Reference
TripPlannerClient
| Method | Description |
|---|---|
find_stop(query, ...) |
Search stops/POIs by name |
find_stop_by_id(stop_id) |
Look up a stop by its ID |
best_stop(query) |
Return the top-matching stop |
plan_trip(origin_id, destination_id, ...) |
Plan a journey |
plan_trip_from_coordinate(lat, lon, dest_id, ...) |
Trip from GPS coordinate |
plan_cycling_trip(origin_id, dest_id, ...) |
Cycling trip |
get_departures(stop_id, ...) |
Upcoming departures from a stop |
get_alerts(...) |
Service alerts |
find_nearby(lat, lon, ...) |
POIs near a coordinate |
find_opal_resellers(lat, lon, ...) |
Opal resellers near a coordinate |
vehicle_positions(mode) |
Live vehicle GPS positions (GTFS-Realtime) |
Key Models
| Model | Key Attributes |
|---|---|
Location |
id, name, type, coord, modes, is_best |
Journey |
legs, departure_time, arrival_time, total_duration, summary, fare_summary |
Leg |
mode, origin, destination, duration, stop_sequence, coords, infos |
Stop |
id, name, departure_time, arrival_time, wheelchair_access |
Transport |
number, mode, destination_name |
StopEvent |
transportation, departure_time, is_realtime, minutes_until_departure |
Fare |
person, price_total, station_access_fee, status |
ServiceAlert |
subtitle, url, affected_stops, affected_lines |
TravelInCars |
number_of_cars, from_car, to_car, message |
VehiclePosition |
vehicle_id, route_id, trip_id, latitude, longitude, bearing, speed, timestamp, coord |
CyclingProfile Enum
| Value | Description |
|---|---|
CyclingProfile.EASIER |
Avoids hills and busy roads |
CyclingProfile.MODERATE |
Intermediate — occasional hills |
CyclingProfile.MORE_DIRECT |
Fastest route, steeper hills allowed |
TransportMode Enum
TRAIN, LIGHT_RAIL, BUS, COACH, FERRY, SCHOOL_BUS, WALK, CYCLE, ON_DEMAND
Error Handling
from tfnsw_trip_planner import APIError, NetworkError
try:
journeys = client.plan_trip("10101331", "10102027")
except NetworkError as e:
print("Network problem:", e)
except APIError as e:
print(f"API error {e.status_code}:", e)
Metadata
Release files for tfnsw-trip-planner 1.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| tfnsw_trip_planner-1.4.0.tar.gz | 28.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| tfnsw_trip_planner-1.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 52.1 kB
Release files / tfnsw_trip_planner-1.4.0.tar.gz
| Download URL | tfnsw_trip_planner-1.4.0.tar.gz |
|---|---|
| Size | 28.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
afb426c7efc994051b9ddef45874627aac7d2a13836337106af4f9ecb4b4779b
|
|
BLAKE2b-256 checksum How to use checksums |
69ace4e4717dc4353cae51db7b35b7bc55e3e7b576ef57e9c73359758ef2f319
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 29, 2026.
Transparency logRelease files / tfnsw_trip_planner-1.4.0-py3-none-any.whl
| Download URL | tfnsw_trip_planner-1.4.0-py3-none-any.whl |
|---|---|
| Size | 23.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9fe3b205c05b583fbc18136909bcf497b04e6fa16b1fd2742a58dbc5b3a9655c
|
|
BLAKE2b-256 checksum How to use checksums |
fccf11412502b2099ac1ed54fe692a7701e3e4c872a241fcdefc0bbe1576bc2d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 29, 2026.
Transparency log