Skip to main content

Unofficial Python client for NTES (Indian Railways)

Project description

NTES Client Documentation

Complete Python interface for India's National Train Enquiry System (NTES)


Overview

NTES Client is an unofficial Python library that provides programmatic access to Indian Railways' National Train Enquiry System. It handles all the complexity of encrypted communication, request formatting, and response parsing.

Key Features:

  • ๐Ÿ” Automatic encryption/decryption handling
  • ๐Ÿš‚ Full train search and tracking capabilities
  • ๐Ÿ“ Live station boards
  • โฑ๏ธ Real-time train status
  • ๐Ÿ”„ Automatic retry logic
  • ๐ŸŽฏ Clean, minimal API surface

Installation

pip install ntes-client

Dependencies:

  • requests - HTTP client
  • pycryptodome - Encryption support

Both are installed automatically.


Quick Start

from ntes import NTESClient

# Initialize client
client = NTESClient()

# Search for trains
trains = client.search("rajdhani")

# Get live train status
status = client.live_status("12301", "02-May-2026")

# Check station departures
departures = client.station_live("NDLS", hours=4)

API Reference

Client Initialization

NTESClient(timeout=10, retries=2)

Parameters:

Parameter Type Default Description
timeout int 10 Request timeout in seconds
retries int 2 Number of retry attempts on failure

Example:

# Default settings
client = NTESClient()

# Custom configuration
client = NTESClient(timeout=15, retries=3)

Methods

search(query: str)

Search for trains by number, name, or keyword.

Parameters:

  • query (str): Search term (train number, name, or partial match)

Returns: Dict containing matching trains

Example:

result = client.search("rajdhani")
# Returns:
# {
#   "Trains": [
#     {
#       "TrainNumber": "12301",
#       "TrainName": "KOLKATA RAJDHANI",
#       "Source": "NDLS",
#       "Destination": "HWH",
#       "SourceName": "NEW DELHI",
#       "DestinationName": "HOWRAH JN"
#     }
#   ],
#   "TrainNoName": "RAJDHANI"
# }

Use Cases:

  • Find train number from partial name
  • Discover trains on specific routes
  • Validate train existence

train_info(train_no: str)

Get detailed information about a specific train.

Parameters:

  • train_no (str): 5-digit train number

Returns: Dict with train details and instances

Example:

info = client.train_info("12301")
# Returns:
# {
#   "TrainNo": "12301",
#   "TrainName": "KOLKATA RAJDHANI",
#   "Src": "NDLS",
#   "Dstn": "HWH",
#   "vInstanceList": [
#     {
#       "trainStatus": 0,
#       "trainPosition": "Yet to start from its source",
#       "startDate": "02-May-2026"
#     }
#   ]
# }

Response Fields:

  • TrainNo: Train number
  • TrainName: Full train name
  • Src: Source station code
  • Dstn: Destination station code
  • vInstanceList: Recent/upcoming train instances

schedule(train_no: str, start_date: str = "")

Retrieve complete train schedule with all stops.

Parameters:

  • train_no (str): 5-digit train number
  • start_date (str, optional): Date in DD-MMM-YYYY format (e.g., "02-May-2026")

Returns: Dict with schedule details

Example:

schedule = client.schedule("12301")
# Returns:
# {
#   "TrainNumber": "12301",
#   "TravelTime": "17:30",
#   "DaysOfRun": "Daily",
#   "stations": [
#     {
#       "StationCode": "NDLS",
#       "StationName": "NEW DELHI",
#       "STA": "Source",
#       "STD": "16:35",
#       "Distance": "0",
#       "Day": "1"
#     },
#     {
#       "StationCode": "CNB",
#       "StationName": "KANPUR CENTRAL",
#       "STA": "22:00",
#       "STD": "22:05",
#       "Distance": "441",
#       "Day": "1"
#     }
#   ]
# }

