Service web des fiches card : extraction de variables hydroclimatiques sur les débits de la Banque Hydro (via Hub'Eau) et diagnostic de stationnarité Mann-Kendall / pente de Sen (via stase).
Documentation interactive : https://card-api.riverly.inrae.fr/docs (essai des requĂȘtes dans le navigateur, schĂ©mas de rĂ©ponse).
Service public de recherche (INRAE, UR RiverLy). Ouvert, sans inscription ; code GPL-3, données Hub'Eau en Licence Ouverte. Déploiement et développement : INSTALL.md.
flowchart LR
CARD["card<br/>fiches YAML<br/><i>quoi calculer</i>"] --> API
STASE["stase<br/>moteur<br/><i>comment calculer</i>"] --> API
HE["Hub'Eau<br/>eaufrance<br/><i>débits observés</i>"] --> API
API["card-api<br/>ce service"] --> OUT["résultat JSON<br/>provenance + droits"]
card ne calcule rien, stase ne connaßt pas les fiches, et la donnée vient d'ailleurs. C'est pourquoi chaque réponse porte la version des trois, et les droits qui vont avec.
| Votre cas | La porte | Pour voir |
|---|---|---|
| Quelques stations, ponctuellement | ce service | la doc interactive |
| Des milliers de stations, ou vos propres données | la bibliothÚque Python, en local, sans quota | dépÎt card |
| Les mĂȘmes, mais en R | le paquet R, qui appelle card sans le réécrire | dĂ©pĂŽt card4r |
| Comprendre ce qu'une fiche calcule | la fiche dessinée | VCN10 en figure |
| Savoir quels filtres existent | le vocabulaire de classification | /v1/vocabulary |
| Brancher un site web | l'API depuis le navigateur (CORS ouvert) | les fiches d'étiage |
| Publier un résultat | citer la fiche par son swhid, présent dans la réponse |
CITATION.cff |
| Une variable qui n'existe pas | copier une fiche, l'adapter, l'exécuter chez vous | développer sa propre fiche |
| Endpoint | RĂŽle |
|---|---|
GET / |
racine : liens typés vers le contrat, la doc et la version courante |
GET /v1 |
point d'entrée : ce qu'est le service, ce qu'il relie, droits |
GET /v1/cards |
catalogue des fiches CARD, filtrable par facettes ; ids rend la sĂ©lection prĂȘte Ă coller |
GET /v1/cards/{id} |
détail d'une fiche (fr/en) et liens vers sa définition |
GET /v1/cards/{id}/figure |
la fiche dessinée (texte) : sa chaßne de calcul, lang=fr ou en |
GET /v1/vocabulary |
valeurs valides des facettes, donc les filtres acceptés |
GET /v1/stations |
recherche de stations ; codes rend la sĂ©lection prĂȘte Ă coller |
GET /v1/extract |
chroniques Hub'Eau â variables CARD |
GET /v1/extract.csv |
les mĂȘmes sĂ©ries, en CSV pour le tableur |
GET /v1/trend |
extraction + test de Mann-Kendall et pente de Sen |
GET /v1/trend.csv |
les mĂȘmes diagnostics, en CSV pour le tableur |
GET /v1/trend/figure |
le diagnostic dessiné (texte) : sens, ampleur, verdict |
POST /v1/jobs |
grosses demandes en file de calcul (202 + ticket) |
GET /v1/jobs/{id} |
statut et progression ; /result : résultat gelé |
GET /v1/health |
santé du service (file de calcul, disque) |
/docs |
documentation interactive (OpenAPI) |
Chaque rĂ©ponse est en JSON et se suffit Ă elle-mĂȘme : data (les
résultats), meta (unités, noms français et anglais, classification),
la source des données et les versions des logiciels. Deux formats au
choix : orient=records (défaut, une liste d'objets, comme Hub'Eau)
ou orient=columns (colonnaire, {colonne: [valeurs]}, plus
compact).
La recherche interroge le référentiel hydrométrique Hub'Eau par nom, code ou département, et renvoie pour chaque station son code, son libellé, ses coordonnées et son état de service. C'est aussi le moyen de retrouver le code actuel d'une station connue sous son ancien code Banque Hydro.
curl "https://card-api.riverly.inrae.fr/v1/stations?libelle=Austerlitz"
# â F700000103 | La Seine Ă Paris - Austerlitz [>2006]
curl "https://card-api.riverly.inrae.fr/v1/stations?departement=07&size=100"La rĂ©ponse porte un champ codes : tous les codes trouvĂ©s, dĂ©jĂ
séparés par des virgules, à coller tel quel dans le paramÚtre
stations d'/v1/extract ou d'/v1/trend. Vingt stations ne se
recopient pas une par une.
Chaque fiche définit une variable calculable sur la chronique de
débit : module, étiages, crues, saisonnalité... Le catalogue se
filtre par facettes de classification (domain, phenomenon,
season, output...) ou par texte libre, en français ou en anglais :
curl "https://card-api.riverly.inrae.fr/v1/cards?phenomenon=basses%20eaux&output=série"
curl "https://card-api.riverly.inrae.fr/v1/cards?statistic=change&search=VCN"
curl "https://card-api.riverly.inrae.fr/v1/cards/VCN10?lang=fr" # dĂ©tail d'une ficheMĂȘme chose cĂŽtĂ© fiches : le champ ids rend la sĂ©lection filtrĂ©e
prĂȘte Ă coller dans le paramĂštre cards. Attention, l'identifiant
d'une fiche est le nom de son fichier (colonne id), pas sa variable :
ETPMA_month produit ETPMA_jan Ă ETPMA_dec, et c'est le premier
qu'attendent les endpoints.
Le mĂȘme fil en Python puis en R : extraire des indicateurs annuels, en tracer un, puis diagnostiquer sa tendance et superposer points et droite de Sen. Les indicateurs annuels se tracent en points (une valeur par an), pas en ligne continue.
Deux paramÚtres méritent un mot :
sampling=preferredfige la fenĂȘtre annuelle de calcul sur celle que chaque fiche dĂ©clare (par exemple l'annĂ©e hydrologique 09-01 pour les crues). Par dĂ©faut, les fiches d'Ă©tiage et de crue adaptent leur fenĂȘtre Ă chaque station ;preferredrend les rĂ©sultats directement comparables entre stations et reproductibles.series=truesur/v1/trendjoint Ă la rĂ©ponse, sousseries, les sĂ©ries extraites sur lesquelles la tendance a Ă©tĂ© calculĂ©e : points et diagnostic issus du mĂȘme calcul, sans second appel./v1/trend/figurerend le mĂȘme rĂ©sultat en table lisible, avec les mĂȘmes paramĂštres : sens, ampleur, p-value et verdict en clair. De quoi lire une rĂ©ponse sans traverser le JSON, dans un terminal comme dans/docs./v1/extract.csvet/v1/trend.csvrendent les mĂȘmes donnĂ©es en fichier ouvrable au tableur, virgule et point dĂ©cimal. La provenance (versions, SWHID, source, empreinte, droits) voyage en lignes#en tĂȘte, queread_csv(comment="#")etread.csv(comment.char="#")sautent d'eux-mĂȘmes : un fichier enregistrĂ© sait toujours dire d'oĂč il vient. Le nom proposĂ© au tĂ©lĂ©chargement porte l'analyse,card-api_trend_F700000103_QA-VCN10_AR1_2005-2026_ac9c7eed.csv.stations_meta=truejoint les fiches du rĂ©fĂ©rentiel Hub'Eau des stations demandĂ©es (libellĂ©, coordonnĂ©es, Ă©tat de service). Un rĂ©sultat devient autoportant : tracer une carte ne demande plus d'aller chercher les positions ailleurs.
Une grosse demande reçoit un ticket (202) au lieu d'une réponse
immédiate. Ce qui décide n'est pas le nombre de stations mais le nombre
de stations à télécharger : rapatrier une chronique coûte environ
trente fois plus que la calculer. Vingt stations déjà lues répondent donc
en direct, les mĂȘmes vingt Ă froid partent en file. La mĂȘme URL peut par
conséquent donner un ticket au premier appel et un résultat au second,
et c'est voulu.
Le résultat d'un job se récupÚre dans la représentation de votre choix, quelle que soit celle demandée au dépÎt :
| route | rend |
|---|---|
/v1/jobs/{id}/result |
le JSON gelé, avec son bloc de provenance |
/v1/jobs/{id}/result.csv |
le mĂȘme rĂ©sultat en CSV, mĂȘmes colonnes qu'en direct |
/v1/jobs/{id}/result/figure |
le mĂȘme rĂ©sultat dessinĂ© (jobs de tendance) |
Ce qui limite le choix n'est pas la porte d'entrée mais ce que le
rĂ©sultat contient : une extraction n'a pas de verdict de stationnaritĂ© Ă
dessiner. Le ticket rendu par un .csv porte directement l'adresse de
son CSV, il n'y a rien Ă deviner.
Toutes les stations du référentiel hydrométrique ne publient pas de débit
journalier. Une échelle limnimétrique, par exemple, mesure une hauteur
d'eau : sans courbe de tarage elle n'a pas de série de débit, et rien
dans le référentiel ne l'annonce (ni type_station, ni en_service, qui
vaut d'ailleurs false pour beaucoup de stations fermées dont
l'historique est intact et parfaitement utilisable). La demander est le
seul moyen de le savoir.
Une telle station est donc écartée du calcul, pas fatale : les autres sont calculées, et le résultat dit exactement ce qu'il contient.
| champ | contenu |
|---|---|
stations |
les stations réellement calculées, celles que data contient |
stations_requested |
celles que vous avez demandées |
stations_omitted |
les écartées, avec reason et detail |
reason vaut no_series (aucune chronique publiée), no_data_in_period
(chronique prĂ©sente, mais rien dans la fenĂȘtre demandĂ©e) ou
ambiguous_site (code de site dont plusieurs stations mesurent en
parallÚle). Le bloc est toujours présent, vide quand tout va bien :
un client n'a pas Ă tester son existence.
Deux garde-fous. Si aucune station n'a de série, la demande est refusée
en 404 plutÎt que rendue vide, un résultat sans lignes se laissant lire
comme un résultat. Et une panne Hub'Eau reste une erreur 504 : elle
n'est jamais transformée en omission, sans quoi un incident passager
produirait des résultats discrÚtement amputés.
L'information voyage dans toutes les représentations : bloc JSON, lignes
# station Ă©cartĂ©e en tĂȘte des .csv, mention en clair dans
/v1/trend/figure, et gelée dans le résultat d'un job.
Extraction : module (QA) et étiage (VCN10) de la Seine à Paris.
import requests, pandas as pd
r = requests.get("https://card-api.riverly.inrae.fr/v1/extract", params={
"stations": "F700000103",
"cards": "QA,VCN10",
"start": "1990-01-01",
"orient": "columns", # directement ingérable par pandas
}).json()
r["data"]["VCN10"]
# {"code_station": ["F700000103", "F700000103", ...],
# "date": ["2005-02-01", "2006-02-01", ...],
# "VCN10": [None, 104.6066, ...]} # la 1re année est incomplÚte
r["meta"][1]
# {"variable_en": "VCN10", "unit_fr": "m^{3}.s^{-1}",
# "name_fr": "Minimum annuel de la moyenne sur 10 jours du débit journalier", ...}Figure, avec l'unité lue dans les métadonnées :
import matplotlib.pyplot as plt
vcn10 = pd.DataFrame(r["data"]["VCN10"])
meta = pd.DataFrame(r["meta"])
unit = meta.loc[meta.variable_en == "VCN10", "unit_fr"].iloc[0]
vcn10.plot(x="date", y="VCN10", style="o", ylabel=f"VCN10 [{unit}]")
plt.show()Tendance du VCN10 : une ligne par station (H : tendance
significative ? p-value, pente de Sen absolue a et relative).
r = requests.get("https://card-api.riverly.inrae.fr/v1/trend", params={
"stations": "F700000103",
"cards": "VCN10",
"sampling": "preferred",
"series": "true",
}).json()
tr = pd.DataFrame(r["data"]["VCN10"]).iloc[0]
# code_station F700000103 h False
# level 0.1 p 0.339880
# a -0.776973 b 150.192329
# a_relative -0.678864 mean_period 114.451837h est le verdict au seuil level, p la p-value, a la pente de Sen
dans l'unitĂ© de la variable et par an, a_relative la mĂȘme en pourcentage
de la moyenne de la période. Ici la baisse apparente n'est pas
significative, et le service le dit plutĂŽt que de la laisser croire.
Points et droite de Sen sur la mĂȘme figure :
s = pd.DataFrame(r["series"]["VCN10"])
dates = pd.to_datetime(s["date"])
years = (dates - pd.Timestamp("1970-01-01")).dt.days / 365.25
plt.plot(dates, s["VCN10"], "o")
plt.plot(dates, tr["a"] * years + tr["b"], "--")
plt.show()Extraction : module (QA) et étiage (VCN10) de la Seine à Paris
(format records par défaut : fromJSON en fait des data.frame).
library(jsonlite)
r <- fromJSON(paste0("https://card-api.riverly.inrae.fr/v1/extract?stations=F700000103",
"&cards=QA,VCN10&start=1990-01-01"))
head(r$data$VCN10, 3)
# code_station date VCN10
# 1 F700000103 2005-02-01 NA
# 2 F700000103 2006-02-01 104.6066
# 3 F700000103 2007-02-01 139.7335Ces exemples appellent le service par HTTP, ce qui est le bon geste pour quelques stations Hub'Eau. Pour vos propres donnĂ©es, ou pour du volume sans quota, card4r appelle le mĂȘme recueil en local depuis R.
Figure, avec l'unité lue dans les métadonnées :
vcn10 <- r$data$VCN10
unit <- r$meta$unit_fr[r$meta$variable_en == "VCN10"]
plot(as.Date(vcn10$date), vcn10$VCN10,
ylab = paste0("VCN10 [", unit, "]"))Tendance du VCN10 : une ligne par station (H : tendance
significative ? p-value, pente de Sen absolue a et relative).
r <- fromJSON(paste0("https://card-api.riverly.inrae.fr/v1/trend?stations=F700000103",
"&cards=VCN10&sampling=preferred&series=true"))
tr <- r$data$VCN10[1, ]Points et droite de Sen sur la mĂȘme figure :
s <- r$series$VCN10
dates <- as.Date(s$date)
years <- as.numeric(dates) / 365.25
plot(dates, s$VCN10)
lines(dates, tr$a * years + tr$b, lty = 2)Au-dessus de 10 stations ou 20 fiches, la demande devient un job,
sans inscription : la réponse 202 donne un ticket, le calcul se
fait en file, le résultat reste téléchargeable plusieurs jours avec
un bloc de provenance (paramÚtres, versions, date des données) qui le
rend citable et reproductible.
job = requests.post("https://card-api.riverly.inrae.fr/v1/jobs", json={
"endpoint": "trend",
"stations": liste_de_codes, # jusqu'Ă 100
"cards": ["QA", "VCN10"],
"sampling": "preferred",
}).json()
# suivre job["status_url"] (queued -> running -> done, avec progression)
# puis récupérer job["result_url"]Les appels GET /v1/extract et /v1/trend trop gros basculent
automatiquement sur ce circuit (réponse 202 au lieu d'un refus).
Le service est public avec un quota par IP et par minute ; en cas de
dĂ©passement (429), l'en-tĂȘte Retry-After indique quand rĂ©essayer.
Les chroniques sont mises en cache 24 h cÎté serveur : répéter une
requĂȘte ne re-tĂ©lĂ©charge rien depuis Hub'Eau.
Si vous atteignez le quota, c'est presque toujours qu'une boucle
appelle le service une fois par station. Ce n'est pas la bonne forme :
stations prend une liste (stations=A,B,C), un appel rapporte
tout, et au-delĂ des plafonds synchrones la demande bascule d'elle-mĂȘme
en file de calcul. Le quota est calculĂ© large pour ne gĂȘner personne en
usage normal, y compris tout un Ă©tablissement derriĂšre une mĂȘme adresse
publique.
Pour un besoin massif ou récurrent (centaines de stations, chaßnes de
traitement), demandez une clĂ© de prioritĂ© gratuite par courriel Ă
louis.heraut@inrae.fr : qui vous ĂȘtes, l'usage prĂ©vu, et le projet
associé si vous voulez. Attribution manuelle, réponse rapide. Le jeton
vous revient par le mĂȘme canal, ce qui est la raison de ce choix : une
issue GitHub est publique, le jeton ne peut pas y transiter.
Elle se passe en en-tĂȘte X-API-Key (de prĂ©fĂ©rence Ă key=, qui
laisse la clé dans les logs web) : quotas par minute levés, plafonds
relevĂ©s (jusqu'Ă 1000 stations par job), jobs en tĂȘte de file, et
GET /v1/jobs liste vos jobs déposés avec la clé (tickets compris :
pratique pour retrouver un résultat dont le ticket est égaré).
Le jeton n'est communiqué qu'une fois, à la création (le serveur n'en garde qu'un hachage) : conservez-le, un jeton perdu se remplace. Le journal du service ne stocke jamais votre nom, seulement le préfixe du jeton.
Chaque rĂ©ponse porte de quoi refaire le calcul plus tard, ou expliquer pourquoi il ne redonne pas la mĂȘme chose :
| Champ | Ce qu'il dit |
|---|---|
api_version |
la version du service qui a répondu |
card_version, card_commit, card_swhid |
le corpus de fiches employé |
stase_version, stase_commit, stase_swhid |
le moteur de calcul employé |
meta[].version, meta[].swhid |
la définition de chaque variable, fiche par fiche |
data_fetched_at |
quand les chroniques Hub'Eau ont été lues |
data_fingerprint |
ce qu'elles contenaient |
Les identifiants swh: s'ouvrent en collant
https://archive.softwareheritage.org/ devant : ils donnent le code et
les fiches tels qu'ils étaient, indépendamment de GitHub.
data_fingerprint demande une explication, parce qu'il ne se recalcule
pas de votre cÎté : c'est un jeton de comparaison, pas une somme de
contrÎle. Il résume les chroniques employées, entiÚres, avant tout
filtre de pĂ©riode. Deux rĂ©sultats qui portent la mĂȘme valeur reposent
sur la mĂȘme donnĂ©e ; deux valeurs diffĂ©rentes signalent que Hub'Eau a
révisé sa donnée entre les deux appels, ce qui arrive réguliÚrement et
explique alors l'écart sans qu'il faille chercher du cÎté du calcul. Le
préfixe v1: est la version de l'algorithme : s'il change un jour, vous
saurez que deux empreintes ne sont plus comparables.
Pour un job, le résultat gelé porte en plus data_fingerprints, le
détail station par station : quand un lot de 200 stations change, il dit
laquelle.
Le service ne fournit que des débits journaliers (fiches à entrée
Q) ; le diagnostic de tendance ne s'applique qu'aux fiches de forme
series (la tendance d'un scalaire ou d'une courbe n'a pas de sens).
| card | le recueil de fiches, en Python |
| stase | le moteur d'agrégation et de tendance |
| card4r | le mĂȘme recueil, appelĂ© depuis R |
| card-api | le service web, sur les dĂ©bits Hub'Eau (vous ĂȘtes ici) |
| CARD-R · EXstat | les paquets R historiques, remplacés |
Les métadonnées de citation sont dans CITATION.cff (bouton « Cite this repository » de GitHub) et codemeta.json (moissonné par Software Heritage et HAL ; identifiant pérenne à venir par ce canal). Dans une publication, citez aussi la source des données (Hub'Eau hydrométrie, eaufrance, Licence Ouverte) et ce qui a produit votre résultat : chaque réponse porte les versions et les identifiants Software Heritage du corpus et du moteur, la version de chaque fiche employée, la date de lecture des données et leur empreinte. Le détail est dans « Savoir ce qui a produit un résultat » plus haut ; le résultat gelé d'un job les rassemble dans un bloc de provenance avec les paramÚtres de l'appel.
Ăditeur. INRAE (Institut national de recherche pour l'agriculture, l'alimentation et l'environnement), Ă©tablissement public Ă caractĂšre scientifique et technologique, 147 rue de l'UniversitĂ©, 75338 Paris Cedex 07. Service dĂ©veloppĂ© et exploitĂ© par l'unitĂ© de recherche RiverLy, 5 rue de la Doua, CS 20244, 69625 Villeurbanne Cedex.
Responsable de la publication. Louis Héraut (INRAE, UR RiverLy), louis.heraut@inrae.fr.
Hébergement. Machine virtuelle des centres de données d'INRAE.
Propriété intellectuelle. Le code du service, du corpus de
définitions card et du moteur
stase est sous
GPL-3.0-or-later. Les
observations hydrométriques proviennent de
Hub'Eau (eaufrance) et restent sous
Licence Ouverte / Etalab 2.0.
Chaque réponse du service porte ces droits dans son bloc rights.
Le service est public, sans inscription et sans compte : le consulter et l'interroger ne demande aucune identité. Deux traitements existent néanmoins, de natures trÚs différentes.
1. Le journal d'usage, anonymisĂ©. Une ligne par requĂȘte de calcul : horodatage, endpoint appelĂ©, nombre de stations et variables demandĂ©es, et un identifiant technique dĂ©rivĂ© de l'adresse IP. L'adresse IP elle-mĂȘme n'est jamais Ă©crite : seul en est conservĂ© un condensat SHA-256 salĂ© et tronquĂ©, dont le sel est propre au dĂ©ploiement. Il permet de compter des visiteurs distincts sans permettre de remonter Ă quiconque. Aucun traceur, aucun cookie, aucune mesure d'audience tierce.
FinalitĂ© : mesurer l'usage du service, ce qui constitue la preuve d'impact attendue dans les dossiers de financement de la recherche publique. Base lĂ©gale : l'exĂ©cution de la mission d'intĂ©rĂȘt public d'INRAE. Conservation : journal segmentĂ© par annĂ©e, les fichiers anciens sont supprimĂ©s. Ces donnĂ©es Ă©tant anonymisĂ©es, elles ne permettent pas de vous identifier et aucune demande individuelle ne peut y ĂȘtre rattachĂ©e.
2. Les clĂ©s de prioritĂ©, nominatives. Une clĂ© est attribuĂ©e Ă une personne identifiĂ©e, et le fichier de clĂ©s du serveur conserve son nom et son organisme, associĂ©s au prĂ©fixe public du jeton et Ă sa date de crĂ©ation. Le jeton lui-mĂȘme n'est pas conservĂ©, seul son condensat l'est.
FinalitĂ© : attribuer et rĂ©voquer les clĂ©s, et distinguer les usages massifs dans les statistiques. Base lĂ©gale : l'exĂ©cution de la mission d'intĂ©rĂȘt public d'INRAE. Conservation : jusqu'Ă la rĂ©vocation de la clĂ©, qui efface le lien entre le prĂ©fixe et la personne. Diffusion : le journal d'usage ne reçoit que le prĂ©fixe, jamais le nom.
Une clĂ© se demande par courriel, et non par une issue publique : votre nom, votre organisme et votre projet n'ont pas Ă ĂȘtre exposĂ©s, et le jeton lui-mĂȘme ne peut voyager que par un canal privĂ©.
Vos droits. Sur les données nominatives des clés, vous disposez d'un droit d'accÚs, de rectification, d'opposition pour motifs légitimes, de limitation et d'effacement. Pour les exercer, ou pour toute question sur ces traitements, écrire à louis.heraut@inrae.fr, ou au délégué à la protection des données d'INRAE : cil-dpo@inrae.fr, INRAE, 24 chemin de Borde Rouge, Auzeville, CS 52627, 31326 Castanet-Tolosan Cedex.
En cas de désaccord persistant, vous pouvez saisir la CNIL, 3 place de Fontenoy, TSA 80715, 75334 Paris Cedex 07.
Politique de protection des données d'INRAE : https://science-ouverte.inrae.fr/fr/donnees-personnelles.