Aller au contenu

Authentification

Tous les endpoints protégés de l'API attendent une preuve d'identité dans l'en-tête de la requête. Elle repose toujours sur un identifiant obtenu au préalable : une session (éventuellement un jeton OpenID Connect) ou une clé d'API.

Une famille d'endpoints reste volontairement hors de tout cela : le référentiel des gares ferroviaires est une donnée ouverte rattachée à aucun groupe, et n'attend aucun identifiant.

Obtenir une session

1. Découvrir le provider du groupe

GET /api/v4/auth/provider?group_id=<group_id> indique comment un groupe s'authentifie : {"provider": "legacy"} pour un login e-mail / mot de passe, ou {"provider": "auth0", "auth0": {"domain": "...", "connection": "..."}} lorsque le groupe est délégué à un provider OpenID Connect.

curl --fail --location \
  "https://api.pysae.com/api/v4/auth/provider?group_id=${GROUP_ID}"

2. Ouvrir la session

Groupe legacy — POST /api/v4/login. Corps application/x-www-form-urlencoded avec email et password. La réponse renvoie {"session_id": "...", "expires_days": ...}.

curl --fail --location \
  --data-urlencode "email=${EMAIL}" \
  --data-urlencode "password=${PASSWORD}" \
  "https://api.pysae.com/api/v4/login"

Automatisation — POST /api/v4/login-scoped. Destiné à l'automatisation : les paramètres de requête scopes et expire bornent la session (portée et date d'expiration). Corps identique (email, password), réponse identique.

curl --fail --location \
  --data-urlencode "email=${EMAIL}" \
  --data-urlencode "password=${PASSWORD}" \
  "https://api.pysae.com/api/v4/login-scoped?scopes=read&expire=2026-12-31"

Groupe auth0 — jeton OpenID Connect. Obtenir un JWT auprès du provider OpenID Connect renvoyé par auth/provider, puis l'utiliser en Authorization: Bearer <jwt>. Le login e-mail / mot de passe est refusé pour ces groupes.

Le session_id (ou le JWT) obtenu s'emploie ensuite dans les requêtes protégées, cf. la section « Mécanismes acceptés ». GET /api/v4/logout ferme la session (réponse 204) :

curl --request GET \
  --header "Authorization: Bearer <session_id>" \
  "https://api.pysae.com/api/v4/logout"

Une session cesse également d'être valide dès que le compte utilisateur qui la porte est supprimé : ses sessions sont révoquées avec lui, sans attendre leur date d'expiration.

Les anciennes routes de déconnexion GET /api/v2/logout et GET /auth/logout acceptent respectivement next et redirect pour revenir à une page locale. Une destination externe est remplacée par /.

Obtenir une clé d'API

Une clé d'API est un identifiant persistant et révocable, lié à un groupe et à un principal (un utilisateur, un appareil ou un rôle). Elle évite de manipuler un mot de passe dans un système automatisé.

Sa création passe par un appel API authentifié par une session : il faut donc d'abord ouvrir une session (ci-dessus) disposant du rôle admin. POST /api/v4/groups/{group_id}/api-keys crée la clé et la renvoie dans le champ value. Le corps déclare exactement un principal : user_id, device_id ou role (ce dernier accompagné de group_ids) ; expire borne sa durée de validité. Donnez-lui un label (de 1 à 100 caractères) qui nomme l'intégration qui la détiendra.

Laissez scopes de côté. Ce champ restreint la clé aux quelques routes qui déclarent une portée correspondante et vise les identifiants de service internes ; une clé portant une portée qu'aucune route ne déclare est refusée à la création, et une portée sans rapport ne mène nulle part. Sans lui, la clé atteint tout ce que son rôle autorise.

La clé n'est communiquée qu'une seule fois

Le champ value n'apparaît que dans la réponse à cette création : conservez-le dès réception. L'API n'en garde qu'une empreinte, et aucun autre endpoint ne le renvoie. Une clé égarée ne se retrouve pas — elle se révoque et se recrée.

body="$(printf '{"user_id": "%s"}' "${USER_ID}")"

curl --fail --location \
  --request POST \
  --header "Authorization: Bearer ${SESSION_ID}" \
  --header "Content-Type: application/json" \
  --data "${body}" \
  "https://api.pysae.com/api/v4/groups/${GROUP_ID}/api-keys"

Les clés d'un groupe se listent via GET /api/v4/groups/{group_id}/api-keys et se suppriment via DELETE /api/v4/groups/{group_id}/api-keys/{api_key_id}. Une clé couvrant plusieurs groupes, ou une clé superuser, se crée via POST /api/v4/api-keys. Une clé rattachée à aucun groupe (clé superuser, ou clé user_id ou device_id créée là, qui ne porte jamais de groupe) n'apparaît dans la liste d'aucun groupe : un superuser la liste via GET /api/v4/api-keys et la supprime via DELETE /api/v4/api-keys/{api_key_id}.

Ces endpoints de lecture ne renvoient pas value mais value_masked : les quatre premiers caractères du secret, ***, puis les quatre derniers (cD5D***Ifi8). De quoi reconnaître, avant de la révoquer, la clé que porte une intégration donnée.

Ils renvoient aussi son label, created_by (l'utilisateur qui l'a créée), created_at, et last_used_at : la date du dernier appel authentifié par la clé, à la minute près, vide tant qu'elle n'a jamais servi. On sait ainsi si une intégration se sert encore d'une clé, ou a basculé sur une nouvelle, avant de révoquer l'ancienne.

