Gautrain API reverse engineering

Written by

in

Gautrain API reverse engineering

The iOS 12 update contained a very cool feature: Siri Shortcuts. This replaces the Workflows app. I was using Workflows to send my wife an SMS with my estimated time of arrival at home when I left work. With the new Shortcuts app I could have this all happen automatically, and it also supports reading from Web APIs.
The first part, however, is to figure out how to get data from the Gautrain website.

mitmproxy

mitmproxy or Man In The Middle Proxy is a piece of software you can run on your computer which will stand between a device and the internet and allow you to inspect what is going on. I might cover installation in another post, but basically I just followed instructions to get it going and then fired up the Gautrain app to see what it was doing.

Sequence of events with Gautrain App

First endpoint to check service is live

https://api.gautrain.co.za/user-service/api/0/mobile/isLive/1.0.0

Returns true or false.
The app then hits

https://api.gautrain.co.za/transport-api/api/0/agencies

And gets a whole list of agencies, including this one:

[
....
{
        "culture": "en",
        "description": null,
        "href": "https://platform.whereismytransport.com/api/agencies/edObkk6o-0WN3tNZBLqKPg",
        "id": "edObkk6o-0WN3tNZBLqKPg",
        "name": "Gautrain"
},
...
]

The important part here is "name": "Gautrain" and that id which is used later.
Also interesting is that they are clearly using whereismytransport.com, which is the first time I’ve heard of this service.
The app then asks ask for a list of stops:

https://api.gautrain.co.za/transport-api/api/0/stops/gautrain

The response is a list of stops like this one:

[
...
{
    "agency": {
        "culture": "en",
        "description": null,
        "href": "https://platform.whereismytransport.com/api/agencies/edObkk6o-0WN3tNZBLqKPg",
        "id": "edObkk6o-0WN3tNZBLqKPg",
        "name": "Gautrain"
    },
    "code": null,
    "geometry": {
        "coordinates": [
            28.23794,
            -25.74762
        ],
        "type": "Point"
    },
    "href": "https://platform.whereismytransport.com/api/stops/_rkqSHvRE0Scvbcsuy0EVw",
    "id": "_rkqSHvRE0Scvbcsuy0EVw",
    "modes": [
        "Rail",
        "Bus"
    ],
    "name": "Hatfield"
},
...
    {
    "agency": {
        "culture": "en",
        "description": null,
        "href": "https://platform.whereismytransport.com/api/agencies/edObkk6o-0WN3tNZBLqKPg",
        "id": "edObkk6o-0WN3tNZBLqKPg",
        "name": "Gautrain"
    },
    "code": null,
    "geometry": {
        "coordinates": [
            28.05693,
            -26.10858
        ],
        "type": "Point"
    },
    "href": "https://platform.whereismytransport.com/api/stops/jXU-OlvxukW8wfc7JeVeXw",
    "id": "jXU-OlvxukW8wfc7JeVeXw",
    "modes": [
        "Rail",
        "Bus"
    ],
    "name": "Sandton"
}
...
]

I’ve kept the important ones for me – I’m going from Hatfield to Sandton and back, so I need those coordinates.
Right, so the next thing they hit is

https://api.gautrain.co.za/transport-api/api/0/journey/create

This is the first request which has parameters. You can see that they are using the coordinates for Hatfield station and Sandton station as the start and end points.

{
    "geometry": {
        "coordinates": [
            [
                28.23794,
                -25.74762
            ],
            [
                28.05693,
                -26.10858
            ]
        ],
        "type": "MultiPoint"
    },
    "maxItineraries": 5,
    "omit": {
        "agencies": [
            "A1JHSPIg_kWV5XRHIepCLw",
            "CA3o3RuuGUmoDKfyAPNTsg",
            "NfBxKfzMA0exbwToc-7o2g",
            "XxDwxnin_Eu_T7_wBJIJRA",
            "Hwe1673sC0yuT6fyANnDZQ",
            "DcXaTl-5hkGRHKenAIi_5w",
            "WaasqFZwEEa5VqbIAJKsJQ",
            "wZfSY3o0LUGHiqeoAQDeIQ",
            "W7A63uTwIECgQvU9MxcCIw",
            "Ka2d4V1g3E65VqheANhvVw",
            "FTGTH38Tm0C5O6gAAQifSQ"
        ],
        "modes": []
    },
    "only": {
        "agencies": [
            "edObkk6o-0WN3tNZBLqKPg"
        ],
        "modes": []
    },
    "profile": "ClosestToTime",
    "time": null,
    "timeType": "DepartAfter"
}

