Aller au contenu

Gares ferroviaires

L'API publie le référentiel des gares de voyageurs SNCF : 2782 gares, chacune avec son code UIC, sa position et son rattachement administratif. C'est la seule partie de l'API qui n'appartient à personne — pas de groupe, pas de clé, pas de droits.

Ce qui distingue cet endpoint

Reste de l'API Gares ferroviaires
Périmètre /groups/<group_id>/… aucun groupe
Authentification clé d'API ou session requise aucune
Origine de la donnée le GTFS de votre réseau et les remontées terrain le jeu de données ouvert SNCF gares-de-voyageurs
Change quand en continu, pendant le service quand la SNCF révise son jeu de données, et que le référentiel est rechargé à la main

La conséquence à garder en tête : rien ici n'est temps réel, et rien ici n'est propre à votre réseau. C'est un index, fait pour être lu avant tout le reste — typiquement pour désigner par code UIC la gare à laquelle correspond un arrêt de votre réseau.

Les deux endpoints

Endpoint Rôle
GET /api/v4/rail-stations Cherche dans le référentiel par nom ou par proximité
GET /api/v4/rail-stations/<uic> Lit une gare à partir d'un de ses codes UIC

Chercher par nom

q est cherché n'importe où dans le nom de la gare, sans tenir compte de la casse, des accents ni des séparateurs. chateauroux trouve Châteauroux, et saint germain trouve Saint-Germain-en-Laye Bel-Air – Fourqueux. Il n'y a rien à normaliser de votre côté.

curl --fail --location --get \
  --data-urlencode "q=Châteauroux" \
  "https://api.pysae.com/api/v4/rail-stations"

La réponse porte la page et son total, comme tout endpoint paginé de l'API :

{
  "items": [
    {
      "uic": ["87597005"],
      "name": "Châteauroux",
      "short_label": "CTX",
      "segments_drg": ["A"],
      "lat": 46.809737,
      "lon": 1.699536,
      "insee_code": "36044",
      "source_ref": "0054eed7-a134-4fca-b217-d65e4d2ea153"
    }
  ],
  "total": 1,
  "page": 1,
  "page_size": 50
}

Chercher par proximité

near=<latitude>,<longitude> ordonne les résultats par distance croissante et ajoute distance_meters à chacun. radius, en mètres, borne la recherche ; il vaut 10 km par défaut et est ignoré si near est absent.

GET /api/v4/rail-stations?near=48.880185,2.355151&radius=3000 renvoie les gares à moins de 3 km de Paris Gare du Nord, la plus proche d'abord.

Les deux filtres se combinent : q et near ensemble renvoient les gares dont le nom correspond, dans le rayon, ordonnées par distance.

Une gare, plusieurs codes UIC

uic est une liste, et non une valeur unique : c'est le point qui surprend à l'intégration. La SNCF attribue plusieurs codes à une même gare quand plusieurs réseaux la desservent : Paris Gare du Nord en porte trois, Avignon TGV deux. Les conséquences :

  • N'importe lequel des codes d'une gare la résout. GET /api/v4/rail-stations/87271007 et GET /api/v4/rail-stations/87271031 renvoient tous deux Paris Gare du Nord.
  • Un code appartient à une seule gare. Aucun code UIC n'est partagé, donc en résoudre un n'est jamais ambigu.
  • segments_drg est une liste pour la même raison. Le segment de fréquentation DRG (A le plus fréquenté, C le moins) est donné par code, donc une gare à plusieurs codes peut porter plusieurs segments.

Stocker un seul code de votre côté suffit à désigner une gare, à condition de lire uic comme une liste au moment de comparer.

Rattacher un arrêt de votre réseau à une gare

Le rattachement transforme l'index ci-dessus en donnée de service : il désigne, pour un arrêt de votre GTFS, la gare dont il affiche les départs, et il délimite les gares dont les données ferroviaires sont ingérées pour votre réseau. Contrairement au référentiel, cette partie est propre à chaque groupe et portée par la fonctionnalité payante rail_data : sans elle, les endpoints répondent 403 avec le code LOCKED_FEATURE.

GET /api/v4/groups/<group_id>/rail/station-links liste les rattachements du groupe. Chaque lien porte l'arrêt (stop_id, avec stop_name en libellé), la gare (uic) et un indicateur is_orphan : true quand l'arrêt n'existe pas — ou plus — dans le GTFS actuellement publié du groupe. Un lien orphelin n'est jamais rejeté ni supprimé d'office : vos arrêts sont réimportés à chaque GTFS, et le lien doit survivre à un réimport qui fait momentanément disparaître son arrêt. À vous d'afficher l'indicateur et de corriger ou retirer le lien.

