# Api sncf

**URL:** <https://community.homey.app/t/api-sncf/126178>\
**Category:** Questions & Aide\
**Created:** [January 2, 2025, 10:18am UTC](https://community.homey.app/t/api-sncf/126178 "2025-01-02T10:18:11Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![flo2](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/flo2/32/116386_2.png) [@flo2](https://community.homey.app/u/flo2)\
**Post date:** [January 2, 2025, 10:18am UTC](https://community.homey.app/t/api-sncf/126178/1 "2025-01-02T10:18:11Z")

</div>

bonjour a tous!  
savez vous s’il existe une app qui va chercher les horaires de trains sans l’API SNCF comme nous l’avions sur Jeedom?  
cette application me manque…

merci beaucoup!!!

---

<div class="post-metadata">

**Author:** ![Nlesdain](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/nlesdain/32/101557_2.png) [@Nlesdain](https://community.homey.app/u/Nlesdain)\
**Post date:** [January 11, 2025, 12:08pm UTC](https://community.homey.app/t/api-sncf/126178/2 "2025-01-11T12:08:35Z")

</div>

J’ai également cherché, sans succès !  
Ce serait super d’être prévenu du retard de son train le matin avant d’aller au boulot !

---

<div class="post-metadata">

**Author:** ![flo2](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/flo2/32/116386_2.png) [@flo2](https://community.homey.app/u/flo2)\
**Post date:** [January 17, 2025, 11:56am UTC](https://community.homey.app/t/api-sncf/126178/3 "2025-01-17T11:56:03Z")

</div>

oui la communauté francaise d’homey est beaucoup plus petite que celle de jeedom…  
dommage

---

<div class="post-metadata">

**Author:** ![Nlesdain](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/nlesdain/32/101557_2.png) [@Nlesdain](https://community.homey.app/u/Nlesdain)\
**Post date:** [January 20, 2025, 11:39am UTC](https://community.homey.app/t/api-sncf/126178/4 "2025-01-20T11:39:24Z")

</div>

J’ai déjà du mal à récupérer un accès à l’API…  
J’ai fait une demande ici sans succès pour le moment…

---

<div class="post-metadata">

**Author:** ![Pierre\_V](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/pierre_v/32/16433_2.png) [@Pierre\_V](https://community.homey.app/u/Pierre_V)\
**Post date:** [September 16, 2025, 7:07pm UTC](https://community.homey.app/t/api-sncf/126178/5 "2025-09-16T19:07:19Z")

</div>

Un petit script pour afficher sur une horloge awtrix les horraires de départ ou d’arrivé d’un train à une gare precise  
J’ai pas encore tout tester mais si qqun veut m’aider…  
Hesitez pas à adapter si vous voulez utiliser autre chose que l’awtrix

Quand le script sera fiable, j’en ferais une app

```javascript
 const apiKey = 'XXXX-XXXXX-XXXXX-XXXXX'; // 🔒 Remplace par ta clé API SNCF a récuperer sur https://numerique.sncf.com/startup/api/token-developpeur/

const maxResults = 40; // Nombre de départs à charger

const gares = {//A completer
  "VERPILLIERE": 'stop_area:SNCF:87723395',
  "PART_DIEU": 'stop_area:SNCF:87723197',
  "ST_EXPUERY": 'stop_area:SNCF:87762906'
};

const deviceId = 'YYYYYYYYYY'; // ID DE AWTRIX
const device = await Homey.devices.getDevice({ id: deviceId });
const ipValue = device.capabilitiesObj['ip']?.value;//IP de AWTRIX

function toISO(str) {
  return str.slice(0,4) + '-' + str.slice(4,6) + '-' + str.slice(6,8) + 'T' +
          str.slice(9,11) + ':' + str.slice(11,13) + ':' + str.slice(13,15) + 'Z';
}

const options = {
  method: 'GET',
  headers: {
    'Authorization': apiKey
  }
};

async function getDataForUrl(url) {
  try {
    const response = await fetch(url, options);
    if (!response.ok) throw new Error(`Erreur API SNCF : ${response.status} ${response.statusText}`);
    return await response.json();
  } catch (error) {
    throw new Error('Erreur réseau : ' + error.message);
  }
}

async function getJourneyInfos(trainNumber, stopArea){
  const urlJourney= `https://api.sncf.com/v1/coverage/sncf/vehicle_journeys?headsign=${trainNumber}`;
  const journeyDatas = await getDataForUrl(urlJourney);
  const journeys = journeyDatas.vehicle_journeys || [];
  const disruptionDetails = journeyDatas.disruptions || [];
  const rawNow = journeyDatas.context.current_datetime;
  const rawDayNow = rawNow.split('T')[0];
  const rawDateNow = `${rawDayNow.slice(0, 4)}-${rawDayNow.slice(4, 6)}-${rawDayNow.slice(6, 8)}`;
  
  for (const journey of journeys) {
    var journeyDay = journey.id.split(':')[2];
    if( journeyDay != rawDateNow) continue;

    const stops = journey.stop_times;
    if (!stops || stops.length === 0) continue;

    const matchingStop = stops.find(stop => {
      var stopPointId = stop.stop_point?.id;
      var stopPointIdParts = stopPointId.split(':');
      var stopAreaParts = stopArea.split(':');
      return `${stopPointIdParts[1]}:${stopPointIdParts[2]}` === `${stopAreaParts[1]}:${stopAreaParts[2]}`;
    });

  
    if (!matchingStop) break;
    const departureTimeAtStopPoint = matchingStop.departure_time;
    const arrivalTimeAtStopPoint = matchingStop.arrival_time;
    if (journey.disruptions.length === 0) {
      return {
        'responseStatus': "SUCCESS",
        'contexteTime': rawNow,
        'journeyId': trainNumber,
        'journeyStartTime': journeyDay.replaceAll('-', '')+"T"+stops[0].departure_time,
        'baseStopPointDeparture': journeyDay.replaceAll('-', '')+"T"+departureTimeAtStopPoint,
        'baseStopPointArrival':journeyDay.replaceAll('-', '')+"T"+arrivalTimeAtStopPoint,
        'disruption': 'NO_DISRUPTION'
      };
    }
  
    const journeyDistruptionsIds = [];
    for( const distr of journey.disruptions){
      journeyDistruptionsIds.push(distr.id);
    }
    // Rechercher les détails des disruptions liées
    const relatedDisruptions = disruptionDetails
      .map(d => d.id)
      .map(id => disruptionDetails.find(dd => dd.id === id))
      .filter(Boolean); // supprimer les null

    const hasCancellation = relatedDisruptions.some(disruption => {
      if( !journeyDistruptionsIds.includes(disruption.id)) return false;
      const effect = disruption?.severity?.effect?.toUpperCase() || "";
      return effect === "NO_SERVICE";
    });
    if (hasCancellation) {
      return {
        'responseStatus': "SUCCESS",
        'contexteTime': rawNow,
        'journeyId': trainNumber,
        'journeyStartTime': journeyDay.replaceAll('-', '')+"T"+stops[0].departure_time,
        'baseStopPointDeparture': journeyDay.replaceAll('-', '')+"T"+departureTimeAtStopPoint,
        'baseStopPointArrival':journeyDay.replaceAll('-', '')+"T"+arrivalTimeAtStopPoint,
        'disruption': 'CANCELLED'
      };
    }else {
      return {
        'responseStatus': "SUCCESS",
        'contexteTime': rawNow,
        'journeyId': trainNumber,
        'journeyStartTime': journeyDay.replaceAll('-', '')+"T"+stops[0].departure_time,
        'baseStopPointDeparture': journeyDay.replaceAll('-', '')+"T"+departureTimeAtStopPoint,
        'baseStopPointArrival':journeyDay.replaceAll('-', '')+"T"+arrivalTimeAtStopPoint,
        'disruption': 'HAS_DISRUPTIONS'
      };
    }
  }
  return {
    'responseStatus': "ERROR",
    'journeyId': trainNumber
  };
}

async function sendMessageToAwTrix(waringMessage, appID, msg, lifeTime){
  this.log('Envoie du message: '+msg);
  this.log('Durée du message: '+lifeTime);
  const urlAwtrix = 'http://'+ipValue+'/api/custom?name='+appID;
  const ttlAwtrix = lifeTime;//Math.round((timeReal - timeContexte) / 1000);
  const payload = {
    "text": msg,
    "repeat": 3,
    "icon":waringMessage?"alertwarning":"train",
    "lifetime": ttlAwtrix
  };

  try {
    const response = await fetch(urlAwtrix, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(payload)
    });
    
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
  } catch (error) {
    console.error('Erreur lors du POST:', error);
  }
}

async function computeAndSendInfosForDepartureJourney(journeyInfos, stopArea){
  const rawContextDateTime = journeyInfos.contexteTime;
  const timeContexte = new Date(toISO(rawContextDateTime));

  const rawBaseDateTime = journeyInfos.baseStopPointDeparture;
  const timeBase = new Date(toISO(rawBaseDateTime));
  const hours = timeBase.getUTCHours().toString().padStart(2, '0');
  const minutes = timeBase.getUTCMinutes().toString().padStart(2, '0');
  const rawHourBase = `${hours}:${minutes}`

  if (journeyInfos.disruption == "CANCELLED" && timeBase > timeContexte) {
    sendMessageToAwTrix(true, "train"+journeyInfos.journeyId,`${rawHourBase} SUPPRIME`, Math.round((timeBase - timeContexte ) / 1000))
    return;
  }

  const urlStopPointInfo = `https://api.sncf.com/v1/coverage/sncf/stop_areas/${stopArea}/departures?count=${maxResults}`;
  const data = await getDataForUrl(urlStopPointInfo);
  // Trouve le train par son numéro exact
  const train = data.departures.find(dep => {
    const tripNum = dep.display_informations?.trip_short_name || '';
    return tripNum === journeyInfos.journeyId;
  });

  if (!train){
    return;
  }

  let messageFinal = rawHourBase;

  const rawRealDateTime = train.stop_date_time?.departure_date_time;
  const timeReal = new Date(toISO(rawRealDateTime));

  var delaySec = 0;
  if( rawBaseDateTime != rawRealDateTime){
    delaySec = Math.round((timeReal - timeBase) / 1000);
  }
  const delayMin = Math.round(delaySec / 60);

  if (delayMin > 0){
    messageFinal += " +"+delayMin+" MN";
  }else{
    messageFinal += " A L'HEURE";
  }
  sendMessageToAwTrix( delayMin > 0, "train"+journeyInfos.journeyId, messageFinal, Math.round((timeReal - timeContexte) / 1000))
}

async function computeAndSendInfosForArrivalJourney(journeyInfos, stopArea){
  const rawContextDateTime = journeyInfos.contexteTime;
  const timeContexte = new Date(toISO(rawContextDateTime));

  const rawBaseDateTimeArrival = journeyInfos.baseStopPointArrival;
  const timeBaseArrival = new Date(toISO(rawBaseDateTimeArrival));
  const hoursArrival = timeBaseArrival.getUTCHours().toString().padStart(2, '0');
  const minutesArrival = timeBaseArrival.getUTCMinutes().toString().padStart(2, '0');
  const rawHourBaseArrival = `${hoursArrival}:${minutesArrival}`

  if (journeyInfos.disruption == "CANCELLED" && timeBaseArrival > timeContexte) {
    sendMessageToAwTrix(true, "train"+journeyInfos.journeyId,`${rawHourBaseArrival} SUPPRIME`, Math.round((timeBase - timeContexte ) / 1000))
    return;
  }
  const urlStopPointInfo = `https://api.sncf.com/v1/coverage/sncf/stop_areas/${stopArea}/arrivals?count=${maxResults}`;
  const data = await getDataForUrl(urlStopPointInfo);

  // Trouve le train par son numéro exact
  const train = data.arrivals.find(dep => {
    const tripNum = dep.display_informations?.trip_short_name || '';
    return tripNum === journeyInfos.journeyId;
  });
  if (!train){
    return;
  }

  let messageFinal = rawHourBaseArrival;
  const rawRealDateTimeArrival = train.stop_date_time?.arrival_date_time;
  const timeRealArrival = new Date(toISO(rawRealDateTimeArrival));
  var delaySec = 0;
  if( rawBaseDateTimeArrival != rawRealDateTimeArrival){
    delaySec = Math.round((timeRealArrival - timeBaseArrival) / 1000);
  }

  const delayMin = Math.round(delaySec / 60);

  if (delayMin > 0){
    messageFinal += " +"+delayMin+" MN";
  }else{
    messageFinal += " A L'HEURE";
  }
  sendMessageToAwTrix( delayMin > 0, "train"+journeyInfos.journeyId, messageFinal, Math.round((timeRealArrival - timeContexte) / 1000))
}

/*Usage exemple
const jsonArgs = {
  "mode": "ARRIVAL",
  "stopPoint": "PART_DIEU",
  "trains": ["6669", "875718"]
};

const jsonArgs = {
  "mode": "DEPARTUR",
  "stopPoint": "PART_DIEU",
  "trains": ["6669", "875718"]
};*/
const jsonArgs = JSON.parse(args[0]);

const mode = jsonArgs.mode;
for (const trainNumber of jsonArgs.trains) {
  const journeyInfos= await getJourneyInfos(trainNumber, gares[jsonArgs.stopPoint]);
  if( !journeyInfos || journeyInfos.responseStatus == "ERROR")
    return;
  if( mode == "DEPARTURE")
    computeAndSendInfosForDepartureJourney(journeyInfos, gares[jsonArgs.stopPoint]);
  else if( mode == "ARRIVAL"){
    computeAndSendInfosForArrivalJourney(journeyInfos, gares[jsonArgs.stopPoint]);
  }
}

```
