Russian Railway (РЖД) API Documentation

For a long time, I’ve been interested in air travel and using my programming skills to find interesting routes, cheap fares and mileage optimised routings. However, after reading the blogs such as The Man in Seat Sixty-One I’ve also beginning to get more interested in overland travel, specifically train and bus journeys.

While there are numerous interesting train routes out there, one of the most famous ones is undoubtedly the Trans-Siberian Railway. With over 9289 km or 5772 Miles it’s the world’s longest railway line. What I found particularly interesting is that you can connect even further, basically allowing to routings from Central Europe to Hanoi, Vietnam or Beijing, China.

When trying to plan your own trip on “Transsib” you’ll soon notice that it can soon become a very time-intensive hobby, as some trains don’t always run, have no availability (90 day booking period) or are not necessarily arriving or departing at the right time. To make things worse, the РЖД website used to be not the easiest to navigate and at times buggy. I, therefore, did a little bit of reverse engineering and searched for ways to automate the search. As a result, I documented the RZD API in Postman, to allow easy implementation in a language of your choice. The documentation can be found on documenter.

Which data can be retrieved from РЖД API?

I was able to retrieve the following pieces of information from the website:

  • Get the train-stations for a specific train number on a certain date:
  • Search train stations based on a query string
  • Distance between the stations, waiting times as well as arrival and departure times

How the RZD API works (high-level)

The RZD API has a request scheme that is fairly easy to grasp. For each task, there is an endpoint that is usually defined by a parameter called layer_id that takes certain parameters (see the specifics of each endpoint below). If the request was valid, the API would respond with a json containing a RID. The response willJSONk similar to this one:

1
{"result":"RID","RID":11406098162,"timestamp":"26.12.2019 14:14:06.673"}

Alongside the RID, the Server will also yield back a SET-Cookie header that has JSESSIONID Cookie. This cookie has to be added to any future requests. Unlike the RID, the Cookie JSESSIONID Cookie doesn’t change during every request.

The actual data is then fetched via a POST request from a single endpoint containing the previously retrieved RID.

Please see the Postman documentation for a full list of parameters and options.

Little Python Example

Let’s assume we want to search available a one-way third-class ticket from Saint Petersburg to Moscow and we are willing to transfer. The matching Python request could look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import requests
import time

# layer_id 5827 Specifies the API endpoint to search for tickets
#
# dir 0 Type of journey one way 0 return 1
#
# code0 2004000 Origin station code 2004000 is Saint Petersburg
# code1 2000000 Destination station code 2000000 is Moscow
# you can find the station codes on https://parovoz.com/spravka/codes/index.html
#
# tfl 3  Class of train For trains, subtrains and local train set to 3 For subtrains and local train set to 2 For local train set to 1
#
# checkSeats1 Check availability of seats enable by setting parameter to 1 disable by setting parameter to 0
#
# dt0 28.12.2019 Date of departure from origin d.m.Y
#
# dt1 31.12.2019 Date of departure from destination d.m.Y
#
# md 1 Include routes with transfers (include transfers by setting parameter to 1, exclude transfers by setting paraemter to 0)
url = "https://pass.rzd.ru/timetable/public/?layer_id=5827&dir=0&tfl=3&checkSeats=1&code0=2004000&code1=2000000&dt0=15.04.2021&md=1"

s = requests.Session()
response = s.request("POST", url).json()
print(response)
print(s.cookies)

The response, which we get back, will look something like this (notice the RID):

1
{'result': 'RID', 'RID': 14518510324, 'timestamp': '13.01.2021 18:46:35.492'}

Along with the JSON response, we also retrieved two cookies:

1
<RequestsCookieJar[<Cookie AuthFlag=false for .rzd.ru/>, <Cookie ClientUid=2Ih... for .rzd.ru/>, <Cookie lang=ru for .rzd.ru/>, <Cookie JSESSIONID=0000... for pass.rzd.ru/>]>

We can now use these to retrieve the actual data. To do so, I will simply reuse the session object, but this time make a POST request containing the RID.

Update
I have added a delay of more than 60 seconds prior to executing the second request. According to some feedback of a lovely reader, this is necessary to get the desired response.
1
2
3
4
5
6
7
8
9
time.sleep(61)
url = "https://pass.rzd.ru/timetable/public/ru?layer_id=5827"

payload = 'rid={}'.format(response["RID"])
headers = {
  'Content-Type': 'application/x-www-form-urlencoded'
}

response = s.request("POST", url, headers=headers, data= payload).json()

If we piece it together, the whole code will then look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import requests
import time

url = "https://pass.rzd.ru/timetable/public/?layer_id=5827&dir=0&tfl=3&checkSeats=1&code0=2004000&code1=2000000&dt0=01.03.2021&md=1"
s = requests.Session()
response = s.request("POST", url).json()
print(response["RID"])

time.sleep(61)

url = "https://pass.rzd.ru/timetable/public/ru?layer_id=5827"
payload = 'rid={}'.format(response["RID"])
headers = {
  'Content-Type': 'application/x-www-form-urlencoded'
}
response = s.request("POST", url, headers=headers, data= payload).json()
print(response)

As a response, you should get a neat json with all the information you desire.

I hope my RZD API documentation is also useful for your next train journey.