Chaque lien porte aussi un ingestion_status, qui dit où en sont les données ferroviaires de la gare : ready quand ses départs sont interrogeables, pending pendant que la gare est en cours d'ingestion, not_found quand aucun train du feed national courant ne la dessert. Rattacher une gare encore absente du magasin ferroviaire répond immédiatement avec pending et prépare ses données en asynchrone — l'affaire de quelques minutes en général ; re-listez les liens jusqu'à ce qu'il passe ready, et affichez « préparation en cours » plutôt qu'un tableau des départs vide entre-temps. not_found n'est pas une erreur : le lien est conservé, et le rafraîchissement quotidien le réévalue à chaque nouveau feed SNCF.

L'écriture est réservée au rôle admin du groupe :

  • PUT /api/v4/groups/<group_id>/rail/station-links crée le lien (stop_id, uic) ou, s'il existe déjà, rafraîchit son stop_name — l'appel est idempotent, le resoumettre ne crée jamais de doublon. Le stop_id n'est pas validé (voir is_orphan ci-dessus) ; le code UIC, lui, doit appartenir au référentiel, sinon la réponse est un 404 RAIL_STATION_NOT_FOUND.
  • DELETE /api/v4/groups/<group_id>/rail/station-links/<stop_id>/<uic> retire le lien ; sur un lien inexistant, la réponse est un 404 RAIL_STATION_LINK_NOT_FOUND.

Lire le tableau des départs d'une gare rattachée

C'est ce à quoi sert le rattachement : GET /api/v4/groups/<group_id>/rail/departures?stop_id=<stop_id> renvoie les trains au départ des gares rattachées à cet arrêt de votre réseau, horaires théoriques et temps réel confondus. Même fonctionnalité payante rail_data que les rattachements : sans elle, 403 LOCKED_FEATURE.

L'arrêt est désigné par son stop_id dans votre GTFS — pas par un code UIC. Un arrêt rattaché à plusieurs gares renvoie les départs de toutes, dans une seule liste.

La fenêtre

from et to sont des instants absolus, en ISO 8601 ou en epoch secondes, comme partout ailleurs dans l'API. Les deux bornes sont incluses.

Ce que vous passez Fenêtre appliquée
rien maintenantmaintenant + 1 h
from seul fromfrom + 1 h
to seul to - 1 hto
les deux tel quel, 24 h au maximum

Une fenêtre inversée ou plus large que 24 h est refusée avec un 400 INVALID_RAIL_DEPARTURES_WINDOW : au-delà, vous demandez un catalogue horaire, pas un tableau des départs.

La réponse

Une liste triée par scheduled_departure croissant — l'horaire théorique, celui sur lequel la fenêtre filtre, pour que l'ordre ne bouge pas quand les estimations évoluent.

Champ Rôle
scheduled_departure L'horaire théorique. Toujours présent
estimated_departure L'horaire estimé par le temps réel, null en son absence
delay_seconds L'écart signé entre les deux, négatif pour un train en avance
status scheduled, on_time, delayed, early, canceled, skipped, added
destination Ce qu'il faut afficher : stop_headsign, sinon trip_headsign, sinon le nom du dernier arrêt du train
route_type Le mode, route_type GTFS
platform La voie, si la source la publie
disruptions Les alertes temps réel qui couvrent ce train, sa ligne, la gare ou tout le réseau
service_date Le jour de service (YYYYMMDD), qui n'est pas le jour calendaire pour un train partant après minuit

Deux pièges à connaître :

  • status: scheduled signifie « pas de temps réel », pas « à l'heure ». Quand aucune entité temps réel ne couvre le train, le tableau est servi sur ses seuls horaires théoriques : estimated_departure, delay_seconds restent null et disruptions est vide. L'absence de temps réel dégrade l'affichage, elle ne fait jamais échouer l'appel — et elle se fait train par train, pas tableau par tableau.
  • Aucun filtre par mode. Tous les modes desservant la gare sont renvoyés, autocars de substitution Car TER (route_type 3) compris : ils font partie du service que le voyageur doit voir. Filtrez côté client sur route_type si vous ne voulez que les trains.

platform est optionnel de bout en bout : il vient du platform_code du GTFS de la source, que le feed national SNCF ne publie pas. Attendez-vous à null sur les données SNCF, et n'en faites pas une colonne obligatoire de votre affichage.

Un arrêt rattaché à aucune gare répond 200 avec une liste vide — c'est un état de configuration, pas une erreur. Idem pour une gare dont les données ne sont pas encore ingérées : lisez ingestion_status sur le rattachement pour distinguer « en préparation » de « aucun train ».

Variante par code UIC — interne

