Ces routes gèrent les appareils et les profils d’appareils par code. Les chemins se trouvent sous https://<votre hôte>/tenant/<tenantId>. Les conventions (pagination, corps stricts, heures) sont décrites dans REST API basics.
Appareils
| Méthode | Chemin | Permission | Action |
|---|---|---|---|
| GET | /api/iot/devices | iot_device.devices.read | Lister les appareils |
| POST | /api/iot/devices | iot_device.devices.create | Créer un appareil et son jeton d’accès |
| GET | /api/iot/devices/{deviceId} | iot_device.devices.read | Lire un appareil |
| PATCH | /api/iot/devices/{deviceId} | iot_device.devices.update | Modifier le nom, le libellé, le type, le profil, le statut, les informations supplémentaires |
| DELETE | /api/iot/devices/{deviceId} | iot_device.devices.delete | Supprimer un appareil |
| GET | /api/iot/devices/{deviceId}/connection | iot_device.devices.read | Où l’appareil se connecte |
| GET | /api/iot/devices/{deviceId}/connections | iot_device.devices.read | Le journal des connexions |
| GET | /api/iot/devices/{deviceId}/log | iot_device.devices.read | Journal de connexion et lignes d’audit, fusionnés |
Lister les appareils
GET /api/iot/devices (pages à partir de 1)
| Paramètre | Valeurs |
|---|---|
page | à partir de 1 (par défaut 1) |
pageSize | de 1 à 100 (par défaut 20) |
search | jusqu’à 128 caractères : nom, libellé ou numéro de série |
deviceProfileId | un identifiant de profil |
status | ACTIVE, QUARANTINED ou DISABLED |
isGateway | true ou false |
online | true, false ou unknown |
sort | name, connection, lastSeen, createdAt, avec :asc ou :desc |
{
"devices": [
{
"deviceId": "6f1c…",
"name": "pump-station-04",
"label": "Pump station 4",
"type": "pump-controller",
"serial": null,
"deviceProfileId": "a1b2…",
"status": "ACTIVE",
"isGateway": false,
"gatewayDeviceId": null,
"model": null,
"fwTitle": null,
"fwVersion": null,
"online": true,
"active": true,
"lastConnectAt": "2026-11-08T06:12:03.000Z",
"lastDisconnectAt": null,
"lastActivityAt": "2026-11-09T09:41:10.000Z",
"lastRemoteIp": "203.0.113.20",
"lastTransport": "mqtts",
"additionalInfo": {},
"createdAt": "2026-10-01T08:00:00.000Z",
"updatedAt": "2026-11-08T06:12:03.000Z"
}
],
"total": 1
}
online vaut null sur un profil dont la règle de présence est Dernière activité uniquement. GET /api/iot/devices/{deviceId} répond { "device": {...} } avec les mêmes champs, plus profileName et profileTransport.
Créer un appareil
POST/tenant/{tenantId}/api/iot/devices
Champs : name (obligatoire, 1 à 128 caractères, unique), label (jusqu’à 255), type (jusqu’à 64), deviceProfileId (sinon le profil par défaut), isGateway, additionalInfo (un objet d’au plus 50 clés et 8 Kio). La réponse contient l’appareil et, pour les profils dont les appareils s’authentifient eux-mêmes, un jeton d’accès dans credential.secret.token. Le jeton n’est affiché que dans cette réponse.
Corps de la requête application/json
{
"name": "pump-station-04",
"label": "Pump station 4",
"deviceProfileId": "a1b2…"
}
Exemple curl
curl -X POST 'https://<your-host>/tenant/{tenantId}/api/iot/devices' \
-H 'Content-Type: application/json' \
-d '{"name":"pump-station-04","label":"Pump station 4","deviceProfileId":"a1b2…"}'
Réponses
201Créé
{
"device": {
"deviceId": "6f1c…",
"name": "pump-station-04",
"status": "ACTIVE",
"…": "…"
},
"credential": {
"credential": {
"credentialId": "…",
"type": "ACCESS_TOKEN",
"…": "…"
},
"secret": {
"token": "Xq3…"
}
}
}
403Interdit
{
"message": "The device limit of this account is reached.",
"code": "FORBIDDEN",
"details": {
"key": "max_devices",
"limit": 43,
"current": 43,
"adding": 1
}
}
409Conflit
{
"message": "A device with this name already exists.",
"code": "CONFLICT",
"details": {
"reason": "name_taken"
}
}
Modifier un appareil
PATCH /api/iot/devices/{deviceId} accepte l’un quelconque de name, label, type, deviceProfileId, isGateway, additionalInfo, status (ACTIVE ou DISABLED). N’envoyez que ce qui change. Un appareil en quarantaine s’approuve via le provisioning, pas ici : « Un appareil en quarantaine s’active via le provisioning, et non en le modifiant ici. » Un nouveau nom, un nouveau profil ou une désactivation met fin à la session active de l’appareil ; il se reconnecte avec les nouveaux paramètres.
Supprimer un appareil
DELETE répond { "message": "Device deleted." }. Les relevés de l’appareil sont supprimés en arrière-plan. Un appareil encore utilisé est refusé avec 409, par exemple « The device is still in use by 1 asset placement or relation (iot_asset). » Retirez-le de son actif et supprimez d’abord ses relations : voir Placing devices et Relations.
Détails de connexion
GET /api/iot/devices/{deviceId}/connection ne renvoie aucun secret :
{
"connection": {
"mqtt": { "host": "mqtt.example.com", "port": 8883, "tls": true },
"httpBase": "https://acme.example.com",
"topics": { "telemetry": "v1/telemetry", "attributes": "v1/attributes", "rpcRequest": "v1/rpc/req/+", "rpcResponse": "v1/rpc/res/{id}" },
"telemetryKeys": [{ "key": "temperature", "type": "number" }]
}
}
mqtt vaut null lorsque le profil refuse MQTT sur cet écouteur ; rpcRequest et rpcResponse valent null avec le schéma Legacy compatible.
Journaux
GET .../connections?page=1&pageSize=50: connexions et déconnexions, les plus récentes d’abord (pages à partir de 1,pageSizejusqu’à 100).GET .../log?page=1&pageSize=50&source=connection|audit: le journal de connexion et les lignes d’audit de l’appareil, fusionnés, les plus récents d’abord.page×pageSizene doit pas dépasser 1 000.
Identifiants de l’appareil
| Méthode | Chemin | Action |
|---|---|---|
| GET | /api/iot/devices/{deviceId}/credentials | L’identifiant actif, sans son secret |
| PUT | /api/iot/devices/{deviceId}/credentials | Passer à un autre identifiant |
| POST | /api/iot/devices/{deviceId}/credentials/rotate | Un nouveau secret du même type (201) |
| DELETE | /api/iot/devices/{deviceId}/credentials | Révoquer l’identifiant |
Toutes requièrent iot_device.credentials.manage. Le passage, la rotation et la révocation mettent fin immédiatement à l’ancien identifiant (sans période de tolérance) et déconnectent l’appareil.
Corps des PUT :
{ "type": "ACCESS_TOKEN" }
{ "type": "MQTT_BASIC", "username": "pump04", "clientId": "pump04", "password": null }
{ "type": "X509_CERTIFICATE", "certificateId": "…" }
Pour MQTT_BASIC, omettez password (ou mettez null) pour obtenir un mot de passe généré. Un mot de passe choisi doit comporter au moins 16 caractères, variés, et ne pas contenir le nom d’utilisateur ni l’identifiant client. Le nom d’utilisateur provision est réservé. La réponse est { "credential": {...}, "secret": { "token": "…" } } ou { ..., "secret": { "password": "…" } }, affichée une seule fois ; un identifiant X.509 n’a pas de secret. Un identifiant X.509 se renouvelle en associant un nouveau certificat : « Un identifiant X.509 se renouvelle en associant un nouveau certificat. » Voir Device credentials.
Profils d’appareils
| Méthode | Chemin | Permission | Action |
|---|---|---|---|
| GET | /api/iot/device-profiles | iot_device_profile.profiles.read | Liste (page à partir de 1, pageSize, search, transportType, onlineRule, sort) |
| POST | /api/iot/device-profiles | iot_device_profile.profiles.write | Créer |
| GET | /api/iot/device-profiles/{profileId} | .profiles.read | Lire |
| PATCH | /api/iot/device-profiles/{profileId} | .profiles.write | Modifier ; envoyer expectedVersion |
| DELETE | /api/iot/device-profiles/{profileId} | .profiles.write | Supprimer |
| POST | /api/iot/device-profiles/{profileId}/default | .profiles.write | En faire le profil par défaut |
| POST | /api/iot/device-profiles/{profileId}/duplicate | .profiles.write | Le copier sous le nom « |
| GET | /api/iot/device-profiles/{profileId}/key-usage?keys=a,b | .profiles.read | Qui lit ces clés (règles, cartes, flux) |
| GET, PUT, POST | /api/iot/device-profiles/{profileId}/provisioning | iot_device_profile.provisioning.manage | Paramètres de provisioning ; POST fait tourner ou révoque une clé de provisioning |
Champs de tri : name, transport, online, updated. Une liste répond { "profiles": [...], "total": n } ; un profil, { "profile": {...} }.
Créer un profil
Un nouveau profil requiert un name (jusqu’à 128 caractères) et une onlineRule : LIVE_SOCKET (Connexion en direct), UPLINK_WINDOW (Message récent, nécessite aussi expectedIntervalS, de 60 à 604 800), ou LAST_SEEN_ONLY (Dernière activité uniquement). Chaque autre champ prend sa valeur par défaut.
{
"name": "Cold room sensor",
"onlineRule": "UPLINK_WINDOW",
"expectedIntervalS": 300,
"transportType": "MQTT",
"telemetrySchema": [
{ "key": "temperature", "type": "number", "unit": "°C", "label": "Temperature", "min": -30, "max": 10, "decimals": 1 }
]
}
Champs retenus et leurs plages :
| Champ | Plage |
|---|---|
transportType | MQTT, HTTP, ANY, ou un protocole enregistré |
allowedAuthModes | 1 à 3 parmi ACCESS_TOKEN, MQTT_BASIC, X509_CERTIFICATE |
mqttTransport | TLS_REQUIRED (par défaut) ou PLAINTEXT_ALLOWED |
allowedNetworks | jusqu’à 64 réseaux ou adresses |
payloadMaxBytes | 1 024 à 65 536 |
downlinkMaxBytes | 1 024 à 65 536 |
inactivityTimeoutSec | 10 à 604 800 |
maxGatewayChildren | 0 à 10 000 |
rpcTimeoutDefaultSec | 1 à 300 |
onlineWindowFactor | 1,5 à 10, une décimale |
telemetrySchema, cfgSchema | jusqu’à 500 clés chacun |
rateLimits | count:seconds[,count:seconds] par famille, par exemple "telemetry_msgs": "10:1,300:60" |
Lisez d’abord un profil existant pour voir chaque champ avec sa valeur actuelle ; l’éditeur de profils couvre les mêmes champs avec des explications, voir Profils d’appareils.
Refus
Un corps qui enfreint une règle de champ renvoie une 400 avec les problèmes dans issues, chacun avec son path et son message, par exemple online_rule_required (un nouveau profil nécessite une règle de connexion), expected_interval_required (Message récent nécessite expectedIntervalS) ou online_rule_not_allowed (la règle de connexion ne convient pas au transport).
Les autres refus portent details.reason, par exemple :
| Motif | Signification |
|---|---|
stale_profile | Quelqu’un a enregistré le profil depuis votre lecture (409) |
name_taken | Un autre profil porte déjà ce nom |
default_profile | Le profil par défaut ne peut pas être supprimé |
builtin_profile | Un profil intégré ne peut pas être modifié de cette façon |
still_referenced | Des appareils ou d’autres enregistrements utilisent encore le profil |
reference_unverifiable | Un module susceptible d’utiliser le profil n’a pas pu être interrogé ; réessayez |

