Estas rutas gestionan dispositivos y perfiles de dispositivo desde código. Las rutas cuelgan de https://<your host>/tenant/<tenantId>. Las convenciones (paginación, cuerpos estrictos, horas) están en REST API basics.
Dispositivos
| Método | Ruta | Permiso | Función |
|---|---|---|---|
| GET | /api/iot/devices | iot_device.devices.read | Listar dispositivos |
| POST | /api/iot/devices | iot_device.devices.create | Crear un dispositivo y su token de acceso |
| GET | /api/iot/devices/{deviceId} | iot_device.devices.read | Leer un dispositivo |
| PATCH | /api/iot/devices/{deviceId} | iot_device.devices.update | Cambiar nombre, etiqueta, tipo, perfil, estado, información adicional |
| DELETE | /api/iot/devices/{deviceId} | iot_device.devices.delete | Eliminar un dispositivo |
| GET | /api/iot/devices/{deviceId}/connection | iot_device.devices.read | Dónde se conecta el dispositivo |
| GET | /api/iot/devices/{deviceId}/connections | iot_device.devices.read | El registro de conexiones |
| GET | /api/iot/devices/{deviceId}/log | iot_device.devices.read | Registro de conexiones y filas de auditoría, combinados |
Listar dispositivos
GET /api/iot/devices (las páginas empiezan en 1)
| Parámetro | Valores |
|---|---|
page | desde 1 (por defecto 1) |
pageSize | de 1 a 100 (por defecto 20) |
search | hasta 128 caracteres: nombre, etiqueta o número de serie |
deviceProfileId | un id de perfil |
status | ACTIVE, QUARANTINED o DISABLED |
isGateway | true o false |
online | true, false o unknown |
sort | name, connection, lastSeen, createdAt, con :asc o :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 es null en un perfil cuya regla de online es Last seen only. GET /api/iot/devices/{deviceId} responde { "device": {...} } con los mismos campos más profileName y profileTransport.
Crear un dispositivo
POST/tenant/{tenantId}/api/iot/devices
Campos: name (obligatorio, de 1 a 128 caracteres, único), label (hasta 255), type (hasta 64), deviceProfileId (si no, el perfil por defecto), isGateway, additionalInfo (un objeto de hasta 50 claves y 8 KiB). La respuesta contiene el dispositivo y, para los perfiles cuyos dispositivos se autentican por sí mismos, un token de acceso en credential.secret.token. El token solo se muestra en esta respuesta.
Cuerpo de la petición application/json
{
"name": "pump-station-04",
"label": "Pump station 4",
"deviceProfileId": "a1b2…"
}
Ejemplo 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…"}'
Respuestas
201Creado
{
"device": {
"deviceId": "6f1c…",
"name": "pump-station-04",
"status": "ACTIVE",
"…": "…"
},
"credential": {
"credential": {
"credentialId": "…",
"type": "ACCESS_TOKEN",
"…": "…"
},
"secret": {
"token": "Xq3…"
}
}
}
403Prohibido
{
"message": "The device limit of this account is reached.",
"code": "FORBIDDEN",
"details": {
"key": "max_devices",
"limit": 43,
"current": 43,
"adding": 1
}
}
409Conflicto
{
"message": "A device with this name already exists.",
"code": "CONFLICT",
"details": {
"reason": "name_taken"
}
}
Cambiar un dispositivo
PATCH /api/iot/devices/{deviceId} acepta cualquiera de name, label, type, deviceProfileId, isGateway, additionalInfo, status (ACTIVE o DISABLED). Envíe solo lo que cambia. Un dispositivo en cuarentena se aprueba mediante el aprovisionamiento, no aquí: «A quarantined device is activated through provisioning, not edited here.» Un nuevo nombre, un nuevo perfil o la desactivación finalizan la sesión activa del dispositivo; se reconecta con la nueva configuración.
Eliminar un dispositivo
DELETE responde { "message": "Device deleted." }. Las lecturas del dispositivo se eliminan en segundo plano. Un dispositivo que sigue en uso se rechaza con 409, por ejemplo: «The device is still in use by 1 asset placement or relation (iot_asset).» Quítelo de su activo y elimine antes sus relaciones: véase Placing devices y Relations.
Detalles de conexión
GET /api/iot/devices/{deviceId}/connection no devuelve nada secreto:
{
"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 es null cuando el perfil no admite MQTT en ese listener; rpcRequest y rpcResponse son null en el esquema Legacy compatible.
Registros
GET .../connections?page=1&pageSize=50: conexiones y desconexiones, de más reciente a más antiguo (las páginas empiezan en 1,pageSizehasta 100).GET .../log?page=1&pageSize=50&source=connection|audit: el registro de conexiones y las filas de auditoría del dispositivo combinados, de más reciente a más antiguo.page×pageSizepuede ser como máximo 1,000.
Credenciales del dispositivo
| Método | Ruta | Función |
|---|---|---|
| GET | /api/iot/devices/{deviceId}/credentials | La credencial activa, sin su secreto |
| PUT | /api/iot/devices/{deviceId}/credentials | Cambiar a otra credencial |
| POST | /api/iot/devices/{deviceId}/credentials/rotate | Un nuevo secreto del mismo tipo (201) |
| DELETE | /api/iot/devices/{deviceId}/credentials | Revocar la credencial |
Todas requieren iot_device.credentials.manage. Cambiar, rotar y revocar finalizan la credencial anterior de inmediato (no hay periodo de gracia) y desconectan el dispositivo.
Cuerpos de PUT:
{ "type": "ACCESS_TOKEN" }
{ "type": "MQTT_BASIC", "username": "pump04", "clientId": "pump04", "password": null }
{ "type": "X509_CERTIFICATE", "certificateId": "…" }
Para MQTT_BASIC, omita password (o use null) para obtener una generada. Una contraseña elegida necesita al menos 16 caracteres, variada, sin contener el nombre de usuario ni el client id. El nombre de usuario provision está reservado. La respuesta es { "credential": {...}, "secret": { "token": "…" } } o { ..., "secret": { "password": "…" } }, mostrada una sola vez; una credencial X.509 no tiene secreto. Una credencial X.509 se rota adjuntando un nuevo certificado: «An X.509 credential is rotated by attaching a new certificate.» Véase Device credentials.
Perfiles de dispositivo
| Método | Ruta | Permiso | Función |
|---|---|---|---|
| GET | /api/iot/device-profiles | iot_device_profile.profiles.read | Listar (page desde 1, pageSize, search, transportType, onlineRule, sort) |
| POST | /api/iot/device-profiles | iot_device_profile.profiles.write | Crear |
| GET | /api/iot/device-profiles/{profileId} | .profiles.read | Leer |
| PATCH | /api/iot/device-profiles/{profileId} | .profiles.write | Cambiar; envíe expectedVersion |
| DELETE | /api/iot/device-profiles/{profileId} | .profiles.write | Eliminar |
| POST | /api/iot/device-profiles/{profileId}/default | .profiles.write | Hacerlo el perfil por defecto |
| POST | /api/iot/device-profiles/{profileId}/duplicate | .profiles.write | Copiarlo como « |
| GET | /api/iot/device-profiles/{profileId}/key-usage?keys=a,b | .profiles.read | Quién lee estas claves (reglas, placas, flujos) |
| GET, PUT, POST | /api/iot/device-profiles/{profileId}/provisioning | iot_device_profile.provisioning.manage | Ajustes de aprovisionamiento; POST rota o revoca una clave de aprovisionamiento |
Campos de ordenación: name, transport, online, updated. Una lista responde { "profiles": [...], "total": n }; un perfil { "profile": {...} }.
Crear un perfil
Un perfil nuevo necesita un name (hasta 128 caracteres) y un onlineRule: LIVE_SOCKET (Live connection), UPLINK_WINDOW (Recent message, también necesita expectedIntervalS, de 60 a 604,800) o LAST_SEEN_ONLY (Last seen only). Cada otro campo toma su valor por defecto.
{
"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 }
]
}
Campos seleccionados y sus rangos:
| Campo | Rango |
|---|---|
transportType | MQTT, HTTP, ANY, o un protocolo registrado |
allowedAuthModes | De 1 a 3 de ACCESS_TOKEN, MQTT_BASIC, X509_CERTIFICATE |
mqttTransport | TLS_REQUIRED (predeterminado) o PLAINTEXT_ALLOWED |
allowedNetworks | hasta 64 redes o direcciones |
payloadMaxBytes | 1.024 a 65.536 |
downlinkMaxBytes | 1.024 a 65.536 |
inactivityTimeoutSec | 10 a 604.800 |
maxGatewayChildren | 0 a 10.000 |
rpcTimeoutDefaultSec | 1 a 300 |
onlineWindowFactor | 1,5 a 10, un decimal |
telemetrySchema, cfgSchema | hasta 500 claves cada uno |
rateLimits | count:seconds[,count:seconds] por familia, por ejemplo "telemetry_msgs": "10:1,300:60" |
Lea primero un perfil existente para ver todos los campos con su valor actual; el editor de perfiles cubre los mismos campos con explicaciones, consulte Perfiles de dispositivo.
Rechazos
Un cuerpo que incumple una regla de campo devuelve 400 con los problemas en issues, cada uno con su path y message, por ejemplo online_rule_required (un perfil nuevo necesita una regla de conexión), expected_interval_required (Mensaje reciente necesita expectedIntervalS) o online_rule_not_allowed (la regla de conexión no encaja con el transporte).
Otros rechazos incluyen details.reason, por ejemplo:
| Motivo | Significado |
|---|---|
stale_profile | Alguien guardó el perfil después de que usted lo leyera (409) |
name_taken | Otro perfil tiene este nombre |
default_profile | El perfil predeterminado no se puede eliminar |
builtin_profile | Un perfil integrado no se puede modificar de esta forma |
still_referenced | Dispositivos u otros registros todavía usan el perfil |
reference_unverifiable | No se pudo consultar un módulo que podría usar el perfil; inténtelo de nuevo |

