376 lines
8.5 KiB
Markdown
376 lines
8.5 KiB
Markdown
# 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)
|
|
```python
|
|
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
|
|
|
|
```json
|
|
{
|
|
"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 number
|
|
- `launchingDate`: Date in milliseconds
|
|
- `plannedTime`: Planned time in milliseconds
|
|
- `forecastedOrAuditedDelay`: Delay in seconds
|
|
- `circulationState`: RUNNING, PENDING_TO_CIRCULATE, etc.
|
|
|
|
### OnePaths Response (Complete Route)
|
|
|
|
```json
|
|
{
|
|
"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 trains
|
|
- `AVLDMD` - High Speed, Long and Medium Distance
|
|
- `OTHERS` - Other
|
|
- `TRAVELERS` - Passengers
|
|
- `GOODS` - Freight
|
|
- `ALL` - All types
|
|
|
|
### State (commercialService, commercialStopType)
|
|
- `YES`
|
|
- `NOT`
|
|
- `BOTH`
|
|
|
|
---
|
|
|
|
## Common Issues and Solutions
|
|
|
|
### Issue 1: NULL Fields Cause Errors
|
|
|
|
**Problem**: Including `null` fields in JSON causes 401 errors.
|
|
|
|
```json
|
|
// 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:
|
|
|
|
```python
|
|
# 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 keys
|
|
- `apk_extracted/assets/stations_all.json` - Station database
|
|
- `apk_decompiled/sources/com/adif/elcanomovil/serviceNetworking/interceptors/auth/ElcanoAuth.java` - HMAC algorithm
|
|
|
|
### Main Analyzed Classes
|
|
- `TrafficCirculationPathRequest` - Request model
|
|
- `OneOrSeveralPathsRequest` - Path requests
|
|
- `ElcanoAuth` - HMAC-SHA256 implementation
|
|
- `ServicePaths` - URL and key definitions
|