GET /api/v4/groups/<group_id>/rail/departures/<uic> sert le même tableau, adressé par gare au lieu d'arrêt. C'est un utilitaire de support et de débogage, absent de la spécification OpenAPI publique : aucune intégration ne doit s'indexer sur un code UIC, qui est un identifiant du référentiel et non de votre réseau.

Le périmètre reste celui du groupe appelant : un code UIC qu'un autre groupe a rattaché mais pas celui-ci répond 200 avec une liste vide, jamais les données de l'autre groupe.

Brancher une source ferroviaire propre au groupe

Par défaut, un groupe équipé de la fonctionnalité rail_data lit la source nationale SNCF, ingérée pour toute la plateforme. L'intégration rail_data permet de superposer la propre source GTFS d'un client : ses données sont ingérées sous un identifiant de source propre au groupe (<group_id>:rail_data), filtrées sur les seules gares que ce groupe a rattachées, et priment sur la source nationale en cas de collision. Sans intégration déclarée, le groupe lit uniquement la source nationale ; la déclarer ajoute la surcharge, elle ne remplace rien pour les autres groupes.

À la lecture, le tableau des départs consolide les deux sources dans cet ordre : la source nationale d'abord, celle du groupe par-dessus. Une collision se résout par remplacement, jamais champ par champ — la donnée client tient toute la place de la donnée SNCF homonyme, et un champ que son feed ne porte pas vaut « absent », pas « hérité de la SNCF ». Les clés naturelles de collision :

Entité Clé
gare codes_uic
ligne route_id
passage — une ligne du tableau (trip_id, service_date, uic)
arrêt stop_id
entité temps réel (TripUpdate, alerte) entity_id du FeedEntity

La clé du passage porte la gare, et pas seulement la course : un train desservant deux gares rattachées au même arrêt rend une ligne par gare, pas une seule. C'est le uic qui y figure plutôt que le stop_id, parce que c'est l'identité de gare partagée entre les deux sources — votre surcharge remplace donc bien le passage SNCF homonyme même si les deux feeds nomment la voie différemment.

Le source_id de chaque ligne du tableau dit laquelle des deux sources l'a produite. C'est délibérément l'inverse du fusionneur multi-GTFS de la v5 (merge_trips_to_v4_schemas), qui unit les listes et somme les agrégats : ici on surcharge, d'où un chemin de lecture dédié.

La configuration passe par les endpoints d'intégration, réservés au rôle admin du groupe :

  • PUT /api/v3/groups/<group_id>/integrations/rail_data crée ou remplace la configuration ;
  • GET /api/v3/groups/<group_id>/integrations/rail_data la relit — api_key y est toujours masquée ;
  • DELETE /api/v3/groups/<group_id>/integrations/rail_data la retire et purge immédiatement les données déjà ingérées de cette source ; le groupe retombe sur la seule source nationale.

Les champs :

Champ Rôle
dataset_url La source GTFS : soit un jeu de données transport.data.gouv.fr (URL de la page ou de l'API — le zip est alors résolu via l'API des datasets, comme la source nationale), soit l'URL directe d'un zip GTFS
gtfs_rt_trip_updates_url Optionnel — flux GTFS-RT TripUpdates de la source
gtfs_rt_service_alerts_url Optionnel — flux GTFS-RT ServiceAlerts de la source
api_key Optionnel — envoyée en Authorization: Bearer sur chaque requête vers la source
enabled false suspend la source sans perdre sa configuration ; ses données sont purgées au rafraîchissement suivant

Toutes les URLs doivent être en https : une URL http ou d'un autre schéma est rejetée à la validation, et l'ingestion refuse de contacter une adresse IP non publique.

Une source purement statique est acceptée : les deux URLs GTFS-RT peuvent rester vides. Les données sont rafraîchies par le batch quotidien du magasin ferroviaire ; un zip direct est considéré comme changé d'après ses en-têtes ETag / Last-Modified, et réingéré à chaque passage si le serveur n'en expose aucun.

Bonnes pratiques

  • Mettez en cache ce que vous lisez. Le référentiel est statique entre deux rechargements manuels ; le réinterroger à chaque action utilisateur n'apporte rien.
  • Indexez sur le code UIC, pas sur le nom. Les noms sont retouchés par la SNCF ; les codes sont l'identifiant stable. source_ref permet de retrouver l'enregistrement d'origine dans le jeu de données source, pour audit.
  • Un code inconnu répond 404, avec le code RAIL_STATION_NOT_FOUND — à distinguer d'une recherche vide, qui est un 200 avec total: 0.
  • Ne mettez pas le tableau des départs en cache comme le référentiel. Les flux temps réel ferroviaires se rafraîchissent toutes les deux minutes environ : interroger departures plus souvent que ça renvoie les mêmes estimations.