L'API d'intensité carbone de l'électricité française — souveraine, open source et dev-first.
L'équivalent français de carbonintensity.org.uk, bâti sur les données ouvertes RTE / éCO2mix via ODRÉ.
L'intensité carbone du réseau électrique français (en gCO₂eq/kWh) est une donnée publique précieuse — pour décaler une recharge de véhicule, planifier un batch de calcul, ou afficher l'empreinte d'un service. Mais la source officielle reste brute, plafonnée en appels, et sans prévision d'intensité.
carbon-fr la rend directement consommable par des développeurs et des machines :
- 🇫🇷 Souverain & auto-hébergeable — aucune dépendance propriétaire obligatoire, licences OSI.
- 🚀 Dev-first — API REST lisible et versionnée (
/v1), OpenAPI 3.1 + Swagger UI servis, collection Bruno, SDK TypeScript (sdk/typescript/). - 🛡️ Résilient au quota — un poller unique alimente la base ; l'API sert tous les clients depuis ce read-model, à moins de 8 % du quota RTE.
- 🔬 Méthodologie versionnée — chaque mesure porte sa méthode de calcul (
rte-directetacv-ademe— cycle de vie production et consommation), jamais de changement silencieux. - 🔮 Prévision comme valeur ajoutée — l'intensité prévisionnelle n'existe pas à la source :
carbon-frla modélise derrière un port dédié (climatology@1, gardée par backtest). - ⏱️ Carbon-aware & live — primitives de scheduling (créneau sous échéance, lowest-k, seuil, économie), flux SSE, et webhooks signés (sur clé API).
Aucune installation : l'instance hébergée répond tout de suite. L'intensité carbone courante du réseau national, en une requête :
curl https://carbon-fr-api.kovelt.fr/v1/intensity/now{
"region": "national",
"timestamp": "2026-06-17T06:30:00Z",
"intensity": { "value": 20.0, "unit": "gCO2eq/kWh" },
"methodology": "rte-direct",
"methodology_version": 1,
"vintage": "tr"
}Ajoute ?region=<slug> pour l'une des 12 régions, ou ?methodology=acv-ademe pour le cycle de vie (cf. Fonctionnalités).
🌐 Dans le navigateur — explore et essaie tous les endpoints depuis la Swagger UI : carbon-fr-api.kovelt.fr/docs.
📦 En TypeScript / JavaScript — le SDK officiel (@carbon-fr/sdk, zéro dépendance runtime) :
npm install @carbon-fr/sdkimport { CarbonFr } from "@carbon-fr/sdk";
const cf = new CarbonFr(); // instance hébergée par défaut
const now = await cf.intensityNow();
console.log(now.intensity.value, now.intensity.unit); // 20 gCO2eq/kWh| Endpoint | Nature | Statut |
|---|---|---|
GET /v1/intensity/now |
Intensité courante (national + 12 régions) | ✅ |
GET /v1/mix |
Mix de production par filière | ✅ |
GET /v1/exchanges |
Échanges transfrontaliers par frontière (flux signé + carbone du voisin, ENTSO-E) | ✅ |
GET /v1/exchanges/date?from=&to= |
Série historique des échanges transfrontaliers | ✅ |
GET /v1/weather · /weather/date |
Météo nationale (vent 100 m + irradiance, Open-Meteo CC-BY) | ✅ |
GET /v1/renewable |
Production renouvelable estimée depuis la météo + facteur de charge (ADR-0018) | ✅ |
GET /v1/intensity/date?from=&to= |
Historique sur un intervalle (révisé/consolidé/définitif) | ✅ |
GET /v1/intensity/stats?from=&to=[&interval=hour|day] |
Résumé (moyenne/min/max) + série agrégée | ✅ |
GET /v1/intensity/forecast |
Prévision d'intensité (climatology@1 ; acv-ademe@2 via ?methodology=acv-ademe&version=2) |
✅ |
GET /v1/intensity/greenest-window |
Créneau le plus bas-carbone (+ overlay électrolyseur ?eligibility=rfnbo|low-carbon, ADR-0025/0026) |
✅ |
GET /v1/eligibility/rulesets |
Catalogue des cadres d'éligibilité électrolyseur (rulesets versionnés, servis + planifiés) | ✅ |
GET /v1/schedule · /schedule/slots · /intensity/below |
Scheduling carbon-aware (échéance, lowest-k, seuil + économie) | ✅ |
GET /v1/intensity/stream |
Flux live (Server-Sent Events) | ✅ |
GET /v1/methodologies · /factors |
Catalogue des méthodes + table des facteurs (vérifiabilité) | ✅ |
GET /v1/price · /price/date |
Décomposition du prix payé ancrée sur le TRV (énergie spot ENTSO-E + TURPE + taxes + résidu, ADR-0023) | ✅ |
GET /v1/cost-reference |
Couche comparative LCOE (coût de production), estimation en fourchette, jamais soustraite du marché (ADR-0024) | ✅ |
POST/GET/DELETE /v1/webhooks |
Abonnements webhook signés (clé API requise) | ✅ |
Les endpoints d'intensité (/intensity/now, /intensity/date, /intensity/stats, /mix) acceptent ?region=<slug> (national par défaut) et ?methodology=<id> : rte-direct (estimation RTE, combustion directe — défaut, national uniquement) ou acv-ademe (cycle de vie ADEME, national + 12 régions, ADR-0008). Les endpoints de prix, coût, échanges, météo, renouvelable et catalogue (/price, /cost-reference, /exchanges, /weather, /renewable, /methodologies, /factors) sont nationaux (/price renvoie 400 hors national).
/intensity/greenest-window accepte en plus ?eligibility=rfnbo|low-carbon (overlay « électrolyseur », ADR-0025/0026) : chaque créneau de la fenêtre est annoté de son éligibilité au regard du cadre choisi — rfnbo (part renouvelable ≥ 90 % ou prix day-ahead ≤ 20 €/MWh, Règl. UE 2023/1184) ou low-carbon (intensité ≤ seuil indicatif dérivé de l'acte délégué 2025/2359) — en réponse additive (rien ne change sans le paramètre), avec verdicts pass/fail/indeterminate et disclaimer de neutralité. Le catalogue des rulesets versionnés est servi par /v1/eligibility/rulesets.
La spécification OpenAPI 3.1 (dérivée du code via utoipa) est servie sous GET /v1/openapi.json, et une Swagger UI sous GET /docs. Une collection Bruno versionnée (dossier bruno/) couvre tous les endpoints (cas nominaux + erreurs).
Une carte « électrolyseurs × carbone live » est servie sous GET /hydrogene (hors contrat /v1, comme /docs) : les 233 électrolyseurs européens géolocalisés (European Hydrogen Observatory, instantané semestriel) croisés avec l'intensité carbone régionale live et les fenêtres d'éligibilité rfnbo/low-carbon (ADR-0029). Page auto-contenue — aucun CDN, aucune tuile externe.
Les erreurs suivent RFC 9457 (application/problem+json) : type/title/status/detail + un champ code court et stable (no_data, bad_request…) sur lequel s'aligner. Côté disponibilité, l'API peut répondre 503 (unavailable) quand une donnée dérivée n'est pas encore prête — p. ex. /v1/renewable si le modèle n'est pas calibré, ou /health/ready si la base est injoignable — et /v1/price renvoie 404 si aucune source de prix spot n'est configurée (token ENTSO-E absent).
Tier hébergé & clés API (ADR-0015, opt-in) : par défaut l'API est anonyme et sans quota (parité avec l'auto-hébergement). L'opérateur peut activer une limite de débit (CARBONFR_RATELIMIT_ENABLED=1) — anonyme 60 req/min, clé Free 600 req/min, en-têtes RateLimit-*, 429 (rate_limited) au dépassement. Une clé API (Bearer, requise pour les webhooks) se délivre côté serveur via la sous-commande mint-key : l'empreinte est stockée, la clé n'est affichée qu'une seule fois.
Un compteur de consultation sobre est exposé (GET /v1/stats, POST /v1/stats/visit) : l'IP n'est jamais stockée — seule une empreinte SHA-256 salée sert à dédupliquer (unique par IP/jour), RGPD-friendly.
Couverture National + 12 régions métropolitaines. Le
taux_co2publié par RTE (rte-direct) n'existe qu'au national ; l'intensité régionale est dérivée viaacv-ademe(cycle de vie appliqué au mix régional, ADR-0008).acv-ademe@1est basée production ;acv-ademe@2consumption-based (imports valorisés à l'intensité du voisin via ENTSO-E + pertes T&D, ADR-0010) est servie au national via?methodology=acv-ademe&version=2.
carbon-fr suit une architecture hexagonale (ports & adapters) stricte : le domaine ne dépend de rien, les dépendances pointent vers l'intérieur. Changer de source de données, de base, ou de modèle de prévision = un nouvel adapter, sans toucher au cœur métier.
Adapters entrants Adapters sortants
(API axum, CLI…) (ODRÉ · PostgreSQL · ForecastModel)
│ ▲
▼ appelle les cas d'usage │ implémentent les ports
┌───────────────────────────────────────────────────────────┐
│ core (lib pure, zéro IO) │
│ application/ cas d'usage ports/ traits sortants │
│ domain/ intensité, régions, mesures, méthodologie │
└───────────────────────────────────────────────────────────┘
▲ assemble tout
bin/server (composition root)
Le détail — vision, contraintes, modèle de données, quota — vit dans docs/ARCHITECTURE.md. Le « pourquoi » des choix structurants est tracé dans les ADR.
carbon-fr/
├── Cargo.toml # workspace Cargo
├── crates/
│ ├── core/ # ✅ domaine + cas d'usage + ports (lib PURE, zéro IO)
│ ├── adapter-odre/ # ✅ impl Eco2mixSource/Eco2mixArchive (ODRÉ)
│ ├── adapter-postgres/ # ✅ impl repositories (sqlx/Postgres)
│ ├── adapter-http/ # ✅ API axum + OpenAPI + auth + SSE (adapter entrant)
│ ├── adapter-forecast/ # ✅ impl ForecastModel (climatology@1, acv-ademe@2)
│ ├── adapter-meteo/ # ✅ impl WeatherForecastSource (Open-Meteo)
│ ├── adapter-entsoe/ # ✅ impl CrossBorderSource + SpotPriceSource (ENTSO-E)
│ ├── adapter-webhook/ # ✅ impl Notifier (livraison signée, anti-SSRF)
│ └── adapter-gbdt/ # ✅ impl ForecastModel ML (GBDT, exploré — non servi)
├── bin/
│ └── server/ # ✅ composition root : adapters + poller
├── bruno/ # collection Bruno (requêtes .bru versionnées)
├── sdk/typescript/ # SDK client TypeScript (@carbon-fr/sdk)
├── deploy/ # exemples self-host (Caddyfile, systemd) + README (prod Traefik)
├── Dockerfile # image de prod multi-stage
├── .env.example # variables d'environnement documentées
└── docs/
├── ARCHITECTURE.md
└── adr/ # Architecture Decision Records
Pour travailler sur carbon-fr lui-même (et non simplement consommer l'API). Prérequis : Rust (edition 2024, cargo ≥ 1.85).
git clone git@github.com:Kovelt/carbon-fr.git
cd carbon-fr
cargo check --workspace # compile tout le workspace
cargo test --workspace # lance les tests (le core se teste sans IO)
cargo clippy --all-targets -- -D warnings
cargo fmt --allLe crate core se teste entièrement en mémoire, avec des fakes implémentant les ports — c'est le bénéfice direct de l'hexagonal (voir crates/core/tests/use_cases.rs).
Image de production via le Dockerfile multi-stage (binaire --release, runtime Debian slim, utilisateur non-root). Chaque tag git vX.Y.Z publie l'image sur GHCR (ghcr.io/kovelt/carbon-fr:X.Y.Z, publique, ADR-0019) — en prod, épingler une version exacte. En bare-metal, une unité systemd (deploy/carbonfr.service) avec Restart=on-failure. Dans les deux cas, placer l'API derrière un reverse proxy TLS (deploy/Caddyfile, exemple self-host) et activer CARBONFR_TRUST_PROXY=1. L'instance hébergée (carbon-fr-api.kovelt.fr) tourne comme un service de la stack Kovelt derrière Traefik (PostgreSQL dédié) ; détails dans deploy/README.md.
docker build -t carbon-fr .
docker run -e DATABASE_URL=postgres://… -e CARBONFR_VISIT_SALT=… -p 8080:8080 carbon-frConfiguration via variables d'environnement — voir .env.example. Sondes : GET /health (liveness) et GET /health/ready (vérifie la base). Métriques Prometheus sous GET /metrics (fraîcheur du poller, volume ingéré, appels amont — à restreindre au scrapeur côté proxy en prod). Les migrations sont appliquées au démarrage.
Outre le serveur, le binaire expose des sous-commandes one-shot : backfill (rapatrie l'historique par export de masse — prérequis de /intensity/date, /intensity/stats et de la prévision), mint-key (délivre une clé API), les backtest* / train (évaluation et entraînement des modèles de prévision) et --version.
- Unité canonique : gCO₂eq/kWh.
- Périmètre :
National+ 12 régions métropolitaines (couverture éCO2mix régional). - Méthodologie MVP :
rte-direct— reprise de l'estimation RTE (émissions de la production française), directement comparable à éCO2mix. Versionnée et portée par chaque mesure (ADR-0005). - Révisions : la donnée RTE est révisée (
tr→consolidated→definitive). L'ingestion fait un upsert conditionnel au millésime : on sert toujours la meilleure version (ADR-0006). - Source citée, jamais appropriée :
carbon-frre-traite et cite RTE/ODRÉ, il ne s'y substitue pas.
Données amont : RTE éCO2mix, publiées en open data via ODRÉ sous licence ouverte.
- Cadrage — ADR, architecture, modèle de domaine.
- Phase 1 — Socle :
core· adapters ODRÉ / Postgres / HTTP · poller ·/intensity/now+/mix(national). - Phase 2 — Historique & régional : backfill par export de masse ·
/intensity/date· rollups +/intensity/stats· régional viaacv-ademe(12 régions) · OpenAPI 3.1 + Bruno. - Phase 3 — Prévision :
climatology@1(backtest, calibration des intervalles) →/forecast+/greenest-window. - Phase 4 — Enrichissement & usage :
acv-ademe@2consumption-based (ENTSO-E) · prévisionacv-ademe· scheduling carbon-aware + SSE · clés API + quota · webhooks signés. (ML GBDT exploré, gardé par backtest ; raffinements ouverts.) - Phase 5 — Enrichissement, déploiement & SDK : échanges transfrontaliers (
/v1/exchanges), météo (/v1/weather), dérivation renouvelable (/v1/renewable) ; prix de l'électricité (/v1/price, décomposition TRV, ADR-0023) + couche comparative LCOE (/v1/cost-reference, ADR-0024) ; déployé sur VPS FR/EU (Traefik + PostgreSQL) ; SDK TypeScript (@carbon-fr/sdk). - Extension hydrogène carbon-aware (v0.4.0) : couche A « électrolyseur » — éligibilité RFNBO / bas-carbone par créneau au-dessus de
/greenest-window(?eligibility=) + catalogue/v1/eligibility/rulesets(ADR-0025, ADR-0026). - À venir : SDK Rust ; site statique (o2switch) ;
UsageMeterpersistant ; suite hydrogène (roadmap dédiée : rulesetrfnbo:2026-revisionquand le droit sera adopté,MixForecast, couche B-light « carte électrolyseurs × carbone live »).
Les contributions sont les bienvenues — lire d'abord CONTRIBUTING.md et les conventions de code dans CLAUDE.md. En résumé : cargo fmt + cargo clippy -D warnings doivent passer, le core reste sans IO, et toute décision structurante passe par un ADR. La branche main est protégée (PR obligatoire, CI verte, historique linéaire — pas de push direct ; voir ADR-0027).
Distribué sous licence MIT OU Apache 2.0, au choix.
Sauf mention contraire explicite, toute contribution soumise pour inclusion dans ce dépôt — telle que définie par la licence Apache 2.0 — sera distribuée sous cette double licence, sans condition supplémentaire.