NRSDB API
Details of how to connect to NRSDB from other applications.

Internal API
This API is designed for internal use within Network Rail. It uses a simple authentication mechanism that does not link to a specific "user" within the system. It was created in response to a request to report into Power BI. 

 Base URL 

 https://nrsdb.uk/api/v1 

 Authentication 

 Registered users of the API will be issued with a key - all requests (other than this help URL) must pass this key as a Bearer token 

 Sample Code 

 Sample GET request 

 const params = new URLSearchParams();

 params.append('property', value);

 const response = await fetch(`${url}?${params}`, {

 'credentials': 'same-origin',

 'headers': {

 'Authorization': `Bearer ${token}`

 'Accept': 'application/json',

 'Content-Type': 'application/json',

 'X-Requested-With': 'XMLHttpRequest'

 },

 'method': 'GET'

 }); 

 Sample POST from a form element 

 const response = await fetch(url, {

 'body': formData,

 'credentials': 'same-origin',

 'headers': {

 'Authorization': `Bearer ${token}`

 },

 'method': 'POST'

}); 

 NOTE: in my experience it is best not to set the 'Content-Type' header here 

 Endpoints 

 Routes 

 E.g. Anglia, North-West, Wales 

 /routes - return all Routes 

 /route/{routeId} - detail of a specific route 

 

 Delivery Units 

 /dus - return all Delivery Units 

 /dus/route/{routeId} - return Delivery Units on a given route 

 /du/1 - get details for a specific DU 

 

 

 

 

 Sample Response 

 

 {

 "data": {

   "name": "Ipswich",

   "routeId": 2,

 }

} 

 

 ELRs 

 /elrs - returns all of the ELRs known to the system 

 /elr/{elrId} - returns the details for a specific ELR 

 

 Sample Response 

 

 

 Locations 

 /locations/{elrId} 

 Returns the significant locations along a given ELR, with Miles + Yards location and a GeoJSON marker if available 

 NOTE: data set is too large to return for the entire railway 

 NOTE: this is different to the Autumn Inspection locations (see below) 

   

 Autumn Inspections 

 /autumn/locations/{routeId} - return all Locations to be inspected on the given route 

 /autumn/inspections - returns all of the inspections 

 /autumn/inspections/route/{routeId} - returns all inspections for a given route 

 

 Inspection Example Response 

 {

 "id": 33,

 "inspectionDate": "2025-06-09T23:55:39+01:00",

 "locationId": 236,

 "location": {

 "id": 236,

 "location": "Falconwood",

 "locationClass": "Platform",

 "routeId": 8,

 "ownerId": 33,

 "elrId": 81,

 "trackId": 1100,

 "track": "",

 "start": 827,

 "end": 827,

 "frequency": 1,

 "active": true,

 "inspections": []

 },

 "inspectorId": 3,

 "inspector": null,

 "weather": {

 "precipitation": 3,

 "wind": 2,

 "overhead": 2,

 "ground": 2

 },

 "rail_condition": "dry",

 "evaluation": 0,

 "action_taken": "",

 "comments": "",

 "images": []

 } 

 

 ESRs 

 /esrs - returns all known ESRs 

 /esrs/route/{routeId} - returns ESRs for a given route 

 

 See ESR data structure 

 GIS 

 /gis/nearest/{lng}/{lat} -- returns the nearest mileage marker (ELR, track, miles, chains) 

 /gis/w3w/{elr}?trackId=1100&miles=1&chains=0 -- returns the W3W code for the marker at the given location, trackId is optional, chains defaults to 0 if not given 

 Error Handling 

 We return appropriate HTTP response codes whenever an error is encountered 

 400 Bad Request 

 If the URL parameter "r" is missing 

 401 Unauthorized 

 This is returned if the given signature is invalid or lapsed. A signature is valid for only 5 mins so should be generated for each request 

 501 Not Implemented 

 This is returned if "r" is supplied but is not a valid endpoint 

 Others 

 Internal errors, for example unable to contact the database or failure to encode JSON will return "400 Bad Request" unless there's a specific HTTP response associated with the error 

 

 

 Power BI 

 To access this data using Power BI; 

 - click Get Data / Blank Query 

 - enter something like = Json.Document(Web.Contents("https://training.nrsdb.uk" & "/api/v1" & "/dus/1", [Headers=[Authorization="Bearer YOUR_KEY"]]))