Response Fields:

  • TravelTime: Total journey duration
  • DaysOfRun: Operating days
  • stations[]: List of all stops
    • STA: Scheduled Time of Arrival
    • STD: Scheduled Time of Departure
    • Distance: From source (km)
    • Day: Journey day number

station_live(station_code: str, hours: int = 2)

Get live train movements at a station.

Parameters:

  • station_code (str): 3-5 character station code (e.g., "NDLS", "LKO")
  • hours (int, optional): Time window in hours (default: 2)

Returns: Dict with arriving/departing trains

Example:

live = client.station_live("NDLS", hours=4)
# Returns:
# {
#   "TrainsAtStation": [
#     {
#       "TrainNumber": "12301",
#       "TrainName": "KOLKATA RAJDHANI",
#       "ETA": "16:30",
#       "ETD": "16:35",
#       "Platform": "16",
#       "DelayArr": "On Time",
#       "DelayDep": "On Time"
#     }
#   ]
# }

Response Fields:

  • ETA: Expected Time of Arrival
  • ETD: Expected Time of Departure
  • Platform: Platform number
  • DelayArr: Arrival delay status
  • DelayDep: Departure delay status

live_status(train_no: str, start_date: str)

Track real-time position and status of a running train.

Parameters:

  • train_no (str): 5-digit train number
  • start_date (str): Journey start date in DD-MMM-YYYY format

Returns: Dict with current train status

Example:

status = client.live_status("12301", "02-May-2026")
# Returns:
# {
#   "TrainNo": "12301",
#   "TrainName": "KOLKATA RAJDHANI",
#   "CurrentStation": "CNB",
#   "CurrentStationName": "KANPUR CENTRAL",
#   "LastUpdate": "Train has departed from KANPUR CENTRAL",
#   "DelayDep": "On Time",
#   "Platform": "4",
#   "NextStationCode": "ALD",
#   "NextStationName": "ALLAHABAD JN"
# }

Common Status Messages:

  • "Yet to start from its source"
  • "Train has departed from [STATION]"
  • "Train has arrived at [STATION]"
  • "Train is running late by [X] minutes"

exceptions(train_no: str)

Get cancellation, diversion, or rescheduling information.

Parameters:

  • train_no (str): 5-digit train number

Returns: Dict with exception details or alert message

Example:

exc = client.exceptions("12301")
# Returns (no exceptions):
# {
#   "AlertMsg": "No Exceptional Details found for train 12301 !!!"
# }

# Returns (with exceptions):
# {
#   "Exceptions": [
#     {
#       "ExceptionDate": "05-May-2026",
#       "ExceptionType": "CANCELLED"
#     }
#   ]
# }

Error Handling

The library uses custom exceptions for clear error categorization.

Exception Types

from ntes import NTESError, NTESCryptoError

# NTESError - General API/network errors
# NTESCryptoError - Encryption/decryption failures

Error Handling Pattern

from ntes import NTESClient, NTESError

client = NTESClient()

try:
    data = client.train_info("99999")
except NTESError as e:
    print(f"Error: {e}")
    # Handle gracefully

Common Error Scenarios

Error Cause Solution
"empty response" NTES server issue Retry after delay
"invalid json response" Malformed data Report as bug
"request failed" Network timeout Check connectivity
Train/station alerts Invalid code Verify input

Best Practices

import time
from ntes import NTESClient, NTESError

def fetch_with_retry(client, train_no, max_attempts=3):
    """Robust fetching with exponential backoff"""
    for attempt in range(max_attempts):
        try:
            return client.train_info(train_no)
        except NTESError as e:
            if attempt == max_attempts - 1:
                raise
            time.sleep(2 ** attempt)  # 1s, 2s, 4s
    
client = NTESClient(timeout=15, retries=3)
data = fetch_with_retry(client, "12301")

Advanced Usage

Batch Operations

train_numbers = ["12301", "12302", "12951", "12952"]

results = []
for train_no in train_numbers:
    try:
        info = client.train_info(train_no)
        results.append(info)
    except NTESError:
        continue  # Skip failed requests

