Files
adif-api-reverse-engineering/docs/API_DOCUMENTATION.md
Dasemu 68fac80520 Refactor: reorganización completa del proyecto y documentación consolidada
Esta actualización reorganiza el proyecto de reverse engineering de la API de ADIF con los siguientes cambios:

Estructura del proyecto:
- Movida documentación principal a carpeta docs/
- Consolidados archivos markdown redundantes en CLAUDE.md (contexto completo del proyecto)
- Organización de tests en carpeta tests/ con README explicativo
- APK renombrado de base.apk a adif.apk para mayor claridad

Archivos de código:
- Movidos adif_auth.py y adif_client.py a la raíz (antes en api_testing_scripts/)
- Eliminados scripts de testing obsoletos y scripts de Frida no utilizados
- Nuevos tests detallados: test_endpoints_detailed.py y test_onepaths_with_real_trains.py

Descubrimientos:
- Documentados nuevos hallazgos en docs/NEW_DISCOVERIES.md
- Actualización de onePaths funcionando con commercialNumber real (devuelve 200)
- Extraídos 1587 códigos de estación en station_codes.txt

Configuración:
- Actualizado .gitignore con mejores patrones para Python e IDEs
- Eliminados archivos temporales de depuración y logs
2025-12-05 11:22:13 +01:00

13 KiB

Adif Elcano API - Ingeniería Reversa

Documentación completa de la API de ADIF (El Cano Móvil) obtenida mediante ingeniería reversa de la aplicación móvil.

Estado: Proyecto completado con éxito Última actualización: 2025-12-05

🎯 Resumen de Funcionalidad

Característica Estado Notas
Autenticación HMAC-SHA256 Implementada Algoritmo completo y validado
Endpoints funcionales 4/8 (50%) departures, arrivals, onepaths, stationsobservations
Endpoints bloqueados ⚠️ 4/8 401 Unauthorized por permisos limitados
Códigos de estación 1587 Extraídos de assets/stations_all.json
Claves extraídas Completas ACCESS_KEY y SECRET_KEY de libapi-keys.so

URLs Base

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/

Headers de Autenticación

Para API de Circulaciones y Composiciones

Content-Type: application/json;charset=utf-8
User-key: f4ce9fbfa9d721e39b8984805901b5df

Para API de Estaciones

Content-Type: application/json;charset=utf-8
User-key: 0d021447a2fd2ac64553674d5a0c1a6f

Para Avisa Login

Authorization: Basic YXZpc3RhX2NsaWVudF9hbmRyb2lkOjh5WzZKNyFmSjwhXypmYXE1NyNnOSohNElwa2MjWC1BTg==

Para Suscripciones

Authorization: Basic ZGVpbW9zOmRlaW1vc3R0
X-CanalMovil-Authentication: <token>
X-CanalMovil-deviceID: <device_id>
X-CanalMovil-pushID: <push_id>

Tokens

REGISTRATION_TOKEN = Bearer b9034774-c6e4-4663-a1a8-74bf7102651b

Endpoints

Estaciones

Obtener todas las estaciones

GET /portroyalmanager/secure/stations/allstations/reducedinfo/{token}/
Base: https://estaciones.api.adif.es
Headers: User-key para estaciones

Obtener detalles de una estación

POST /portroyalmanager/secure/stations/onestation/
Base: https://estaciones.api.adif.es
Headers: User-key para estaciones

Body:
{
  "stationCode": "string"
}

Observaciones de estación

POST /portroyalmanager/secure/stationsobservations/
Base: https://estaciones.api.adif.es
Headers: User-key para estaciones

Body:
{
  "stationCodes": ["string"]  // Array de códigos de estación (requerido)
}

Ejemplo:
{
  "stationCodes": ["60000", "71801"]
}

Circulaciones (Trenes)

Salidas (Departures)

POST /portroyalmanager/secure/circulationpaths/departures/traffictype/
Base: https://circulacion.api.adif.es
Headers: User-key para circulaciones

Body:
{
  "commercialService": "YES|NOT|BOTH",      // Estado del servicio comercial
  "commercialStopType": "YES|NOT|BOTH",     // Tipo de parada comercial
  "destinationStationCode": "string|null",  // Código estación destino (opcional)
  "originStationCode": "string|null",       // Código estación origen (opcional)
  "page": {
    "pageNumber": number                     // Número de página
  },
  "stationCode": "string|null",             // Código estación (opcional)
  "trafficType": "CERCANIAS|AVLDMD|OTHERS|TRAVELERS|GOODS|ALL"  // Tipo de tráfico
}

Ejemplo:
{
  "commercialService": "BOTH",
  "commercialStopType": "BOTH",
  "destinationStationCode": null,
  "originStationCode": null,
  "page": {
    "pageNumber": 0
  },
  "stationCode": "60000",
  "trafficType": "ALL"
}

Llegadas (Arrivals)

