Développeurs
API publique et webhooks Control
Control reste autonome : toute intégration externe est facultative et passe par des appels signés. Aucune donnée voyageur n'est échangée.
1. Authentification des appels
Chaque requête entrante et chaque webhook sortant est signé en HMAC-SHA256 avec la clé partagée de l'intégration. La signature porte sur la concaténationtimestamp + "." + corps brut et le corps doit être transmis tel quel, sans reformatage.
x-control-timestamp: 1756838400000 # millisecondes, tolérance ±5 minutes
x-control-signature: sha256=<hex minuscule> # HMAC-SHA256(clé, timestamp + "." + body)
x-control-event-id: <uuid> # webhooks sortants uniquement (idempotence)
content-type: application/json # corps limité à 64 KoUn horodatage hors tolérance ou une signature invalide renvoie 401, un rejeu déjà traité renvoie 409, un corps non conforme 400.
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(secret, timestamp, rawBody, header) {
const expected = createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
const received = header.replace(/^sha256=/, "");
const a = Buffer.from(expected);
const b = Buffer.from(received);
return a.length === b.length && timingSafeEqual(a, b);
}TS=$(date +%s000)
BODY='{"connection_id":"00000000-0000-0000-0000-000000000000"}'
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$CONTROL_SHARED_SECRET" -hex | awk '{print $2}')
curl -X POST https://control.marrerosterenn.fr/api/public/heating/status \
-H "content-type: application/json" \
-H "x-control-timestamp: $TS" \
-H "x-control-signature: sha256=$SIG" \
-d "$BODY"2. Points d'entrée
Ouvre une demande de liaison et renvoie l'URL de consentement à présenter au propriétaire.
Requête
{
"external_account_id": "ms-owner-42",
"external_account_email": "proprietaire@example.com",
"external_property_id": "ms-property-001",
"scopes": ["heating:read", "heating:write"],
"redirect_uri": "https://exemple.fr/retour-control",
"state": "a1b2c3d4e5f6a7b8c9d0"
}Réponse
{ "request_id": "…", "consent_url": "https://…/liaison?…", "expires_at": "…" }Échange le code temporaire (durée de vie 2 minutes) contre la connexion active.
Requête
{ "code": "…", "state": "a1b2c3d4e5f6a7b8c9d0" }Réponse
{ "connection_id": "…", "organization_id": "…", "scopes": ["heating:read"] }Retourne l'état de la liaison et du forfait Control pour un compte externe.
Requête
{ "external_account_id": "ms-owner-42" }Réponse
{ "status": "authorized", "subscription_status": "active" }Lecture du chauffage, des consignes et des alertes du logement relié.
Requête
{ "connection_id": "…" }Réponse
{ "mode": "eco", "target_temperature": 17, "devices": [], "alerts": [] }Applique un mode ou une consigne. Nécessite le droit heating:write ; bornée par les seuils du logement.
Requête
{ "connection_id": "…", "mode": "comfort", "target_temperature": 20 }Réponse
{ "applied": 2, "failed": 0, "target_temperature": 20 }Vue conciergerie / propriétaire : liste en direct les appareils réels du logement (ballon, chauffage, prises, capteurs) avec pièce, état et actions possibles.
Requête
{ "external_property_id": "ms-property-001" }Réponse
{
"property_name": "La Maison de Marylou",
"controllable": true,
"emergency_stop": false,
"devices": [{ "device_id": "…", "name": "Ballon", "kind": "water_heater", "room": "Garage",
"online": true, "state": "off", "actions": ["turn_on", "turn_off"] }]
}Pilote un appareil (turn_on, turn_off, set_temperature). Refusé si arrêt d'urgence, installation non validée ou abonnement inactif. Chaque commande est journalisée avec sa source (concierge ou owner).
Requête
{
"external_property_id": "ms-property-001",
"device_id": "…",
"action": "turn_on",
"source": "concierge"
}Réponse
{ "ok": true, "device_id": "…", "action": "turn_on", "state": "on" }Transmet une période d'occupation minimale. Aucune donnée voyageur n'est acceptée.
Requête
{
"connection_id": "…",
"external_property_id": "ms-property-001",
"occupied_from": "2026-09-05T15:00:00.000Z",
"occupied_until": "2026-09-08T10:00:00.000Z"
}Réponse
{ "ok": true }Synchronise la liste des logements autorisés côté application externe.
Requête
{
"organization_id": "…",
"properties": [{ "external_property_id": "ms-property-001", "display_name": "Studio Port" }]
}Réponse
{ "linked": 1, "unlinked": 0 }Révoque une connexion. Control cesse immédiatement d'accepter ses commandes.
Requête
{ "connection_id": "…", "reason": "compte fermé" }Réponse
{ "ok": true, "status": "revoked" }3. Webhooks sortants signés
Control envoie chaque événement à l'URL webhook configurée, avec les mêmes en-têtes de signature. La livraison est réessayée avec un report exponentiel (jusqu'à 8 tentatives, plafonné à 6 heures). Répondez 2xx pour acquitter, ou 409 si l'événement a déjà été traité : Control considère alors la livraison comme réussie. Chaque tentative est consignée dans le journal d'événements signés de l'administration Control.
{
"event_id": "8f0f9a4c-…", // identifiant unique, sert d'idempotence
"event": "heating.status_changed",
"version": 1,
"occurred_at": "2026-09-02T18:12:04.220Z",
"sent_at": "2026-09-02T18:12:05.010Z",
"organization_id": "…",
"connection_id": "…", // null si aucune liaison externe active
"control_property_id": "…",
"data": { "mode": "comfort", "target_temperature": 20 }
}Nouvelle réservation importée d'un flux iCal ou saisie manuellement.
"data": {
"reservation_id": "…",
"calendar_id": "…",
"source": "airbnb",
"external_uid": "…",
"starts_at": "2026-09-05T14:00:00.000Z",
"ends_at": "2026-09-08T11:00:00.000Z",
"status": "upcoming"
}Dates, statut ou résumé modifiés lors d'une synchronisation.
"data": { "reservation_id": "…", "starts_at": "…", "ends_at": "…", "status": "in_progress" }Réservation annulée manuellement ou disparue du flux iCal.
"data": { "reservation_id": "…", "starts_at": "…", "ends_at": "…" }Consignes ou bornes de température du logement modifiées.
"data": { "min_temperature": 7, "max_temperature": 24, "comfort_temperature": 20, "eco_temperature": 17, "frost_temperature": 8 }Mode ou consigne appliqués (action utilisateur, API ou automatisation).
"data": { "mode": "comfort", "target_temperature": 20, "devices_applied": 2, "devices_failed": 0, "origin": "automation", "changed_at": "…" }Réglages du ballon modifiés (mode, consigne, heures creuses, cycle sanitaire).
"data": { "mode": "auto", "target_temperature": 55, "offpeak_enabled": true, "sanitary_cycle_temperature": 60 }Marche, arrêt ou cycle sanitaire du ballon d'eau chaude.
"data": { "action": "on", "temperature": 55, "measured": false, "changed_at": "…" }4. Règles et limites
- Aucune donnée voyageur (nom, contact, montant) n'est émise ni acceptée.
- Les commandes de chauffage restent bornées par les seuils du logement et refusées en cas d'arrêt d'urgence.
- La révocation d'une liaison est immédiate et bilatérale : Control continue de fonctionner sans l'intégration.
- Corps limité à 64 Ko, tolérance d'horodatage de ±5 minutes, anti-rejeu actif.