Custom Session Headers

client = NTESClient()

# Add custom headers
client.session.headers.update({
    "X-Custom-Header": "value"
})

Parsing Nested Responses

# Extract specific data from complex responses
schedule = client.schedule("12301")

station_names = [
    station["StationName"] 
    for station in schedule.get("stations", [])
]

total_distance = schedule.get("stations", [])[-1].get("Distance", "0")

Date Formatting Helper

from datetime import datetime, timedelta

def format_ntes_date(dt):
    """Convert datetime to NTES format"""
    return dt.strftime("%d-%b-%Y")

# Get status for tomorrow
tomorrow = datetime.now() + timedelta(days=1)
status = client.live_status("12301", format_ntes_date(tomorrow))

Response Field Reference

Common Patterns

Unavailable Data:

"**UA**"  # Data unavailable
""        # Empty/missing
"Source"  # At origin station
"Destination"  # At final station

Station Codes:

  • NDLS - New Delhi
  • HWH - Howrah Junction
  • CSTM - Mumbai CST
  • MAS - Chennai Central
  • LKO - Lucknow

Date Format:

  • Input/Output: DD-MMM-YYYY (e.g., 02-May-2026)
  • Day names: Full lowercase (monday) or abbreviated (Mon,Tue)

Real-World Examples

1. Track Multiple Trains

from ntes import NTESClient

client = NTESClient()

trains_to_track = {
    "12301": "Rajdhani",
    "12951": "Superfast",
    "22691": "Express"
}

for train_no, name in trains_to_track.items():
    try:
        status = client.live_status(train_no, "02-May-2026")
        print(f"{name}: {status.get('LastUpdate')}")
    except Exception as e:
        print(f"{name}: Error - {e}")

2. Station Departure Board

def get_departures(station_code, hours=4):
    client = NTESClient()
    data = client.station_live(station_code, hours)
    
    trains = data.get("TrainsAtStation", [])
    for train in trains:
        print(f"{train['TrainNumber']} - {train['TrainName']}")
        print(f"Departs: {train['ETD']} | Platform: {train['Platform']}")
        print(f"Status: {train.get('DelayDep', 'N/A')}\n")

get_departures("NDLS")

3. Journey Planner

def plan_journey(train_no, from_station, to_station):
    client = NTESClient()
    schedule = client.schedule(train_no)
    
    stations = schedule.get("stations", [])
    route = []
    
    capture = False
    for station in stations:
        if station["StationCode"] == from_station:
            capture = True
        
        if capture:
            route.append({
                "station": station["StationName"],
                "arrival": station.get("STA", "N/A"),
                "departure": station.get("STD", "N/A"),
                "distance": station["Distance"]
            })
        
        if station["StationCode"] == to_station:
            break
    
    return route

journey = plan_journey("12301", "NDLS", "CNB")
for stop in journey:
    print(f"{stop['station']} - Arr: {stop['arrival']} Dep: {stop['departure']}")

4. Train Alert System

import time
from datetime import datetime

def monitor_train(train_no, start_date, check_interval=300):
    """Monitor train and alert on delays"""
    client = NTESClient()
    
    while True:
        try:
            status = client.live_status(train_no, start_date)
            delay = status.get("DelayDep", "On Time")
            
            if delay != "On Time":
                print(f"ALERT: Train {train_no} - {delay}")
                # Send notification (email, SMS, etc.)
            else:
                print(f"Train {train_no}: {status.get('LastUpdate')}")
            
            time.sleep(check_interval)
        except Exception as e:
            print(f"Error: {e}")
            time.sleep(60)

# Check every 5 minutes
monitor_train("12301", "02-May-2026", check_interval=300)

Technical Details

Reverse Engineering Methodology

This library was created through reverse engineering of the official NTES Android application.

Tools & Environment:

  • Device: Google Pixel 2
  • OS: Android 11 (API Level 30)
  • Instrumentation: Frida (dynamic analysis)
  • Proxy: Burp Suite (traffic interception)

