Une commande (un RPC) demande à un appareil d’effectuer une action et, sauf si elle est à sens unique, attend la réponse de l’appareil. Chaque commande est enregistrée comme une requête avec un état, afin que vous puissiez l’envoyer maintenant et lire le résultat plus tard. Le fonctionnement des commandes pour les utilisateurs de l’application est décrit dans Envoyer des commandes aux appareils. Les chemins sont sous https://<your host>/tenant/<tenantId>.
En cours de développement : les commandes vers les appareils derrière une passerelle (les appareils enfants d’une passerelle) sont en cours de développement. Envoyez des commandes aux appareils qui se connectent d’eux-mêmes.
Routes
| Méthode | Chemin | Permission | Action |
|---|---|---|---|
| POST | /api/iot/devices/{deviceId}/rpc | iot_rpc.read plus le niveau de la commande | Envoyer une commande |
| GET | /api/iot/devices/{deviceId}/rpc | iot_rpc.read | Historique des commandes de l’appareil |
| GET | /api/iot/rpc/{rpcId} | iot_rpc.read | Une commande avec sa réponse |
| POST | /api/iot/rpc/{rpcId}/cancel | iot_rpc.read plus le niveau de la commande | Annuler une commande en file d’attente ou envoyée |
| GET | /api/iot/devices/{deviceId}/rpc-commands | iot_rpc.send.basic | Les commandes qu’un contrôle de tableau de bord peut envoyer à cet appareil |
| GET | /api/iot/rpc-commands/{profileId} | iot_rpc.read | Catalogue de commandes d’un profil (?format=export pour le format d’import) |
| POST | /api/iot/rpc-commands/{profileId} | iot_rpc.commands.manage | Ajouter une commande au catalogue |
| PUT | /api/iot/rpc-commands/{profileId} | iot_rpc.commands.manage | Remplacer tout le catalogue |
| PATCH, DELETE | /api/iot/rpc-commands/{profileId}/{commandId} | iot_rpc.commands.manage | Modifier ou supprimer une commande |
Qui peut envoyer quoi
Chaque commande du catalogue d’un profil a un niveau. Le niveau détermine la permission :
| Niveau | Permission | Rôle par défaut | Une clé d’API peut-elle la détenir ? |
|---|---|---|---|
| basic | iot_rpc.send.basic | Utilisateur | Oui |
| privileged | iot_rpc.send.privileged | Administrateur | Oui |
| critical | iot_rpc.send.critical | Administrateur | Oui |
| brut (une méthode absente du catalogue) | iot_rpc.send.raw | Propriétaire | Non |
Ainsi, une clé d’API ne peut envoyer que les méthodes présentes dans le catalogue du profil d’appareil.
Envoyer une commande
POST/tenant/{tenantId}/api/iot/devices/{deviceId}/rpc
Champs : method (obligatoire, namespace.verb, jusqu’à 64 caractères), params (JSON quelconque, 16 KiB au maximum), oneway, persistent, timeoutMs (1 000 à 600 000 ; par défaut celui du catalogue ou du profil, sinon 10 000), expiresInS (1 à 604 800, pour une commande persistante ; par défaut 86 400), wait, confirm, confirmText, source (api, console ou dashboard_control). Une commande à deux sens, non persistante, avec un délai d’au plus 30 secondes attend sa fin et répond 200 ; sinon elle répond 202 et s’exécute en arrière-plan.
Corps de la requête application/json
{
"method": "pump.start",
"params": {
"speed": 80
},
"timeoutMs": 15000
}
Exemple curl
curl -X POST 'https://<your-host>/tenant/{tenantId}/api/iot/devices/{deviceId}/rpc' \
-H 'Content-Type: application/json' \
-d '{"method":"pump.start","params":{"speed":80},"timeoutMs":15000}'
Réponses
200OK
{
"rpc": {
"rpcId": "c0de…",
"deviceId": "6f1c…",
"method": "pump.start",
"status": "SUCCESSFUL",
"outcome": null,
"response": {
"ok": true
},
"error": null,
"tier": "privileged",
"highImpact": false,
"source": "api",
"createdAt": "2026-11-09T09:00:00.000Z",
"completedAt": "2026-11-09T09:00:01.200Z",
"…": "…"
}
}
202Acceptée
{
"rpc": {
"rpcId": "c0de…",
"status": "QUEUED",
"…": "…"
}
}
403Interdit
{
"message": "You are not permitted to perform this action",
"code": "FORBIDDEN"
}
409Conflit
{
"message": "The device is disabled or quarantined; it takes no commands.",
"code": "CONFLICT",
"details": {
"reason": "device_not_active"
}
}
429Trop de requêtes
{
"message": "Too many commands to this device; try again in a minute.",
"code": "RATE_LIMIT_EXCEEDED",
"details": {
"reason": "device_rate_limited"
}
}
- Sens unique (
oneway: true) : la commande réussit lorsqu’elle est livrée ; aucune réponse n’est attendue. - Persistante (
persistent: true) : si l’appareil est hors ligne, la commande l’attend jusqu’àexpiresInS, puis se termine enEXPIRED. - wait :
trueimpose l’attente (refusé avec « A synchronous call waits at most 30 s; use wait: false for a longer timeout. » lorsque la commande ne peut pas se terminer en 30 s) ;falserépond toujours202immédiatement. - Commandes à fort impact nécessitent
"confirm": true, et la phrase saisie dansconfirmTextlorsque la commande du catalogue en comporte une. Sans cela : « This command needs confirm: true. » Elles sont inscrites dans le journal d’audit à l’envoi et à leur fin, et sont envoyées au plus une fois.
Cette route n’a pas de clé d’idempotence : chaque POST crée une nouvelle commande. Si votre appel expire sur le réseau, lisez l’historique de l’appareil avant de renvoyer une commande.
États
QUEUED → SENT → DELIVERED → SUCCESSFUL, ou l’un de FAILED, TIMEOUT, EXPIRED, CANCELLED.
| État | Signification |
|---|---|
SUCCESSFUL | La réponse est arrivée, ou une commande à sens unique a été livrée |
FAILED | L’appareil l’a refusée, ou elle n’a pas pu être envoyée |
TIMEOUT | Pas de réponse à temps. Le résultat est inconnu : l’appareil a peut-être exécuté la commande. outcome vaut unknown. |
EXPIRED | Elle n’a jamais atteint l’appareil avant son expiration |
CANCELLED | Annulée en file d’attente ou envoyée |
outcome apporte un détail : running (l’appareil a accepté une commande de longue durée), unknown (un délai dépassé) ou late (une réponse est arrivée après la fin). Les résultats sont conservés 90 jours par défaut.
Lire les résultats
Interrogez GET /api/iot/rpc/{rpcId} jusqu’à ce que l’état soit final, ou abonnez-vous aux événements webhook rpc.completed et rpc.failed et lisez la réponse à leur réception. Voir la Référence des événements webhook.
Historique d’un appareil, du plus récent au plus ancien :
GET /api/iot/devices/{deviceId}/rpc?status=FAILED&limit=50
Sans page, l’historique se lit avec un curseur : status, method, from, to (dates), limit (1 à 100, par défaut 25) et cursor. La réponse est { "requests": [...], "nextCursor": "…" } ; renvoyez nextCursor comme cursor jusqu’à ce qu’il soit null. Avec page (à partir de 0), la liste suit les règles habituelles des listes : pageSize, search, sort (method, status, source, createdAt, completedAt), status et source sous forme de listes séparées par des virgules, createdFrom, createdTo ; la réponse est { "requests": [...], "total": n }. Les paramètres secrets sont masqués dans toutes les réponses.
Annuler
POST /api/iot/rpc/{rpcId}/cancel fait passer une commande QUEUED ou SENT à CANCELLED et répond { "rpc": {...} }. Autres états : 409 « Only a queued or sent request can be cancelled. » L’annulation exige la même permission de niveau que l’envoi. Une annulation n’envoie aucun webhook.
Limites
| Limite | Valeur par défaut |
|---|---|
| Commandes par appareil | 10 par minute (429, Retry-After: 60) |
| Commandes par organisation | 600 par minute (429 « Too many requests for your plan. Please upgrade or try again later. ») |
| Paramètres | 16 KiB de JSON |
| Réponse conservée | 64 KiB |
| Commandes par catalogue de profil | 200 |
Refus
| Statut | details.reason | Message |
|---|---|---|
| 400 | bad_params | Les paramètres ne correspondent pas à la commande. |
| 400 | wait_too_long | Un appel synchrone attend 30 s au maximum ; utilisez wait: false pour un délai plus long. |
| 400 | confirm_required, confirm_text_mismatch | Cette commande nécessite confirm: true. / La confirmation saisie ne correspond pas. |
| 400 | method_refused | La réinitialisation d’usine, l’appairage, l’oubli du Wi-Fi et l’oubli du cloud ne sont jamais envoyés depuis le cloud. |
| 400 | payload_too_large | La requête est plus grande que ce que l’appareil accepte. |
| 403 | rpc_method_reserved | La méthode appartient à un module dédié (par exemple l’accès à distance) et n’est envoyée que par lui ; details.owner nomme le module. |
| 404 | Appareil introuvable. | |
| 409 | rpc_not_supported_by_profile | Le profil d’appareil ne parle que les topics Legacy, qui ne transportent pas de RPC. |
| 409 | device_not_active | L’appareil est désactivé ou mis en quarantaine. |
| 409 | not_cancellable | Seule une demande en file d’attente ou envoyée peut être annulée. |
| 429 | device_rate_limited | Trop de commandes adressées à cet appareil ; réessayez dans une minute. |
Le catalogue de commandes
Une commande du catalogue comporte : method, label (jusqu’à 120), description, paramsSchema (jusqu’à 50 champs, chacun avec label, type valant text, textarea, number, boolean, select, multi-select ou json, et les options required, min, max, step, options), maskedParams (champs secrets jamais réaffichés), tier (basic, privileged, critical), highImpact, oneway, long, defaultTimeoutMs, persistentDefault, confirmText (impact élevé uniquement).
{
"method": "valve.set",
"label": "Set valve position",
"tier": "privileged",
"paramsSchema": { "position": { "label": "Position (%)", "type": "number", "min": 0, "max": 100, "required": true } }
}
GET ...?format=export renvoie { "commands": [...] } sans identifiants, que PUT accepte en retour pour copier un catalogue vers un autre profil.