POST /portroyalmanager/secure/circulationpaths/arrivals/traffictype/
Base: https://circulacion.api.adif.es
Headers: User-key para circulaciones

Body: Mismo formato que departures (TrafficCirculationPathRequest)

Entre estaciones

POST /portroyalmanager/secure/circulationpaths/betweenstations/traffictype/
Base: https://circulacion.api.adif.es
Headers: User-key para circulaciones

Body: Mismo formato que departures (TrafficCirculationPathRequest)

Una ruta específica

POST /portroyalmanager/secure/circulationpathdetails/onepaths/
Base: https://circulacion.api.adif.es
Headers: User-key para circulaciones

Body:
{
  "allControlPoints": boolean|null,        // Todos los puntos de control (opcional)
  "commercialNumber": "string|null",       // Número comercial del tren (opcional)
  "destinationStationCode": "string|null", // Código estación destino (opcional)
  "launchingDate": number|null,            // Fecha de lanzamiento en timestamp (Long) (opcional)
  "originStationCode": "string|null"       // Código estación origen (opcional)
}

Ejemplo:
{
  "allControlPoints": true,
  "commercialNumber": "04138",
  "destinationStationCode": "60000",
  "launchingDate": 1733356800000,
  "originStationCode": "71801"
}

Varias rutas

POST /portroyalmanager/secure/circulationpathdetails/severalpaths/
Base: https://circulacion.api.adif.es
Headers: User-key para circulaciones

Body: Mismo formato que onepaths (OneOrSeveralPathsRequest)

Composiciones

Composición de tren

POST /portroyalmanager/secure/circulationpaths/compositions/path/
Base: https://circulacion.api.adif.es
Headers: User-key para circulaciones

Body: Same as onepaths request

Avisa (Sistema de incidencias)

Login

POST /avisa-ws/api/token
Base: https://avisa.adif.es
Headers: Basic auth token para Avisa

Query params:
- grant_type: "password"
- username: <username>
- password: <password>

Registrar cliente

POST /avisa-ws/api/v1/client
Base: https://avisa.adif.es

Estaciones Avisa

GET /avisa-ws/api/v1/station
Base: https://avisa.adif.es

Categorías de estación

GET /avisa-ws/api/v1/category
Base: https://avisa.adif.es

Incidencias

GET /avisa-ws/api/v1/incidence
POST /avisa-ws/api/v1/incidence
GET /avisa-ws/api/v1/incidence/{incidenceId}
Base: https://avisa.adif.es

Suscripciones

Listar suscripciones

GET /api/subscriptions?platform=300
Base: https://elcanoweb.adif.es
Headers: Basic auth + X-CanalMovil headers

Crear suscripción

POST /api/subscriptions
Base: https://elcanoweb.adif.es
Headers: Basic auth + X-CanalMovil headers

Silenciar suscripción

PUT /api/subscriptions/{id}/mute
Base: https://elcanoweb.adif.es
Headers: Basic auth + X-CanalMovil headers

📊 Estructura de Respuestas

Respuesta de Departures/Arrivals

{
  "commercialPaths": [
    {
      "commercialPathInfo": {
        "timestamp": 1764927847100,
        "commercialPathKey": {
          "commercialCirculationKey": {
            "commercialNumber": "90399",
            "launchingDate": 1764889200000
          },
          "originStationCode": "10620",
          "destinationStationCode": "60004"
        },
        "commercialOriginStationCode": "10620",
        "commercialDestinationStationCode": "60004",
        "line": null,
        "core": null,
        "observation": null,
        "trafficType": "CERCANIAS",
        "opeProComPro": {
          "operator": "RF",
          "product": "C",
          "commercialProduct": " "
        },
        "compositionData": {
          "compositionLenghtType": null,
          "compositionFloorType": null,
          "accesible": false
        },
        "announceableStations": ["60004"]
      },
      "passthroughStep": {
        "stopType": "NO_STOP",
        "announceable": false,
        "stationCode": "10200",
        "arrivalPassthroughStepSides": null,
        "departurePassthroughStepSides": {
          "plannedTime": 1764927902000,
          "forecastedOrAuditedDelay": 2175,
          "timeType": "FORECASTED",
          "plannedPlatform": "2",
          "sitraPlatform": null,
          "ctcPlatform": null,
          "operatorPlatform": null,
          "resultantPlatform": "PLANNED",
          "preassignedPlatform": null,
          "observation": null,
          "circulationState": "RUNNING",
          "announceState": "NORMAL",
          "technicalCirculationKey": {
            "technicalNumber": "90399",
            "technicalLaunchingDate": 1764889200000
          },
          "visualEffects": {
            "inmediateDeparture": false,
            "countDown": false,
            "showDelay": true
          }
        }
      }
    }
  ]
}

Campos importantes:

  • commercialNumber: Número comercial del tren
  • launchingDate: Fecha de salida en milisegundos (timestamp)
  • plannedTime: Hora planificada en milisegundos
  • forecastedOrAuditedDelay: Retraso en segundos
  • circulationState: Estado del tren (RUNNING, PENDING_TO_CIRCULATE, etc.)
  • plannedPlatform: Andén planificado