Process:

  1. Instrumented the official app using Frida
  2. Intercepted encrypted traffic via Burp Suite
  3. Extracted encryption keys and algorithm from app binary
  4. Reverse engineered request/response format
  5. Implemented clean Python interface

This methodology is documented for transparency and educational purposes.

Encryption

NTES uses a proprietary encryption scheme:

  1. Algorithm: AES-128 (CBC mode)
  2. Encoding: Base64 โ†’ Hex
  3. Signature: MD5 hash with secret key

Payload Structure:

MD5(data + secret_key) # HEX(BASE64(AES_ENCRYPT(data)))

The library handles all encryption/decryption automatically.

Keys (extracted from app):

  • AES Key: 8EA4DB2CC1EB3DC5
  • IV: 7DC5EB3BB4DB6EA8
  • Secret: 645fbc1e56e23365f2f3c204ae0899f6

Request Flow

User Code
    โ†“
NTESClient.method()
    โ†“
Encrypt payload
    โ†“
HTTP POST to NTES
    โ†“
Receive encrypted response
    โ†“
Decrypt & parse JSON
    โ†“
Return to user

Network Configuration

  • Endpoint: https://enquiry.indianrail.gov.in/crisns/AppServAnd
  • Method: POST
  • Content-Type: application/json
  • User-Agent: Android client signature

Limitations & Caveats

API Stability

  • Unofficial API: No stability guarantees
  • Schema Changes: Response format may change without notice
  • Downtime: Backend may be unavailable during maintenance

Data Quality

  • Inconsistent Fields: Not all trains return same fields
  • Missing Data: Some fields may be empty or **UA**
  • Delayed Updates: Real-time data may lag by 5-10 minutes

Rate Limiting

  • No official rate limits documented
  • Recommended: Max 60 requests/minute
  • Implement backoff for production use

Legal & Ethical

  • Respect NTES terms of service
  • Don't overload the system
  • Use for personal/research purposes
  • No commercial guarantees

Troubleshooting

Common Issues

Problem: "empty response" error

# Solution: Increase timeout and retries
client = NTESClient(timeout=20, retries=3)

Problem: Train not found in search

# Solution: Try variations
client.search("12301")  # Number
client.search("rajdhani")  # Name
client.search("kolkata")  # Route

Problem: Stale data in live_status

# Solution: Verify date format and train schedule
# Ensure train runs on specified date
info = client.train_info("12301")
# Check vInstanceList for valid dates

Problem: Missing platform information

# Solution: Platform may not be announced yet
# Check closer to departure time

Debug Mode

import logging

# Enable detailed logging
logging.basicConfig(level=logging.DEBUG)

client = NTESClient()
# Now see all HTTP requests/responses

Performance Tips

  1. Reuse Client Instance

    # Good: One client for multiple requests
    client = NTESClient()
    for train in trains:
        client.train_info(train)
    
    # Bad: New client each time
    for train in trains:
        client = NTESClient()
        client.train_info(train)
    
  2. Cache Responses

    from functools import lru_cache
    
    @lru_cache(maxsize=100)
    def get_schedule(train_no):
        return client.schedule(train_no)
    
  3. Parallel Requests (Use with caution)

    from concurrent.futures import ThreadPoolExecutor
    
    with ThreadPoolExecutor(max_workers=5) as executor:
        results = executor.map(client.train_info, train_numbers)
    

Migration Guide

From Direct API Calls

Before:

import requests
# Manual encryption, parsing, error handling...

After:

from ntes import NTESClient
client = NTESClient()
data = client.search("rajdhani")

From Other Libraries

Most train tracking libraries have similar patterns:

# Other library
from other_lib import RailClient
rail = RailClient()
rail.get_train("12301")

# NTES Client
from ntes import NTESClient
client = NTESClient()
client.train_info("12301")

Contributing

Development Setup

