Un comando (un RPC) pide a un dispositivo que haga algo y, salvo que sea unidireccional, espera la respuesta del dispositivo. Cada comando se guarda como una solicitud con un estado, para que puedas enviarlo ahora y leer el resultado más tarde. Cómo funcionan los comandos para las personas en la aplicación se explica en Envío de comandos a dispositivos. Las rutas están bajo https://<your host>/tenant/<tenantId>.
En desarrollo: Los comandos a dispositivos detrás de una pasarela (los dispositivos hijos de una pasarela) están en desarrollo. Envía comandos a dispositivos que se conectan por sí mismos.
Rutas
| Método | Ruta | Permiso | Función |
|---|---|---|---|
| POST | /api/iot/devices/{deviceId}/rpc | iot_rpc.read más el nivel del comando | Enviar un comando |
| GET | /api/iot/devices/{deviceId}/rpc | iot_rpc.read | El historial de comandos del dispositivo |
| GET | /api/iot/rpc/{rpcId} | iot_rpc.read | Un comando con su respuesta |
| POST | /api/iot/rpc/{rpcId}/cancel | iot_rpc.read más el nivel del comando | Cancelar un comando en cola o enviado |
| GET | /api/iot/devices/{deviceId}/rpc-commands | iot_rpc.send.basic | Los comandos que un control del panel puede enviar a este dispositivo |
| GET | /api/iot/rpc-commands/{profileId} | iot_rpc.read | El catálogo de comandos de un perfil (?format=export para el formato de importación) |
| POST | /api/iot/rpc-commands/{profileId} | iot_rpc.commands.manage | Añadir un comando al catálogo |
| PUT | /api/iot/rpc-commands/{profileId} | iot_rpc.commands.manage | Reemplazar todo el catálogo |
| PATCH, DELETE | /api/iot/rpc-commands/{profileId}/{commandId} | iot_rpc.commands.manage | Cambiar o eliminar un comando |
Quién puede enviar qué
Cada comando del catálogo de un perfil tiene un nivel. El nivel determina el permiso:
| Nivel | Permiso | Rol predeterminado | ¿Puede tenerlo una clave de API? |
|---|---|---|---|
| basic | iot_rpc.send.basic | Usuario | Sí |
| privileged | iot_rpc.send.privileged | Administrador | Sí |
| critical | iot_rpc.send.critical | Administrador | Sí |
| raw (un método que no está en el catálogo) | iot_rpc.send.raw | Propietario | No |
Así, una clave de API solo puede enviar métodos que estén en el catálogo del perfil del dispositivo.
Enviar un comando
POST/tenant/{tenantId}/api/iot/devices/{deviceId}/rpc
Campos: method (obligatorio, namespace.verb, hasta 64 caracteres), params (cualquier JSON, como máximo 16 KiB), oneway, persistent, timeoutMs (de 1.000 a 600.000; por defecto el del catálogo o el del perfil, si no 10.000), expiresInS (de 1 a 604.800, para un comando persistente; por defecto 86.400), wait, confirm, confirmText, source (api, console o dashboard_control). Un comando bidireccional, no persistente, con un tiempo de espera de como máximo 30 segundos espera a su final y responde 200; cualquier otro caso responde 202 y continúa en segundo plano.
Cuerpo de la solicitud application/json
{
"method": "pump.start",
"params": {
"speed": 80
},
"timeoutMs": 15000
}
Ejemplo 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}'
Respuestas
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",
"…": "…"
}
}
202Accepted
{
"rpc": {
"rpcId": "c0de…",
"status": "QUEUED",
"…": "…"
}
}
403Forbidden
{
"message": "You are not permitted to perform this action",
"code": "FORBIDDEN"
}
409Conflict
{
"message": "The device is disabled or quarantined; it takes no commands.",
"code": "CONFLICT",
"details": {
"reason": "device_not_active"
}
}
429Too Many Requests
{
"message": "Too many commands to this device; try again in a minute.",
"code": "RATE_LIMIT_EXCEEDED",
"details": {
"reason": "device_rate_limited"
}
}
- Unidireccional (
oneway: true): el comando se completa correctamente cuando se entrega; no se espera respuesta. - Persistente (
persistent: true): si el dispositivo está desconectado, el comando espera a que se conecte durante un máximo deexpiresInSy después termina enEXPIRED. - wait:
truefuerza la espera (se rechaza con «A synchronous call waits at most 30 s; use wait: false for a longer timeout.» cuando el comando no puede terminar en 30 s);falseresponde siempre202de inmediato. - Comandos de alto impacto necesitan
"confirm": truey la frase escrita enconfirmTextcuando el comando del catálogo la tiene. Sin ello: «This command needs confirm: true.» Se registran en el registro de auditoría al enviarse y al terminar, y se envían como máximo una vez.
No hay clave de idempotencia en esta ruta: cada POST crea un nuevo comando. Si tu llamada agota el tiempo de espera en la red, consulta el historial del dispositivo antes de volver a enviar.
Estados
QUEUED → SENT → DELIVERED → SUCCESSFUL, o uno de FAILED, TIMEOUT, EXPIRED, CANCELLED.
| Estado | Significado |
|---|---|
SUCCESSFUL | Llegó la respuesta, o un comando unidireccional fue entregado |
FAILED | El dispositivo lo rechazó, o no pudo enviarse |
TIMEOUT | No hubo respuesta a tiempo. El resultado es desconocido: el dispositivo pudo haberlo ejecutado. outcome es unknown. |
EXPIRED | Nunca llegó al dispositivo antes de su caducidad |
CANCELLED | Cancelado mientras estaba en cola o enviado |
outcome añade detalle: running (el dispositivo aceptó un comando de larga duración), unknown (un tiempo de espera agotado) o late (una respuesta llegó después del final). Los resultados se conservan 90 días por defecto.
Leer resultados
Consulta GET /api/iot/rpc/{rpcId} hasta que el estado sea final, o suscríbete a los eventos de webhook rpc.completed y rpc.failed y lee la respuesta cuando llegue. Consulta la Referencia de eventos de webhook.
Historial de un dispositivo, del más reciente al más antiguo:
GET /api/iot/devices/{deviceId}/rpc?status=FAILED&limit=50
Sin page, el historial se lee con un cursor: status, method, from, to (fechas), limit (de 1 a 100, por defecto 25) y cursor. La respuesta es { "requests": [...], "nextCursor": "…" }; pasa nextCursor de vuelta como cursor hasta que sea null. Con page (desde 0), la lista sigue las reglas habituales de listas: pageSize, search, sort (method, status, source, createdAt, completedAt), status y source como listas separadas por comas, createdFrom, createdTo; la respuesta es { "requests": [...], "total": n }. Los parámetros secretos se enmascaran en todas las respuestas.
Cancelar
POST /api/iot/rpc/{rpcId}/cancel pasa un comando QUEUED o SENT a CANCELLED y responde { "rpc": {...} }. Otros estados: 409 «Only a queued or sent request can be cancelled.» Cancelar necesita el mismo permiso de nivel que enviar. Una cancelación no envía webhook.
Límites
| Límite | Predeterminado |
|---|---|
| Comandos por dispositivo | 10 por minuto (429, Retry-After: 60) |
| Comandos por organización | 600 por minuto (429 «Too many requests for your plan. Please upgrade or try again later.») |
| Parámetros | 16 KiB de JSON |
| Respuesta conservada | 64 KiB |
| Comandos por catálogo de perfil | 200 |
Rechazos
| Estado | details.reason | Mensaje |
|---|---|---|
| 400 | bad_params | Los parámetros no coinciden con el comando. |
| 400 | wait_too_long | Una llamada síncrona espera como máximo 30 s; usa wait: false para un tiempo de espera mayor. |
| 400 | confirm_required, confirm_text_mismatch | This command needs confirm: true. / The typed confirmation does not match. |
| 400 | method_refused | El restablecimiento de fábrica, el emparejamiento, el olvido de Wi-Fi y el olvido de la nube nunca se envían desde la nube. |
| 400 | payload_too_large | La solicitud es mayor de lo que acepta el dispositivo. |
| 403 | rpc_method_reserved | El método pertenece a un módulo propio (por ejemplo, el acceso remoto) y solo lo envía ese módulo; details.owner indica el módulo. |
| 404 | Dispositivo no encontrado. | |
| 409 | rpc_not_supported_by_profile | El perfil del dispositivo solo usa los temas Legacy, que no transportan RPC. |
| 409 | device_not_active | El dispositivo está deshabilitado o en cuarentena. |
| 409 | not_cancellable | Solo se puede cancelar una solicitud que esté en cola o enviada. |
| 429 | device_rate_limited | Demasiados comandos para este dispositivo; inténtalo de nuevo en un minuto. |
El catálogo de comandos
Un comando del catálogo tiene: method, label (hasta 120), description, paramsSchema (hasta 50 campos, cada uno con label, type de text, textarea, number, boolean, select, multi-select o json, y opcionalmente required, min, max, step, options), maskedParams (campos secretos que nunca se muestran de nuevo), tier (basic, privileged, critical), highImpact, oneway, long, defaultTimeoutMs, persistentDefault, confirmText (solo para alto impacto).
{
"method": "valve.set",
"label": "Set valve position",
"tier": "privileged",
"paramsSchema": { "position": { "label": "Position (%)", "type": "number", "min": 0, "max": 100, "required": true } }
}
GET ...?format=export devuelve { "commands": [...] } sin ids, que PUT acepta de nuevo para copiar un catálogo a otro perfil.