Respuesta de OnePaths (Ruta Completa)

{
  "commercialPaths": [
    {
      "commercialPathInfo": { /* Igual que en departures */ },
      "passthroughSteps": [  // ← Array con TODAS las paradas
        {
          "stopType": "COMMERCIAL",
          "announceable": false,
          "stationCode": "10620",
          "arrivalPassthroughStepSides": null,
          "departurePassthroughStepSides": {
            "plannedTime": 1764918000000,
            "forecastedOrAuditedDelay": 430,
            "timeType": "AUDITED",
            "plannedPlatform": "1",
            "circulationState": "RUNNING",
            "showDelay": false
          }
        },
        {
          "stopType": "NO_STOP",
          "stationCode": "C1062",
          "arrivalPassthroughStepSides": { /* ... */ },
          "departurePassthroughStepSides": { /* ... */ }
        }
        // ... más paradas
      ]
    }
  ]
}

Diferencia clave:

  • departures/arrivalspassthroughStep (singular, solo la estación consultada)
  • onepathspassthroughSteps (plural, array con todas las paradas del recorrido)

Respuesta de Station Observations

{
  "stationObservations": [
    {
      "stationCode": "10200",
      "observation": "Texto de la observación"
    }
  ]
}

Status Codes

Código Significado Causa
200 Success Petición exitosa con datos
204 ⚠️ No Content Autenticación correcta pero sin datos disponibles
400 Bad Request Payload incorrecto, campo requerido faltante o formato inválido
401 Unauthorized Sin permisos (claves con perfil limitado)

Nota importante sobre 204: Un status 204 NO es un error. Significa que la autenticación y el payload son correctos, pero no hay datos disponibles para esa consulta específica.

Tipos de Datos

TrafficType (Tipos de tráfico)

  • CERCANIAS - Trenes de cercanías
  • AVLDMD - Alta Velocidad, Larga y Media Distancia
  • OTHERS - Otros tipos de tráfico
  • TRAVELERS - Viajeros
  • GOODS - Mercancías
  • ALL - Todos los tipos

State (Estados para comercialService y comercialStopType)

  • YES - Sí
  • NOT - No
  • BOTH - Ambos

PageInfoDTO

{
  "pageNumber": 0
}

Notas de Seguridad

  • La app usa certificate pinning con claves públicas específicas
  • Los tokens están hardcodeados en la aplicación
  • Las User-keys son diferentes para cada servicio (estaciones vs circulaciones)
  • El token de registro b9034774-c6e4-4663-a1a8-74bf7102651b está en el código

🗺️ Códigos de Estación

Total: 1587 estaciones disponibles Archivo: station_codes.txt (raíz del proyecto) Fuente: apk_extracted/assets/stations_all.json

Formato

código    nombre    tipos_tráfico

Top Estaciones

10200    Madrid Puerta de Atocha          AVLDMD
10302    Madrid Chamartín-Clara Campoamor AVLDMD
71801    Barcelona Sants                  AVLDMD,CERCANIAS
60000    València Nord                    AVLDMD
11401    Sevilla Santa Justa              AVLDMD
50003    Alacant Terminal                 AVLDMD,CERCANIAS
54007    Córdoba Central                  AVLDMD
79600    Zaragoza Portillo                AVLDMD,CERCANIAS
03216    València J.Sorolla               AVLDMD
04040    Zaragoza Delicias                AVLDMD,CERCANIAS

Notas de Implementación

Esta documentación se ha obtenido mediante ingeniería reversa del código decompilado de la aplicación Android de ADIF Elcano.

Herramientas utilizadas:

  • Ghidra: Extracción de claves de libapi-keys.so
  • JADX: Decompilación del APK
  • Python 3: Implementación del cliente
  • Frida: Análisis dinámico (opcional)

Clases principales analizadas:

  • com.adif.elcanomovil.serviceNetworking.circulations.model.request.TrafficCirculationPathRequest
  • com.adif.elcanomovil.serviceNetworking.circulations.model.request.OneOrSeveralPathsRequest
  • com.adif.elcanomovil.serviceNetworking.stationObservations.model.StationObservationsRequest
  • com.adif.elcanomovil.serviceNetworking.circulations.model.request.CirculationPathRequest (interface)
  • com.adif.elcanomovil.serviceNetworking.circulations.model.request.TrafficType (enum)
  • com.adif.elcanomovil.serviceNetworking.interceptors.auth.ElcanoAuth (algoritmo HMAC)

Archivos clave:

  • apk_extracted/lib/x86_64/libapi-keys.so - Claves de autenticación
  • apk_extracted/assets/stations_all.json - Base de datos de estaciones
  • apk_decompiled/sources/com/adif/elcanomovil/ - Código fuente decompilado

Última actualización: 2025-12-05 Estado: Proyecto completado con éxito