Signed API V2
For external use we offer an API that requires each request to be sent with a "signature" created using the request content and a timestamp. This makes each signature useful for a short time only and prevents possible replay attacks. 

 Signed API V2 

 Base URL: https://nrsdb.uk/api/nrsdb/v2 

 Signature 

 Send the following HTTP Headers 

 X-NRSDB-Signature 

 X-NRSDB-Timestamp 

 X-NRSDB-Key 

 Endpoints 

 ESRs 

 https://nrsdb.uk/api/nrsdb/v2/esrs?age=3 

  

Create Signature
Registration 

 Registered users of the API will be issued with a key and secret. 

 End points require authentication by passing the following URL params: 

 - key - identifies the requesting organisation - timestamp - UNIX timestamp at time signature is calculated, used to prevent a stolen signature be reused - signature - calculated as SHA256 hash of a string constructed by concatenating the `key` and `timestamp`; then base64 encoded 

 Signature Generation 

 NOTE: the signature hash string should be base64 encoded. This is pretty simple in PHP but the output of the hash function in some languages is not a string! 

 PHP Sample Code 

 $signature = base64_encode(

   hash_hmac(

       'sha256',

       $key . $timestamp,

       $secret

   )

);

 

 Python Sample Code 

     import requests

   import time

   import hmac

   import hashlib

   import base64

   API_KEY = ""

   API_SECRET = ""

   timestamp = str(int(time.time()))

   message = API_KEY + timestamp

   hash = hmac.new(API_SECRET.encode(), message.encode(), hashlib.sha256)

   signature = base64.b64encode(hash.hexdigest().encode()).decode()

   print("Timestamp:", timestamp)

   print("Hash:", hash.hexdigest())

   print("Signature:", signature)

 

 Typescript Sample Code 

 // Get current UNIX timestamp (in seconds)

const currentTimestamp = Math.floor(Date.now() / 1000);

pm.environment.set("currentTimestamp", currentTimestamp);

// Get key and secret from environment

const key = '***';

const secret = '****';

// Build the message: key + timestamp

const message = key + currentTimestamp;

// Step 1: Calculate the HMAC-SHA256 and get HEX string

const hashHex = CryptoJS.HmacSHA256(message, secret).toString(CryptoJS.enc.Hex);

// Step 2: Convert HEX string into a WordArray using UTF-8 encoding

const hexAsUtf8 = CryptoJS.enc.Utf8.parse(hashHex);

// Step 3: Encode that WordArray into Base64

const base64Signature = CryptoJS.enc.Base64.stringify(hexAsUtf8);

console.log("Hex:", hashHex);

console.log("Base64 of hex string:", base64Signature);

// Optionally set it in Postman environment

pm.environment.set("signature", base64Signature);

