Skip to main content

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:

  1. Create an account at https://opendata.transport.nsw.gov.au/data/user/register (or log in if you already have one).
  2. 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.
  3. 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/longitude of 0.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)

Source distribution for tfnsw-trip-planner 1.4.0
File Size Uploaded
tfnsw_trip_planner-1.4.0.tar.gz 28.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for tfnsw-trip-planner 1.4.0
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

This release

1.4.0 This release

2 release files

1.3.1

2 release files

1.3.0

2 release files

1.2.0

2 release files

1.0.6

2 release files

1.0.5

2 release files

1.0.4

2 release files

1.0.3

2 release files

1.0.1

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page