Esta página cubre dos familias de rutas: actualizaciones de firmware (OTA) y ajustes remotos de dispositivos. Cómo funcionan ambas para las personas se explica en Actualizaciones de firmware (OTA), Oleadas de despliegue de firmware y Ajustes remotos de dispositivos. Las rutas están bajo https://<your host>/tenant/<tenantId>.
Rutas de firmware
| Método | Ruta | Permiso | Función |
|---|---|---|---|
| GET | /api/iot/ota/packages | iot_ota.read | Listar paquetes (status, title) |
| POST | /api/iot/ota/packages | iot_ota.packages.manage | Subir un paquete (multipart) |
| GET | /api/iot/ota/packages/{packageId} | iot_ota.read | Un paquete con sus recuentos de actualizaciones |
| PATCH | /api/iot/ota/packages/{packageId} | iot_ota.packages.manage | Notas de la versión, o estado DEPRECATED, READY de nuevo, REVOKED |
| DELETE | /api/iot/ota/packages/{packageId} | iot_ota.packages.manage | Eliminar un paquete no usado |
| GET, POST | /api/iot/ota/assignments | lectura: iot_ota.read; asignar: iot_ota.assign | Listar o crear asignaciones |
| DELETE | /api/iot/ota/assignments/{assignmentId} | iot_ota.assign | Eliminar una asignación |
| GET | /api/iot/ota/rollouts | iot_ota.read | Despliegues por oleadas (packageId, status) |
| GET, POST | /api/iot/ota/rollouts/{rolloutId} | lectura; iot_ota.assign | Lectura; { "action": "pause" \| "resume" \| "abort" } |
| GET | /api/iot/ota/states | iot_ota.read | Intentos de actualización (packageId, state, limit hasta 500) |
| GET, POST | /api/iot/ota/devices/{deviceId} | lectura; iot_ota.assign | La actualización de un dispositivo; { "action": "retry" } |
| GET, PUT | /api/iot/ota/settings/{profile\|device}/{id} | lectura; iot_ota.assign | Ajustes OTA de un perfil, o la anulación de enlace medido de un dispositivo |
| GET, POST | /api/iot/ota/signing-keys | lectura; iot_ota.keys.manage | Listar o añadir claves públicas de firma |
| DELETE | /api/iot/ota/signing-keys/{keyId} | iot_ota.keys.manage | Revocar una clave |
Añadir y revocar claves de firma es un permiso de Propietario, así que una clave de API no puede hacerlo.
Subir un paquete
POST /api/iot/ota/packages acepta multipart/form-data con dos partes: file (la imagen) y meta (texto JSON). El checksum se comprueba antes de guardar nada, y la firma se verifica con tus claves registradas.
curl -s -X POST "https://$HOST/tenant/$TENANT/api/iot/ota/packages" \
-H "Authorization: Bearer $HERMETRY_KEY" \
-F "file=@router-fw-2.5.0.bin" \
-F 'meta={"title":"router-fw","version":"2.5.0","checksum":"<sha256 of the file>","signatureScheme":"usign-ed25519","signature":"<text of the .sig file>","profileId":"a1b2…","releaseNotes":"Fixes LTE reconnect."}'
Campo meta | Reglas |
|---|---|
title | De 1 a 128; letras, dígitos, ., _, +, - |
version | De 1 a 64; letras, dígitos, ., _, +, ~, - |
checksum | SHA-256 del archivo, 64 caracteres hexadecimales |
signatureScheme | usign-ed25519 (una firma separada en signature), el esquema de arranque seguro de la familia de dispositivos para la que es el paquete, o none (solo donde el perfil permita imágenes sin firmar) |
type | FIRMWARE o SOFTWARE |
profileId, hardwareModels (hasta 50), minFromVersion | A qué dispositivos se ajusta |
releaseNotes | hasta 4.000 caracteres |
El tamaño máximo predeterminado es 64 MiB. La respuesta es 201 { "package": {...} }.
| Rechazo | Mensaje |
|---|---|
400 file_missing | Adjunta el archivo de firmware. |
413 too_large | El paquete es mayor de lo permitido. |
413 quota | No queda suficiente almacenamiento para este paquete. |
422 checksum_mismatch | El SHA-256 del archivo subido no coincide con el indicado. |
422 | La firma de la imagen no se verifica con ninguna clave registrada. |
409 package_exists | Ya existe un paquete con este título y versión. |
Asignar y desplegar
POST/tenant/{tenantId}/api/iot/ota/assignments
Asigna un paquete READY a un PROFILE o a un DEVICE (en targetId) o a un GROUP (deviceIds, hasta 5.000, o selector.profileId, ambos fijados ahora). dryRun: true devuelve los recuentos y no escribe nada. waves (de 1 a 20) despliega un perfil o grupo por oleadas; un dispositivo nunca se despliega por oleadas.
Cuerpo de la solicitud application/json
{
"packageId": "f00d…",
"targetType": "PROFILE",
"targetId": "a1b2…",
"waves": [
{
"size": {
"percent": 5
},
"soakMin": 60,
"minSuccessPct": 95,
"maxFailurePct": 2,
"maxDurationMin": 1440
},
{
"size": {
"percent": 100
},
"soakMin": 0,
"minSuccessPct": 95,
"maxFailurePct": 2,
"maxDurationMin": 2880
}
]
}
Ejemplo curl
curl -X POST 'https://<your-host>/tenant/{tenantId}/api/iot/ota/assignments' \
-H 'Content-Type: application/json' \
-d '{"packageId":"f00d…","targetType":"PROFILE","targetId":"a1b2…","waves":[{"size":{"percent":5},"soakMin":60,"minSuccessPct":95,"maxFailurePct":2,"maxDurationMin":1440},{"size":{"percent":100},"soakMin":0,"minSuccessPct":95,"maxFailurePct":2,"maxDurationMin":2880}]}'
Respuestas
201Creado
{
"assignment": {
"…": "…"
},
"preview": {
"…": "…"
},
"result": {
"…": "…"
}
}
200OK
{
"assignment": null,
"preview": {
"…": "…"
}
}
409Conflicto
{
"message": "Only a ready package can be assigned.",
"code": "CONFLICT"
}
El size de una oleada es { "percent": 0–100 } o { "count": 1–100000 }; la última oleada toma el resto. soakMin de 0 a 10.080, minSuccessPct y maxFailurePct de 0 a 100, maxFailures opcional, maxDurationMin de 5 a 43.200.
Estados de despliegue: RUNNING, PAUSED, COMPLETED, ABORTED. Una comprobación fallida pausa el despliegue; resume lo continúa, abort lo termina.
Estados de actualización
La actualización de un dispositivo pasa por QUEUED, INITIATED, WAITING, DOWNLOADING, DOWNLOADED, VERIFIED, UPDATING y termina en UPDATED, FAILED, TIMEOUT, ROLLED_BACK, SKIPPED o CANCELLED. Solo FAILED, TIMEOUT y ROLLED_BACK pueden reintentarse ({ "action": "retry" }), hasta el límite de intentos.
Ajustes OTA
PUT /api/iot/ota/settings/profile/{profileId} acepta otaEnabled, allowMetered, allowUnsigned, downloadTimeoutMin (de 5 a 1.440), updateTimeoutMin (de 2 a 240), maxAttempts (de 1 a 10); null vuelve al valor predeterminado. Para un dispositivo (/settings/device/{deviceId}) solo se acepta allowMetered: «Un dispositivo solo acepta el ajuste de enlace medido.»
Eventos de webhook: ota.update.* y ota.rollout.*, consulta la Referencia de eventos de webhook.
Rutas de ajustes de dispositivos
Los ajustes remotos son para dispositivos con licencia cuyo modelo publica un manifiesto de ajustes.
| Método | Ruta | Permiso | Función |
|---|---|---|---|
| GET | /api/iot/devices/{deviceId}/settings | iot_device_settings.read | Todas las secciones: valores deseados e informados, estado, desviación |
| GET | /api/iot/devices/{deviceId}/settings/schema | .read | El manifiesto del modelo y firmware del dispositivo |
| GET | /api/iot/devices/{deviceId}/settings/history | .read | Cambios anteriores (section, page desde 0, sort=at:desc) |
| GET | /api/iot/devices/{deviceId}/settings/{section} | .read | Una sección; ETag es su revisión |
| PUT | /api/iot/devices/{deviceId}/settings/{section} | .operate y las clases modificadas | Reemplaza una sección: { values, confirm? } |
| PATCH | /api/iot/devices/{deviceId}/settings/{section} | .operate y las clases modificadas | Parche de fusión: { patch, confirm? } |
| POST | /api/iot/devices/{deviceId}/settings/{section}/reapply | .operate | Vuelve a enviar la sección deseada |
| GET | /api/iot/device-settings/models, /model-schema?model= | .read | Tus modelos y las secciones de un modelo |
| GET, POST | /api/iot/device-settings/baselines | .read; .baselines.manage | Listar o crear líneas base |
| GET, PUT, DELETE | /api/iot/device-settings/baselines/{baselineId} | .read; .baselines.manage | Leer, reemplazar, eliminar |
| GET, POST | /api/iot/device-settings/jobs | .read; .jobs.manage | Listar o iniciar trabajos masivos |
| GET | /api/iot/device-settings/jobs/{jobId}, .../targets | .read | Un trabajo, sus oleadas y sus dispositivos |
| POST | /api/iot/device-settings/jobs/{jobId}/pause, /resume, /cancel | .jobs.manage | Dirigir un trabajo |
Lo que una escritura puede cambiar depende de la clase de cada ajuste modificado: los ajustes de operación requieren iot_device_settings.operate, los ajustes de configuración iot_device_settings.write, los ajustes peligrosos iot_device_settings.dangerous.write (solo el Propietario, nunca una clave de API) y "confirm": true.
Cambiar una sección
GET /api/iot/devices/{deviceId}/settings/network → ETag: "7"
PATCH /api/iot/devices/{deviceId}/settings/network
If-Match: "7"
{ "patch": { "lte": { "apn": "iot.example" } } }
- Se requiere
If-Match: sin él,428«If-Match is required: send the rev you read.» - Una sección cambiada desde que la leyó:
412«The section changed since it was read; read it again.» (rev_mismatch). ?dryRun=truevalida y lista lo que cambiaría, y no guarda nada.- Un merge patch fusiona objetos, elimina con
nully reemplaza las listas completas. - Las escrituras están limitadas a 600 por organización y por minuto.
- Los secretos nunca se devuelven: un ajuste secreto se lee como
{ "set": true }.
Un dispositivo que no acepta ajustes por sección responde 409 «This device does not take settings by section.»; uno cuyo manifiesto no está aún en el catálogo, «The settings manifest of this device’s model and firmware is not in the catalog yet.»
Trabajos masivos
POST /api/iot/device-settings/jobs con la cabecera Idempotency-Key: name (hasta 80), baselineId o sections, selector (deviceIds, profileId o models), waves opcional ({ "percent": [1, 10, 100] } o { "count": [...] }, hasta 10), gates (minApplied, maxReverted como fracciones de 0 a 1), onConflict (queue o skip), offlineExpiryDays (de 1 a 90), waveTimeoutMin (de 1 a 1,440), confirm. Una Idempotency-Key repetida responde con el primer trabajo (200). ?dryRun=true responde con el plan. Rechazos: 409 too_many_jobs; 422 confirm_required, invalid_sections, no_devices, too_many_devices; 403 class_forbidden.