Signed API V1 (deprecated)
Signed API V1 (Deprecated) 

 Base URL: https://nrsdb.uk/api/nrsdb.php 

 Signature 

 Append the signature, timestamp and key as URL parameters, e.g. 

 https://nrsdb.uk/api/nrsdb.php ?key=test&timestamp=12345678&signature= 

 Endpoints 

 Add the 'r' parameter to the URL to specify the request 

 ESRs 

 ### r=getEsrs 

 Get properties of all ESRs, optionally filtered by parameters as follows; 

 Parameters 

 - age - limit response to ESRs that have changed within the specified number of days, e.g. age=3 

 Response Format 

 Response has two properties, "count" tells us how many records were returned and === 0 for error, "data" is an array of ESR records 

 The ESR record only returns the "Primary Line", this will always includes the "Commencement Board" and "Termination". 

 ESR Data Structure 

 - refnum (string) - this is made up of a route specific prefix, a three digit incremental counter, plus the two digit year - original_refnum (string) - this gives the original reference, allowing consumer to apply updates - routename (string) - long name for the Route - deliveryunit (string) - responsibly Delivery Unit - elrcode (string) - ELR of track segment impacted e.g. LTN1 - elrdescription (string) - full description of the ELR e.g. "LIVERPOOL STREET - TROWSE LOWER JN" - lorId (int) - internal ID of the LOR - use r=lor?lorId=X to convert - location (string) - description of where the restriction is applied - reason (string) - describes reason the ESR is imposed - speed (string) - "Freight Speed / Passenger Speed" - single number if both are same - withdrawn (bool) - 0 = active, 1 = withdrawn - whenimposed (string) - W3C format date time string - etc (null|string) - if set this is a W3C format date time string - whenwithdrawn (null|string) - if withdrawn === 1, W3C format date time string - line (array) - details of the Primary Line     - elrId - internal ID, not useful - Primary Line is always on the ELR as given above     - trackId (int) - e.g. 1100 = Up Main     - direction (string) - "Up Direction" | "Down Direction" (yuk)     - linedescription (string) - can be driven by trackId or overridden by Controller     - routeheader (string) - "Primary Line" | "Additional Line"     - speed (string) - repeat for backward compatibility     - boards (array)         - type (string) - short name (no spaces)         - description (string) - long name         - elrId (int) - copied from line         - trackId (int) - copied from line         - miles (int) -         - chains (int) - 0 >= X < 80         - serialNumber (string) - not currently used 

 Example 

     {

       "refnum": "AICC 008A.22",

       "original_refnum": "AICC 008.22",

       "routename": "Anglia",

       "deliveryunit": "Ipswich",

       "elr": 255,

       "elrcode": "COC",

       "elrdescription": "Colchester to Clacton",

       "lorId": 1

       "location": "Between here and there",

       "reason": "Track - Cyclic Top",

       "speed": "30/60",

       "withdrawn": 0,

       "whenimposed": "2022-09-02 13:53:09",

       "etr": null,

       "whenwithdrawn": null,

       "line": {

           "elrId": 255,

           "trackId": 1100,

           "direction": "Up Direction",

           "linedescription": "Up Main",

           "routeheader": "Primary Line",

           "boards": [

               {

                   "type": "commencement",

                   "description": "Commencement Board",

                   "elrId": 255,

                   "trackId": 1100,

                   "miles": 1,

                   "chains": 42,

                   "serialNumber": ""

               },

               {

                   "type": "termination",

                   "description": "Termination",

                   "elrId": 255,

                   "trackId": 1100,

                   "miles": 0,

                   "chains": 37,

                   "serialNumber": ""

               }

           ],

           "speed": "30/60"

       }

   }

 

   

 ## Blanket Speed Restrictions 

 ### r = getBlanketSpeeds 

 ### Response Format 

 TBA 

 ## LORs 

 ### r=lor 

 #### Parameters 

 - lorId - give the lorId from an ESR response 

 #### Response Format 

 ``` {"count":1,"data":[{"lorcode":"EA1010","lordescription":"LIVERPOOL STREET TO SEVEN KINGS"}]} ``` 

 Errors 

 We return appropriate HTTP response codes whenever an error is encountered 

 400 Bad Request 

 If the URL parameter "r" is missing 

 401 Unauthorized 

 This is returned if the given signature is invalid or lapsed. A signature is valid for only 5 mins so should be generated for each request 

 501 Not Implemented 

 This is returned if "r" is supplied but is not a valid endpoint 

 Others 

 Internal errors, for example unable to contact the database or failure to encode JSON will return "400 Bad Request" unless there's a specific HTTP response associated with the error