# Clone repository
git clone https://github.com/yourusername/ntes-client.git
cd ntes-client

# Install dependencies
pip install -r requirements.txt

# Run tests
pytest -v

Project Structure

ntes-client/
โ”œโ”€โ”€ ntes/
โ”‚   โ”œโ”€โ”€ __init__.py      # Package exports
โ”‚   โ”œโ”€โ”€ client.py        # Main client class
โ”‚   โ”œโ”€โ”€ crypto.py        # Encryption layer
โ”‚   โ”œโ”€โ”€ exceptions.py    # Custom exceptions
โ”‚   โ””โ”€โ”€ utils.py         # Helper functions
โ”œโ”€โ”€ tests/
โ”‚   โ”œโ”€โ”€ test_client.py   # Client tests
โ”‚   โ””โ”€โ”€ test_crypto.py   # Crypto tests
โ”œโ”€โ”€ README.md
โ”œโ”€โ”€ setup.py
โ””โ”€โ”€ requirements.txt

Code Style

  • PEP 8 compliance
  • Type hints for all functions
  • Docstrings for public methods
  • Minimal dependencies

FAQ

Q: Is this library official?
A: No, this is an unofficial client reverse-engineered from the mobile app.

Q: Can I use this in production?
A: Use at your own risk. No uptime or accuracy guarantees.

Q: Why do some trains return incomplete data?
A: NTES data quality varies. Not all trains have complete information.

Q: How often is data updated?
A: Live data updates every 5-10 minutes. Schedules are relatively static.

Q: Can I get historical data?
A: No, NTES only provides current and near-future data.

Q: What about PNR status?
A: This library covers NTES endpoints only. PNR is a separate system.

Q: How do I report bugs?
A: Open an issue on GitHub with reproduction steps.


License

This library is provided as-is for educational and personal use.

Disclaimer:

  • Not affiliated with Indian Railways or CRIS
  • Created through reverse engineering for educational purposes
  • Use responsibly and at your own risk
  • Respect Indian Railways terms of service
  • No commercial guarantees or warranties

Legal Note: This library is intended for:

  • Personal train tracking
  • Educational exploration
  • Research purposes
  • Non-commercial automation

Not intended for:

  • Commercial redistribution
  • High-volume scraping
  • Service disruption
  • Terms of service violation

Support

  • Documentation: This file
  • Issues: GitHub issue tracker
  • Updates: Check repository for latest version

Changelog

Version 1.0.0

  • Initial release
  • Core API methods
  • Encryption handling
  • Error normalization
  • Retry logic

Acknowledgments

This library abstracts the NTES mobile API for easier Python integration.

Special thanks to the open-source community for cryptography libraries.


Last Updated: May 2026
Library Version: 1.0.0
Python Compatibility: 3.7+

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ntes_client-0.1.1.tar.gz (18.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ntes_client-0.1.1-py3-none-any.whl (10.7 kB view details)

Uploaded Python 3

File details

Details for the file ntes_client-0.1.1.tar.gz.

File metadata

  • Download URL: ntes_client-0.1.1.tar.gz
  • Upload date:
  • Size: 18.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.1

File hashes

Hashes for ntes_client-0.1.1.tar.gz
Algorithm Hash digest
SHA256 3093b4a3d94bceadb98951f91ba42a687ece59f5a133885396c5fce1cfd10f01
MD5 c3f25abfcd290ea97ae2b32cd11f5982
BLAKE2b-256 335cd627d1b54eb246e608c554f646f402faca57812afe61f798bbc84ae6332b

See more details on using hashes here.

File details

Details for the file ntes_client-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: ntes_client-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 10.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.1

File hashes

Hashes for ntes_client-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 5eae0cc2393f0aadabfaf947a8199c09d8c2bc7c4e219405f34a925b983fce5d
MD5 5d11cdc3691b7dce3b10f146e4eabd0b
BLAKE2b-256 cff59f100a05e86004e6e0f38134b8a9f27d05e3609065013892e668fc291279

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page