Une clé se renomme via PATCH /api/v4/groups/{group_id}/api-keys/{api_key_id}, dont le corps porte le nouveau label et rien d'autre : le secret, le principal et l'expiration restent inchangés, et l'intégration qui détient la clé continue de s'authentifier.

Clés de votre propre compte

Un intégrateur qui travaille pour plusieurs groupes gère ses clés sans être administrateur d'aucun d'eux, via /api/v4/users/me/api-keys. Une telle clé agit en tant que votre compte : chaque appel qu'elle authentifie porte vos rôles, restrictions d'équipes comprises, et rien de plus. Un administrateur de groupe qui retire votre rôle sur son groupe coupe immédiatement toutes vos clés de ce groupe.

  • POST /api/v4/users/me/api-keys en crée une. Le corps accepte group_ids, label, expire et scopes ; un champ qui désigne un principal (user_id, role, superuser, device_id) est refusé. Avec group_ids, qui contient un groupe sur lequel votre compte a un rôle, la clé n'atteint que ce groupe et apparaît dans sa liste de clés, où ses administrateurs la voient et peuvent la révoquer. Une clé couvre un groupe au plus, pour que les administrateurs d'un client n'apprennent rien de vos autres clients ni ne puissent les couper : créez une clé par groupe. Sans group_ids, elle atteint tous les groupes sur lesquels votre compte a un rôle. La création exige une session ouverte par une personne, pas une clé d'API, et un compte superuser ne peut pas utiliser cette route. Un compte détient au plus 5 clés actives par groupe, et 5 rattachées à aucun groupe, en comptant celles qu'un administrateur de groupe a créées à son nom. Le secret est renvoyé une seule fois, dans value.
  • GET /api/v4/users/me/api-keys liste les clés qui agissent en tant que votre compte, sans leur secret.
  • PATCH /api/v4/users/me/api-keys/{api_key_id} en renomme une, DELETE /api/v4/users/me/api-keys/{api_key_id} la révoque ; une clé peut se révoquer elle-même. Une clé qui n'est pas la vôtre répond 404.

Une clé user_id créée sur un groupe, par son administrateur ou par vous, n'atteint que ce groupe, même si le compte a des rôles sur d'autres groupes.

Mécanismes acceptés

Mécanisme En-tête Usage
Session en bearer Authorization: Bearer <session_id> Client programmatique réutilisant une session
Clé d'API Authorization: Api-Key <api_key> ou x-api-key: <api_key> Machine-à-machine (recommandé)
JWT (OpenID Connect) Authorization: Bearer <jwt> Groupes délégués à un provider OpenID Connect

| Cookie de session | Cookie: session_id=<session_id> | Navigateur — posé automatiquement après le login | | WebSocket | En-tête Authorization/Cookie, ou premier message {"session_id": "<session_id>"} | Flux temps réel |

En-tête Authorization

Les mécanismes fondés sur l'en-tête Authorization — session en bearer, clé d'API et jeton OpenID Connect — s'emploient de façon identique : on place la valeur exacte de l'en-tête dans la requête. Dans l'exemple ci-dessous, PYSAE_AUTH vaut selon le cas Bearer <session_id>, Api-Key <api_key> ou Bearer <jwt>.

curl --fail --location \
  --header "Authorization: ${PYSAE_AUTH}" \
  "https://api.pysae.com/api/v4/groups/${GROUP_ID}/export/trips?date=20260131" \
  --output trips.csv

La clé d'API accepte aussi l'en-tête dédié x-api-key: <api_key>, équivalent à Authorization: Api-Key <api_key> :

curl --fail --location \
  --header "x-api-key: ${PYSAE_API_KEY}" \
  "https://api.pysae.com/api/v4/groups/${GROUP_ID}/export/trips?date=20260131" \
  --output trips.csv

Après un login réussi, l'API renvoie le session_id dans un cookie Set-Cookie (httponly, secure, samesite=lax). Un navigateur le renvoie automatiquement à chaque requête ; un client manuel le joint explicitement :

curl --cookie "session_id=<session_id>" \
  "https://api.pysae.com/api/v4/groups/<group_id>/export/trips"

WebSocket

Une connexion WebSocket réutilise le session_id. Un client qui peut poser des en-têtes l'envoie via Authorization: Bearer <session_id> ou le Cookie, comme en HTTP. Un client qui ne le peut pas (WebSocket navigateur) l'envoie dans le premier message JSON de la connexion :

# Requires websocat (https://github.com/vi/websocat).
printf '{"session_id": "%s"}\n' "${SESSION_ID}" | websocat \
  "wss://api.pysae.com/api/v4/groups/${GROUP_ID}/events/op/subscribe"

Usage machine-à-machine

Pour une intégration automatisée :

  • Clé d'API (recommandé) — persistante, révocable, scopée par groupe et par rôle, sans mot de passe dans le code appelant.

  • login-scoped — session bornée par scopes et expire, quand un identifiant à durée de vie limitée est préférable à une clé permanente.

Le parcours POST /api/v4/login vise l'usage humain (interface web) et n'est pas recommandé pour l'automatisation.

Le versionnement (v2–v5, v4 par défaut) et le découpage public / internal / datahub sont décrits sur la page d'accueil.