Data Structures
ESR 

 

 

 ESR Data Structure 

 

 - refnum (string) - this is made up of a route specific prefix, a three digit incremental counter, plus the two digit year 

 

 - speed (object) - "Freight Speed / Passenger Speed" - single number if both are same, units and unrestricted line speed 

 

 - route (object) - all properties of the route 

 - du (object) - responsible Delivery Unit 

 - elr (object) - ELR of track segment impacted e.g. LTN1 

 - lor (object) - LOR properties 

 - location (string) - description of where the restriction is applied, should be "At X" or "Between X and Y" 

 - reason (object) - describes reason the ESR is imposed 

 - withdrawn (bool) - 0 = active, 1 = withdrawn 

 - whenimposed (string) - W3C format date time string, updated during amendment 

 - etc (null|string) - if set this is a W3C format date time string 

 - whenwithdrawn (null|string) - if withdrawn === 1, W3C format date time string 

 - lines (array) - object for each "line". A "line" is the combination of ELR + TrackID 

     - elr (object) - as above 

     - trackId (int) - e.g. 1100 = Up Main 

     - direction (string) - "Up Direction" | "Down Direction" (yuk) 

     - linedescription (string) - can be driven by trackId or overridden by Controller 

     - routeheader (string) - "Primary Line" | "Additional Line" 

     - boards (array) 

         - type (string) - short name (no spaces) 

         - description (string) - long name 

         - elrId (int) - copied from line 

         - trackId (int) - copied from line 

         - miles (int) - 

         - chains (int) - 0 >= X < 80 

         - serialNumber (string) - not currently used 

         - marker (GeoJSON) 

 - references (object) 

 - updated_at - time of last database change 

   

 

 Example Response 

 

 {

 "id": 5,

 "refnum": "AICC 001.26",

 "speed": {

 "value": "40",

 "unit": "mph",

 "linespeed": "120"

 },

 "route": {

 "routecode": "EA",

 "routename": "Anglia",

 "shortname": "AICC",

 },

 "du": {

 "name": "Ipswich",

 "routeId": 2,

 },

 "elr": {

 "elrcode": "LTN1",

 "elrdescription": "LIVERPOOL STREET - TROWSE LOWER JN",

 },

 "lor": {

 "lorcode": "EA1011",

 "lordescription": "SEVEN KINGS TO IPSWICH",

 },

 "location": "At Colchester South Jn",

 "reason": {

 "reason": "Structural - Station",

 },

 "lines": [

 {

 "elr": {

 "elrId": 668,

 "elrcode": "LTN1",

 "elrdescription": "LIVERPOOL STREET - TROWSE LOWER JN",

 "active": true

 },

 "trackId": 1100,

 "routeheader": "Primary Line",

 "direction": "Up Direction",

 "linedescription": "Up Main",

 "newStyleBoards": true,

 "boards": [

 {

 "type": "commencement",

 "description": "Speed Indicator (Commencement)",

 "elrId": 668,

 "trackId": 1100,

 "miles": 50,

 "chains": 75,

 "serialNumber": null,

 "marker": {

 "type": "Feature",

 "geometry": {

 "type": "Point",

 "coordinates": [

 0.876876605,

 51.90208042

 ]

 },

 "properties": {

 "type": "commencement",

 "description": "Speed Indicator (Commencement)",

 "elrId": 668,

 "trackId": 1100,

 "miles": 50,

 "chains": 75,

 "serialNumber": null

 }

 }

 },

 {

 "type": "termination",

 "description": "Termination Indicator",

 "elrId": 668,

 "trackId": 1100,

 "miles": 49,

 "chains": 75,

 "serialNumber": null,

 "marker": {

 "type": "Feature",

 "geometry": {

 "type": "Point",

 "coordinates": [

 0.85427535,

 51.89894058

 ]

 },

 "properties": {

 "type": "termination",

 "description": "Termination Indicator",

 "elrId": 668,

 "trackId": 1100,

 "miles": 49,

 "chains": 75,

 "serialNumber": null

 }

 }

 }

 ]

 }

 ],

 "whenimposed": "2026-03-18 11:42:06",

 "etr": null,

 "withdrawn": false,

 "whenwithdrawn": null,

 "references": {

 "fmsnumber": "123",

 "ccilnumber": "23645657",

 "tsrreference": "",

 "tdanumber": "",

 "wo": "",

 "rdms": "",

 "trustcode": "",

 "owner": ""

 },

 "updated_at": "2026-03-18 11:42:06"

}