So that list of agencies to omit is obviously from the original request. I’m hoping I don’t need that. For my purposes I just need to know when the next train will arrive and how long it will take to get to the destination.
The response from this request is quite huge, but the first bit shows the important parts:

{
    "agencies": null,
    "earliestDepartureTime": null,
    "geometry": {
        "coordinates": [
            [
                28.23794,
                -25.74762
            ],
            [
                28.05693,
                -26.10858
            ]
        ],
        "type": "MultiPoint"
    },
    "href": "https://platform.whereismytransport.com/api/journeys/ZPtEXRtgeEq1mqljAE4new",
    "id": "ZPtEXRtgeEq1mqljAE4new",
    "itineraries": [
        {
            "arrivalTime": "2018-09-22T05:29:52Z",
            "departureTime": "2018-09-22T04:56:20Z",
            "distance": {
                "unit": "m",
                "value": 51662
            },
            "duration": 2012,
    ...
    Lots more output

So when is the next train?

I’m only really interested in when the next train will arrive at Sandton and I don’t want to type all that stuff into a mobile app, I want a minimal version.

 Further investigation yields the simplified request:

{"geometry":
 {"coordinates":
  [[28.23794,-25.74762],
  [28.05693,-26.10858]],
  "type":"MultiPoint"},
  "profile":"ClosestToTime",
  "maxItineraries"5,
  "timeType":"DepartAfter",
  "time":null,
  "only":{"agencies":["edObkk6o-0WN3tNZBLqKPg"],
  "modes":[]}}

This is something I can put into the automation app eally easily. More info on that to follow.
For posterity, this is that call being checked via curl:

curl -d '{"geometry":{"coordinates":[[28.23794,-25.74762],[28.05693,-26.10858]],"type":"MultiPoint"},"profile":"ClosestToTime","maxItineraries":5,"timeType":"DepartAfter","only":{"agencies":["edObkk6o-0WN3tNZBLqKPg"]}}' -H "Content-Type: application/json" -X POST https://api.gautrain.co.za/transport-api/api/0/journey/create

Guess it’s also time to create an account at whereismytransport.com!

Comments

  1. Nico Avatar

    Does the Gautrain status feature in Google Maps transit options? If so, this can all be automated out the box with either Stringify or IFTTT.

  2. Carl Sandrock Avatar

    I was specifically trying to get something that would work with Siri Shortcuts as it has privileges on iOS like being able to send a text message automatically in the background. I know it's a bit different on Android, but neither IFTTT or Stringify apps can do that in iOS. Maybe it will get better and I won't need this anymore, but I've been able to solve quite a few problems on with Shortcuts now using this general technique.

  3. Unknown Avatar

    Will you be publishing a tutorial for us non-software engineer types?

  4. Carl Sandrock Avatar

    The easiest way to start is to download the shortcut I've uploaded to iCloud here: https://www.icloud.com/shortcuts/62d092ccdb584868ad58a9d3943eb7fb

    This one is hard-coded for Hatfield to Sandton, but you can see where the locations of the stations are put in by downloading the shortcut and looking at it in the Shortcuts app.

  5. Joey Avatar

    The kids behind whereismytransport are actually quite open with their api and encourage usage of them at hackathons, not sure why the api had to be reverse engineered.

  6. Carl Sandrock Avatar

    Firstly, I only knew that the Gautrain app used whereismytransport by watching their requests, and secondly using the Gautrain version of the API allows me to do the requests without an API key which makes it easier to share with other users.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.