Pourquoi cette nouvelle version v0 ?
Notre API actuelle app.switchgrid.tech/enedis/v2/* présente quelques problèmes / limitations :
- elle est spécifique à Enedis. Nous avons depuis intégré GRDF et Strasbourg Électricité Réseaux. Nous prévoyons de supporter d'autres sources de données à l'avenir.
- elle ne supporte pas le versioning.
Versioning et headers HTTP
Les clients devront déclarer quelle version de l'API ils souhaitent utiliser dans le header x-api-version. Voir le Guide pour plus de détails.
Changements
24 septembre 2026 — le webhook ask.revoked est désormais envoyé
L'événement ask.revoked était documenté mais jamais envoyé. Il l'est maintenant, une fois par Ask : quand vous le révoquez avec POST/ask/{askId}/revoke, ou quand tous ses contrats sont révoqués. Les Asks révoqués avant cette date ne le déclenchent pas.
22 septembre 2026 — nouveau webhook ask.verified
Un Ask qui porte beaucoup de points de livraison n'est pas vérifié pendant l'appel : au-delà de quelques contrats, nous interrogeons les gestionnaires de réseau en tâche de fond pour les ménager, et POST/ask répond avec un Ask en CREATED, sans consentCollectionDetails.userUrl. Rien ne signalait ensuite la fin des vérifications : il fallait relire l'Ask en boucle.
Le webhook ask.verified le fait désormais. Il porte l'askId et le status de l'Ask qui en résulte :
{
"event": {
"_tag": "ask.verified",
"createdAt": "2026-09-22T10:21:32.262Z",
"projectId": "a0efdd19-de68-480d-a92f-458ffd98f5c2",
"askId": "f720e25e-c662-402e-bd64-aa0771aa819f",
"status": "PENDING_USER_ACTION"
}
}
PENDING_USER_ACTION : consentCollectionDetails.userUrl accepte la signature. NOT_VALID : au moins un contrat n'a pas été confirmé, et son statusText dit pourquoi — c'est le point de départ d'un dépôt de justificatif.
Il est émis à chaque fin de vérification, y compris pour un Ask déjà vérifié quand nous répondons — le cas courant. Il est alors redondant avec le corps de la réponse et vous pouvez l'ignorer. Nous préférons l'émettre dans les deux cas plutôt que vous laisser déduire quel Ask est passé par la tâche de fond : le nombre de contrats à partir duquel nous basculons est un détail d'implémentation.
21 septembre 2026 — un champ inconnu dans le corps d'une requête répond désormais 400
Un champ que l'endpoint ne connaissait pas était silencieusement ignoré, et l'appel répondait 200. Sur POST/integration/grdf-adict/donnees-informatives, un corps comme celui-ci renvoyait trois ans de données sans rien signaler — la fenêtre se borne avec since et until, au premier niveau du corps :
{
"pce": "01234567890123",
"periode": { "date_debut": "2026-01-01", "date_fin": "2026-02-01" }
}
Un champ non déclaré répond maintenant 400, en le nommant et, quand c'est évident, en indiquant celui que vous vouliez :
{
"_tag": "BadRequest",
"message": "Unknown field \"periode\" (did you mean \"since\"?) in the request body."
}
Trois points à connaître :
- les champs acceptés par chaque corps de requête sont ceux de la spécification OpenAPI — cette liste est désormais close ;
- la règle porte sur le premier niveau du corps : un objet imbriqué garde le comportement qu'il avait ;
- les noms sont sensibles à la casse :
"Since"est refusé comme n'importe quel autre nom inconnu (le message vous renvoie quand même verssince).
Les paramètres de requête (query string) ne changent pas : un paramètre inconnu y reste ignoré.
C'est un changement de comportement : un appel qui envoyait un champ ignoré recevait 200 et reçoit maintenant 400. Sur 90 jours de trafic, 417 appels étaient dans ce cas. Si l'un de vos appels se met à répondre 400, le message nomme le champ en cause.
17 septembre 2026 — confidence de la recherche de contrat multi-opérateurs est désormais calculée
confidence sur les résultats de la recherche de contrat multi-opérateurs prenait quelques valeurs fixes selon le chemin de recherche (100, 70, 50, 40, 20…), si bien que des lignes étayées différemment étaient à égalité : un nom à une lettre près valait autant qu'un nom sans rapport, un numéro de rue à un près autant qu'une autre rue.
Elle est maintenant calculée à partir de ce que vous avez envoyé et de la concordance de chaque élément avec ce que l'opérateur détient — voir confidence et messages. En bref :
100signifie toujours que l'identifiant est confirmé et que tout ce que vous avez envoyé concorde ; un élément que l'opérateur ne peut pas vérifier ne coûte toujours rien ;- un élément qui ne concorde qu'en partie fait baisser le score à hauteur de l'écart, et les éléments qui concordent le diluent :
id=…&name=Pilse classe au-dessus deid=…&name=Plop, et un nom erroné à côté d'une adresse confirmée au-dessus du même nom erroné seul ; - un point localisé par votre adresse plutôt que par identifiant obtient un score d'autant plus bas que des points partagent cette adresse, plus bas encore sans numéro de rue ; un nom de titulaire qui concorde le fait remonter.
Les codes de messages ne changent pas — seul le nombre bouge. Lisez-le comme un classement entre lignes plutôt qu'un seuil : les valeurs ne sont plus des paliers fixes, et un client qui comparait confidence à 70 ou 40 devrait plutôt se fonder sur les messages de la ligne.
16 septembre 2026 — GET /ask?status=EXPIRED est désormais accepté
EXPIRED est documenté comme statut d'un Ask, mais le filtre status de GET/ask le refusait avec une 400 : c'est un statut dérivé, que nous calculons à la lecture, et le filtre ne connaissait que les statuts stockés.
Il l'accepte maintenant, à l'inclusion comme à l'exclusion :
GET /ask?status=EXPIRED # les consentements arrivés à échéance
GET /ask?status=ACCEPTED,!EXPIRED # ceux qui sont encore valides
GET /ask?status=CREATED,EXPIRED # l'un ou l'autre
EXPIRED et ACCEPTED se recouvrent volontairement : un consentement expiré reste ACCEPTED en base, seule sa date de fin est passée. status=ACCEPTED continue donc de renvoyer les Asks expirés — c'est le comportement actuel, et nous ne le changeons pas pour ne casser personne. Pour les seuls consentements encore actifs, demandez ACCEPTED,!EXPIRED.
15 septembre 2026 — le délai de signature d'un Ask est documenté
Un Ask doit être signé dans les 2 mois qui suivent sa création. C'est le cas depuis juillet 2026, ce n'était simplement pas écrit. Passé ce délai, consentCollectionDetails.userUrl n'accepte plus de signature et invite le signataire à vous redemander un mandat : il vous faut alors créer un nouvel Ask. Le détail est dans le guide.
Ce délai est indépendant de consentDuration, et le statut de l'Ask ne change pas quand il est dépassé : un Ask jamais signé reste en PENDING_USER_ACTION. EXPIRED ne concerne que les Asks déjà signés arrivés au terme de leur consentDuration.
14 septembre 2026 — un Ask révoqué avant signature est désormais renvoyé, avec un signer à null
POST/ask/{askId}/revoke n'a pas de prérequis : vous pouvez révoquer un Ask qui attend encore sa signature, pour l'annuler. Un tel Ask n'a jamais eu de signataire — et nos endpoints de lecture ne s'y attendaient pas. GET/ask le retirait silencieusement de la liste (returnedItemsCount revenait inférieur à totalCount, sans indiquer quel Ask manquait), et GET/ask/{askId} répondait une 500.
Les deux renvoient maintenant l'Ask, avec status: "REVOKED" et signer: null.
GET/ask/{askId}/proof sur un de ces Asks répond une 404 au lieu d'une 500 : rien n'a été signé, il n'y a donc pas de preuve à servir.
signer est désormais nullable lorsque status vaut REVOKED. Il reste non-null pour ACCEPTED et EXPIRED, qui impliquent tous deux que l'Ask a été signé. Un client qui valide strictement la réponse (schéma, types générés depuis une version antérieure de la spécification OpenAPI) doit régénérer ses types, et lire signer sur un Ask REVOKED avec prudence : il vaut null exactement quand acceptedAt vaut null.
8 septembre 2026 — courbe de charge : l'activation de la collecte est de nouveau systématique, et l'erreur le dit
Le 10 juin 2026, Enedis a retiré le motif qui disait explicitement qu'un point n'avait aucun service de collecte de courbe de charge souscrit :
La demande ne peut aboutir : aucun service souscrit de courbe de charge n'est présent pour la période demandée
Ce cas est depuis renvoyé sous deux motifs génériques — le plus souvent « Absence de données de mesure », parfois « L'état contractuel du point ne permet pas de traiter cette demande » — qui couvrent aussi les points sans mesure sur la période. Le volume d'échecs n'a pas bougé, seul le libellé a changé.
Trois conséquences côté API, à partir d'aujourd'hui :
- L'activation de la collecte redevient systématique. Quand un de ces motifs tombe sur une demande
R63/LOADCURVE, nous vérifions auprès d'Enedis si la collecte est souscrite sur le point ; si elle ne l'est pas, nous en demandons l'activation — que la requête ait ou non l'optionenedisRetryAfterLoadcurveActivation. Vos prochaines commandes sur ces points peuvent alors aboutir. - Un message explicite remplace le motif générique dans ce cas précis.
errorMessagedevient « La collecte de courbe de charge n'était pas souscrite sur ce point… », avec lereasonCodeENEDIS_CCDC_AUCUN_SERVICE_SOUSCRIT— celui d'avant juin 2026. Quand la collecte est souscrite, rien ne change : le motif d'Enedis vous est restitué tel quel. enedisRetryAfterLoadcurveActivationpeut être activé par défaut sur votre workspace. Demandez-le nous : la requête attend alors l'activation et rejoue la demande toute seule, au prix d'unPENDINGd'environ une heure (parfois une dizaine) au lieu d'un échec en moins de deux minutes. Une commande qui envoie le champ explicitement garde toujours la main,falsecompris.
Aucun champ n'est ajouté ni supprimé. Si vous testez errorMessage par égalité de chaîne sur « Absence de données de mesure » pour détecter une courbe non activée, basculez sur reasonCode === "ENEDIS_CCDC_AUCUN_SERVICE_SOUSCRIT".
7 septembre 2026 — extraction de factures : nouveau moteur de lecture, et un champ error par fichier
POST/integration/enedis/search_contracts_from_invoices lit désormais les factures avec un algorithme d'extraction amélioré, le même que celui de notre extraction de prix. Concrètement, le nom du titulaire et l'adresse du point de livraison sont extraits plus proprement — sans civilité, sans nom de résidence, de bâtiment ni de numéro d'appartement, et en distinguant le lieu de consommation de l'adresse de facturation lorsque la facture donne les deux. Ce sont ces deux valeurs qui sont comparées au réseau de distribution : mieux les lire, c'est trouver le contrat plus souvent.
Cette version corrige aussi un défaut qui rendait l'endpoint inexploitable : une vérification d'autorisation interne, prévue pour nos outils d'administration, s'appliquait à tous les appels et faisait échouer l'extraction. L'échec était silencieux — la réponse était un 200 avec des tableaux vides.
Chaque entrée de resultsByFile porte désormais un champ error :
error: null: la facture a été lue. Des tableauxcontractsetothersvides veulent alors vraiment dire « nous avons lu cette facture et aucun contrat ne correspond » ;error: "<message>": nous n'avons pas réussi à lire le fichier. Le message distingue les deux cas qui vous concernent : le fichier ne contient rien de lisible (envoyez un meilleur scan), ou la lecture a échoué de notre côté (réessayez).
Le champ est ajouté à la réponse, aucun champ existant ne change. Un client qui valide la réponse de façon stricte (schéma, types générés depuis une version antérieure de la spécification OpenAPI) doit régénérer ses types.
28 août 2026 — rate-limits sur la restitution de données
Les endpoints qui restituent une courbe de charge — GET/integration/enedis/request/{requestId}/data et leurs équivalents de l'ancienne API enedis-v2 — sont désormais soumis à une rate-limit par projet : 5 appels par seconde, et 120 par minute. Les deux fenêtres sont glissantes et le budget est commun à toutes ces routes.
C'est l'appel le plus coûteux de l'API : une rafale soutenue depuis un seul projet allonge les temps de réponse pour tout le monde. Le plafond est volontairement placé au-dessus de ce que consomme notre intégration la plus active, il ne devrait donc se déclencher que sur une boucle de retry ou un export massivement parallélisé.
Au-delà, l'appel est rejeté avec un statut 429 Too Many Requests sans lire la donnée, et sans consommer de budget : dès que la fenêtre glisse, l'appel suivant passe. Réessayez avec un backoff exponentiel.
La limite déjà en place sur GET/integration/enedis/search_contract (2 appels par seconde et par projet) est désormais documentée elle aussi.
Ces limites sont configurables projet par projet : si votre usage légitime les dépasse, contactez-nous. Voir Rate-limits de l'API pour le détail.
27 août 2026 — points de livraison hors GRDF et statut NOT_VERIFIABLE
Les réseaux de distribution de gaz qui ne sont pas exploités par GRDF — les entreprises locales de distribution (ELD) comme Régaz-Bordeaux ou R-GDS — numérotent leurs points de livraison à leur façon, et ces formats ne sont pour la plupart documentés nulle part. Jusqu'ici nous refusions ces identifiants avec une erreur 400 : il était impossible d'inclure un tel compteur dans un mandat.
Désormais :
- POST
/askaccepte, pour un contrat_energyType: gas, undeliveryPointIddont nous ne connaissons pas le format (32 caractères maximum) lorsque l'adresse indique que le réseau n'est pas exploité par GRDF. Sur une adresse desservie par GRDF, undeliveryPointIdqui n'est pas un PCE valide (14 chiffres, ouGIsuivi de 6 chiffres) reste refusé avec une erreur 400 ; - un contrat dont le réseau est exploité par un distributeur auquel nous ne sommes pas intégrés prend le nouveau statut
NOT_VERIFIABLE: nous n'avons interrogé personne, donc nous ne pouvons ni le confirmer ni l'infirmer. Il figure sur le mandat, qui reste signable, mais aucune donnée ne pourra être récupérée pour ce point de livraison. SonstatusTextl'explicite ; - un contrat
NOT_VALID— un distributeur auquel nous sommes intégrés a répondu que le point de livraison n'existe pas, ou que le titulaire ne correspond pas — continue, lui, de rendre l'AskentierNOT_VALID.
Ce changement peut casser une intégration existante : il ajoute une valeur à l'énumération du status des contrats, et il rompt deux hypothèses que votre code fait peut-être aujourd'hui. Les trois points ci-dessous détaillent lesquelles.
1. Une nouvelle valeur apparaît dans l'énumération du status des contrats. Les réponses de GET/ask/{askId} peuvent désormais contenir NOT_VERIFIABLE. Un client qui valide cette énumération de façon stricte — schéma de validation, types générés depuis une version antérieure de la spécification OpenAPI, switch exhaustif — échouera au décodage sur une valeur qu'il ne connaît pas.
2. Un Ask peut être PENDING_USER_ACTION alors que tous ses contrats ne sont pas VALID. Si votre code déduit l'état des compteurs de celui du mandat — « le mandat est signable, donc tous les contrats sont valides », ou « un contrat n'est pas valide, donc l'Ask sera NOT_VALID » — cette hypothèse n'est plus vraie. Après signature, un contrat NOT_VERIFIABLE ne donnera jamais lieu à une livraison de données : vérifiez le statut de chaque contrat plutôt que celui de l'Ask.
3. Un deliveryPointId gaz mal formé ne provoque plus systématiquement une erreur 400. Si vous vous appuyiez sur cette 400 pour valider une saisie utilisateur, un identifiant erroné sur une adresse hors GRDF crée maintenant un Ask — avec un contrat NOT_VERIFIABLE — au lieu d'être rejeté.