8.5 KiB
8.5 KiB
ADIF Elcano API - Complete Documentation
Complete documentation of the ADIF API (El Cano Movil) obtained through reverse engineering of the mobile application.
All 9/9 endpoints functional | HMAC-SHA256 authentication | 1587 station codes
Base URLs
BASE_URL_STATIONS = https://estaciones.api.adif.es
BASE_URL_CIRCULATION = https://circulacion.api.adif.es
BASE_URL_ELCANOWEB = https://elcanoweb.adif.es/api/
BASE_URL_AVISA = https://avisa.adif.es/avisa-ws/api/
Authentication Keys
HMAC Keys (extracted from libapi-keys.so)
ACCESS_KEY = "and20210615"
SECRET_KEY = "Jthjtr946RTt"
User-keys (different for each service)
Circulations: f4ce9fbfa9d721e39b8984805901b5df
Stations: 0d021447a2fd2ac64553674d5a0c1a6f
Other Auth Tokens
Avisa Login: Basic YXZpc3RhX2NsaWVudF9hbmRyb2lkOjh5WzZKNyFmSjwhXypmYXE1NyNnOSohNElwa2MjWC1BTg==
Subscriptions: Basic ZGVpbW9zOmRlaW1vc3R0
Registration Token: Bearer b9034774-c6e4-4663-a1a8-74bf7102651b
Endpoints (9/9 Working)
Station Endpoints
Get All Stations
GET /portroyalmanager/secure/stations/allstations/reducedinfo/{token}/
Base: https://estaciones.api.adif.es
User-key: stations
Token: Initial value is "0"
Get Station Details
POST /portroyalmanager/secure/stations/onestation/
Base: https://estaciones.api.adif.es
User-key: stations
Body:
{
"stationCode": "10200",
"token": "0",
"detailedInfo": {
"extendedStationInfo": true,
"stationActivities": true,
"stationBanner": true,
"stationCommercialServices": true,
"stationInfo": true,
"stationServices": true,
"stationTransportServices": true
}
}
Station Observations
POST /portroyalmanager/secure/stationsobservations/
Base: https://estaciones.api.adif.es
User-key: stations
Body:
{
"stationCodes": ["60000", "71801"]
}
Circulation Endpoints
Departures
POST /portroyalmanager/secure/circulationpaths/departures/traffictype/
Base: https://circulacion.api.adif.es
User-key: circulations
Body:
{
"commercialService": "BOTH",
"commercialStopType": "BOTH",
"page": {"pageNumber": 0},
"stationCode": "10200",
"trafficType": "ALL"
}
Arrivals
POST /portroyalmanager/secure/circulationpaths/arrivals/traffictype/
Base: https://circulacion.api.adif.es
User-key: circulations
Body: Same format as departures
Between Stations
POST /portroyalmanager/secure/circulationpaths/betweenstations/traffictype/
Base: https://circulacion.api.adif.es
User-key: circulations
Body:
{
"commercialService": "BOTH",
"commercialStopType": "BOTH",
"originStationCode": "10200",
"destinationStationCode": "71801",
"page": {"pageNumber": 0},
"trafficType": "ALL"
}
IMPORTANT: Send only origin/destination; do NOT add `stationCode` or explicit `null` fields.
One Train Route (OnePaths)
POST /portroyalmanager/secure/circulationpathdetails/onepaths/
Base: https://circulacion.api.adif.es
User-key: circulations
Body:
{
"allControlPoints": true,
"commercialNumber": "04138",
"destinationStationCode": "60000",
"launchingDate": 1733356800000,
"originStationCode": "71801"
}
Notes:
- `commercialNumber` must be real (grab from departures/arrivals)
- `launchingDate` must be milliseconds
- Omit unused fields instead of sending `null`
Multiple Routes (SeveralPaths)
POST /portroyalmanager/secure/circulationpathdetails/severalpaths/
Base: https://circulacion.api.adif.es
User-key: circulations
Body: Same format as onepaths (omit unused fields)
Returns 204 when there is no data for the requested trains.
Train Composition
POST /portroyalmanager/secure/circulationpaths/compositions/path/
Base: https://circulacion.api.adif.es
User-key: circulations
Body:
{
"commercialNumber": "03194",
"destinationStationCode": "71801",
"launchingDate": 1764889200000,
"originStationCode": "10200"
}
IMPORTANT: Do NOT include `allControlPoints: true` (requires elevated permissions, returns 401)
Response Structures
Departures/Arrivals Response
{
"commercialPaths": [
{
"commercialPathInfo": {
"timestamp": 1764927847100,
"commercialPathKey": {
"commercialCirculationKey": {
"commercialNumber": "90399",
"launchingDate": 1764889200000
},
"originStationCode": "10620",
"destinationStationCode": "60004"
},
"trafficType": "CERCANIAS",
"opeProComPro": {
"operator": "RF",
"product": "C",
"commercialProduct": " "
}
},
"passthroughStep": {
"stopType": "NO_STOP",
"stationCode": "10200",
"departurePassthroughStepSides": {
"plannedTime": 1764927902000,
"forecastedOrAuditedDelay": 2175,
"timeType": "FORECASTED",
"plannedPlatform": "2",
"circulationState": "RUNNING"
}
}
}
]
}
Important fields:
commercialNumber: Train commercial numberlaunchingDate: Date in millisecondsplannedTime: Planned time in millisecondsforecastedOrAuditedDelay: Delay in secondscirculationState: RUNNING, PENDING_TO_CIRCULATE, etc.
OnePaths Response (Complete Route)
{
"commercialPaths": [
{
"commercialPathInfo": { /* Same as departures */ },
"passthroughSteps": [ // Array with ALL stops
{
"stopType": "COMMERCIAL",
"stationCode": "10620",
"departurePassthroughStepSides": {
"plannedTime": 1764918000000,
"forecastedOrAuditedDelay": 430,
"plannedPlatform": "1",
"circulationState": "RUNNING"
}
}
// ... more stops
]
}
]
}
Key difference:
departures/arrivals->passthroughStep(singular)onepaths->passthroughSteps(plural, array)
Status Codes
| Code | Meaning | Cause |
|---|---|---|
| 200 | Success | Request successful with data |
| 204 | No Content | Authentication OK, no data available |
| 400 | Bad Request | Incorrect payload |
| 401 | Unauthorized | No permissions or wrong auth |
Note: 204 is NOT an error. It means authentication and payload are correct.
Data Types
TrafficType
CERCANIAS- Commuter trainsAVLDMD- High Speed, Long and Medium DistanceOTHERS- OtherTRAVELERS- PassengersGOODS- FreightALL- All types
State (commercialService, commercialStopType)
YESNOTBOTH
Common Issues and Solutions
Issue 1: NULL Fields Cause Errors
Problem: Including null fields in JSON causes 401 errors.
// WRONG - causes 401
{"stationCode": null, "trafficType": "ALL"}
// CORRECT - omit the field entirely
{"trafficType": "ALL"}
Issue 2: Header Order for HMAC
The canonical headers order is NOT alphabetical:
1. content-type
2. x-elcano-host <- host before client!
3. x-elcano-client
4. x-elcano-date
5. x-elcano-userid
Issue 3: Timestamps
launchingDate must be in milliseconds:
# Correct
timestamp = int(datetime.now().timestamp() * 1000)
# Wrong (missing 3 zeros)
timestamp = int(datetime.now().timestamp())
Issue 4: allControlPoints Requires Permissions
/compositions/path/ with allControlPoints: true returns 401. Don't include it.
Station Codes
Total: 1587 stations
File: station_codes.txt
Source: apk_extracted/assets/stations_all.json
Top Stations
10200 Madrid Puerta de Atocha AVLDMD
10302 Madrid Chamartin-Clara Campoamor AVLDMD
71801 Barcelona Sants AVLDMD,CERCANIAS
60000 Valencia Nord AVLDMD
11401 Sevilla Santa Justa AVLDMD
50003 Alicante Terminal AVLDMD,CERCANIAS
54007 Cordoba Central AVLDMD
79600 Zaragoza Portillo AVLDMD,CERCANIAS
Implementation Notes
Tools Used
- Ghidra: Key extraction from
libapi-keys.so - JADX: APK decompilation
- Python 3: Client implementation
Key Source Files
apk_extracted/lib/x86_64/libapi-keys.so- Authentication keysapk_extracted/assets/stations_all.json- Station databaseapk_decompiled/sources/com/adif/elcanomovil/serviceNetworking/interceptors/auth/ElcanoAuth.java- HMAC algorithm
Main Analyzed Classes
TrafficCirculationPathRequest- Request modelOneOrSeveralPathsRequest- Path requestsElcanoAuth- HMAC-SHA256 implementationServicePaths- URL and key definitions