user-domain-model: fetchList method on ILockedDomainModel — fetches many locked domains in a single round trip (Mongo: one $in query on the domain_lockKey index; Redis: batched MGET)
platform-consumer: The lockedDomain freshness check at the end of each event batch (filterNewerUpdates) now reads the current timestamps of all lockKeys in one bulk fetchList query instead of one serial findOne per lockKey inside the batch transaction — removes up to batch-size database round trips per batch under per-device event floods
commands: account emails (CreateAccount, CreateSimpleAccount, AddAccountToCompany, AddAccountToOrganization) are normalized to lowercase at schema level — applies to producer factories and command-handler input validation, so case-variant invites resolve to the same account instead of creating duplicates
command-handler: AddAccountToCompany and AddAccountToOrganization now reject an email that already belongs to a member of the target company/organization (self-invite included) instead of silently overwriting the member's role — re-inviting an owner as guest no longer downgrades and locks out the owner; role changes must use the privilege-update commands
api: POST /v1/company/{companyUid}/member and POST /v1/organization/{organizationUid}/member now return 409 Conflict (COMPANY_MEMBER_ALREADY_EXISTS / ORGANIZATION_MEMBER_ALREADY_EXISTS) when the invited email already belongs to a member, matching case-insensitively — previously the invite silently overwrote the member's role
command-handler/commands: the last-owner protection ("Company must have at least one owner") is now a typed command rejection (Account.Privilege.CompanyMustHaveOwner) that survives the command RPC — demoting or removing a company's only owner returns a clear 400 with that message from the API instead of a generic 500
api: member invite endpoints (POST /v1/company/{companyUid}/member, POST /v1/organization/{organizationUid}/member) no longer wait for read-model synchronization — on environments with lagging consumers the sync wait timed out and returned 500 for invites that had already succeeded; the invite is confirmed once the command-handler accepts it
api: observability.lastSuccessfulExportAt on Event Stream exporter responses is no longer documented as required — a freshly created exporter has never delivered anything, so the field may be null or absent
types: incorrectTime, timestamp, and timezoneOffset fields on the DATETIME monitoring log data type (ntpServer becomes optional)
platform-consumer: Compute incorrectTime (timezone mismatch against the platform-configured timezone, or clock deviation over ±10 minutes) on every device current time report and persist DATETIME monitoring logs into telemetry history, the latest snapshot, and the Redis telemetry cache serving the aggregate latest-telemetry endpoints
api: incorrectTime in the device response currentTime object and in DATETIME telemetry responses (/v1/device/:uid/telemetry/latest, bulk /v1/device/telemetry/latest, and /v1/device/:uid/telemetry/DATETIME/latest)
api: incorrectTime boolean filter on GET /v2/device and GET /v2/device/count (devices that never reported time match neither filter value)
api: POST /v1/device/{deviceUid}/custom-script/latest returns the latest execution for each requested Custom Script in one request
user-domain-model: Add an indexed grouped latest-action-log query
platform-consumer: An error while processing a single cache-invalidation event no longer crashes the whole consumer process — the failed event is dead-lettered for delayed retry individually while the remaining events keep processing; cache invalidation now waits for DB synchronization before resolving the cache key (closing a race with the projection for freshly created devices); a DeviceOrganizationUpdated event for a device missing from the read model (deleted) is treated as a no-op instead of an infinitely retried error; unhandled promise rejections are logged instead of terminating the process; AMQP consumers now start only after service.start() so the process-level error handlers are registered before the first message can arrive
user-domain-model: The per-device bulk operation progress writes (updateSuccessfulDevice, updateFailedDevice, updateSkippedDevice and the increment branch of updateInProgressDevice) now append with $push instead of $addToSet. The stored result is identical thanks to the existing { $ne } filter guards, and the full-array rewrite that capped bulk operations at ~10 devices/s regardless of the configured concurrency is gone.
api: Document Device Custom Script execution pagination and accept canonical result pipeline values without removing legacy values
user-domain-model: The unused deviceTemperature model (createDeviceTemperatureModel, prepareDeviceTemperatureTable, the IDeviceTemperature* schema interfaces and the DeviceCollection.Temperature entry). Nothing ever read from or wrote to the deviceTemperature collection — device temperatures are recorded and served through the device monitoring log (DeviceTelemetryType.TEMPERATURE). The existing collection is left untouched in MongoDB and expires on its own TTL index.
api: The deviceTemperatureModel instance in basicModels, which was constructed but never used. GET /v1/device/:deviceUid/temperature is unaffected — it already reads from the device monitoring log.
platform-consumer: The deviceTemperatureModel instance in mongoModels and its redisModels/models entries, which were constructed but never used.
types: computeDefaultedFeatureFlags helper and ENTITLEMENT_DEFAULTED_DEVICE_FEATURE_FLAGS map in Device/FeatureFlag — defaults unset cpuUsage/memoryUsage device feature flags to enabled when the organization holds the cpuTelemetry/ramTelemetry feature entitlements; explicitly stored values are never overridden
API: The per-device plugin, runner and screenshot write endpoints (POST/PUT/DELETE /v1/device/:deviceUid/plugin, .../runner, and POST /v1/device/:deviceUid/screenshot) now verify the Plugins/Runners/Screenshots entitlement against the organization that owns the target device (via checkPermission's fent), regardless of the caller identity. This closes the gap where a multi-organization account entitled in one organization could act on a device owned by a non-entitled organization. The matching read endpoints keep the caller-organization gate.
API: The organization entitlement cache is kept hot so the caller-organization gate avoids cold read-throughs: it is warmed on startup (after a jittered delay) and re-warmed on a jittered ~4h schedule (all organizations streamed via one fetchList, in the background/non-blocking), and an entitlement change reloads the affected organization's entry instead of only evicting it. It has no TTL — freshness comes from that active invalidation-reload plus the periodic re-warm, not from expiry. Best-effort: the batched read-through remains the fallback, so warmup/reload failures degrade to lazy fetches rather than errors.
user-domain-model: model-readable caches implement CacheWithWarmup — a warmup() that streams the full set (one fetchList) into the cache — and accept ttl: null to never expire; EventsInvalidatedCache gains an opt-in reloadOnInvalidate that re-fetches an invalidated key (keeping it hot) instead of only evicting it.
API: The caller-organization feature-entitlement gate no longer fans out into one entitlement lookup per organization for account tokens — EntitlementProvider.canAny resolves all of the account's organizations in a single batched read, removing an N+1 that raised latency on device endpoints for accounts spanning many organizations.
user-domain-model: Cache.getMany now resolves read-through misses with a single batched $in query (one fetchList) on model-readable caches — for any key, not just uid — instead of one fetchOne per missing key.
api: GET /v1/organization/:organizationUid/device-plan-history endpoint returning an organization's device plan change history (newest record first, human-readable originator), restricted to owner and master roles via the new read_device_plan_history ACL action
command-handler: Record every device plan assignment into the append-only devicePlanHistory on organization create and update ({ plan, originator, date } where plan is a snapshot of the whole assigned device plan as the source of truth for auditing; recorded even when the same plan is set again), starting with the initial plan assigned at organization creation
platform-consumer: Project devicePlanHistory into the organization read model
user-domain-model: Add append-only devicePlanHistory to the organization model
types: Add device plan change history type to Company
api: POST /v1/applet/{appletUid}/version and PUT /v1/applet/{appletUid}/version/{appletVersion} now accept frontAppletVersion: null (bundled front applet) in the JSON body instead of rejecting it with 400 INVALID_BODY_PROPERTIES — the write path already handled null (bundledFrontApplet), only the request schema was too strict
command-handler: Allow creating applicationVersion without frontDisplayVersion
platform-consumer: Allow creating applicationVersion without frontDisplayVersion
api: GET /v1/device/telemetry/latest/count now supports filtering devices by their latest online status using type=ONLINE_STATUS together with the online query parameter
api: PUT /v2/device/{deviceUid}/firmware — device firmware upgrade addressed by the firmware version UID, unambiguous even when the same manufacturer version exists for several firmware types or OS versions; gated by the FirmwareUpgrades entitlement like V1; the V1 version-addressed endpoint is deprecated but stays operational
api: GET /v2/firmware (list, count, by-uid) now returns firmwareType, brand, osVersion, and a computed prettifiedName (webOS manufacturer versions displayed in normalized numerical form, raw values preserved), and supports firmwareType[Prefix|Suffix], brand, and osVersion filters; search now matches firmwareType and brand instead of the deprecated deviceTypes
command-handler: LG-only firmware/device compatibility validation for UID-addressed upgrades — the firmware type and OS version must equal what the device reported (fails closed when unreported) and the normalized manufacturer version must differ from the one already installed; rejected with the Firmware.IncompatibleWithDevice error; non-LG behavior preserved
commands/events: Firmware.UpdateFirmwareVersionMetadata command and Firmware.FirmwareVersionMetadataUpdated event replace the device-types update (Firmware.UpdateFirmwareVersionDeviceTypes removed; the old event stays replayable); FirmwareVersionCreated revision 3 carries scalar firmwareType plus optional brand/osVersion with an upcaster from revision 2
common-types: normalizeNumericalVersion/numericalVersionsEqual helpers for numerical manufacturer/OS version comparison (03.30.16 ≡ 3.30.16)
user-domain-model: firmware version compatibility is now the scalar firmwareType (with optional brand and osVersion, missing on legacy records) instead of the deviceTypes list, which is kept in sync as a deprecated single-item array; firmware version uniqueness moved from (applicationType, version) to the (version, firmwareType, osVersion) tuple, validated authoritatively by command-handler with normalized version semantics (6.1-UL5Q-03.15.60 == 03.15.60 == 3.15.60) — the unique partial index on the raw fields is an exact-format backstop. The event store is the single source of truth for the split: the required command-handler patch 2026-07-16_firmware_version_split_by_firmware_type splits multi-type created events (original UID stays with the first listed type, siblings get deterministic sha256-derived UIDs; records merged by the 2026-06-12 read-model consolidation are recreated under their original event UIDs) and reconciles the platform firmwareVersion collection from the patched events in the same process; the 2026-07-16_firmware_version_firmware_type migration then only backfills scalar fields, resolves duplicates, and creates the unique partial index — it aborts if any unsplit multi-type document remains
command-handler: firmware command rejections now retain their structured error type, preventing expected validation failures from returning HTTP 500
platform-consumer: Device history now records organization tag and location assignment changes
platform-consumer: Device export events (DeviceListExportRequested, DeviceListExportSucceeded, DeviceListExportFailed) are now consumed in batches; export data requests are created and updated with bulk database operations
Exclude Windows devices from auto-banning on serial number change in UpdateDeviceInfo
command-handler: UpdateDeviceInfo now completely ignores a serial number change from an already known value on Windows devices — no DeviceManufacturerDetailsUpdated is emitted for the serial number and the device is not banned (other fields still update normally)
api: POST /v1/applet/{appletUid}/version/{appletVersion}/publish, .../deprecate, and .../renew — set an Applet Version's lifecycle status; guarded so publishing requires a successfully built, not-yet-published version, deprecating requires a non-deprecated version, and renewing requires a deprecated version (409 Conflict on invalid transitions)
api: status field (draft | published | deprecated, derived from publishedSince/deprecatedSince) on the Applet Version resource
api: Content Guard and Policy/Tag operation tags were missing from the global OpenAPI tags declaration, causing documentation tooling to append them out of alphabetical order; the global tags list is now inlined in openapi/schema.yml, sorted alphabetically, and enforced by the operation-tag-defined and tags-alphabetical Redocly lint rules plus a new tags consistency test suite; removed the orphaned deviceOfflineRange.yml spec file that was no longer referenced by any path
api: POST /v1/package/:packageUid/version now allows recreating a soft-deleted package version — creation is only rejected with PACKAGE_VERSION_EXISTS_ERROR when a version with the same buildHash (or metadata version) still exists and has not been deleted
api: Applet version endpoints (/v1/applet/:appletUid/version...), device action log list (/v1/device/:deviceUid/action-log), and device applet command list/create (/v1/device/:deviceUid/applet/:appletUid/command) returned 400 INVALID_PATH_PARAMS for URLs with a trailing slash — trailing slash is now normalized before OpenAPI template matching, restoring pre-migration behavior
api: PUT /v1/organization-tag/:uid request body now accepts parentTagUid: null to detach a tag from its parent (previously a string, or omitted to leave it unchanged)
command-handler: CreateTiming now rejects with Cannot create timing for deprovisioned device when the target device does not belong to an organization (deprovisioning removes it). Closes a race where device-policy-manager re-created a policy timing milliseconds after deprovision (reacting to the deprovision's own TimingDeleted, whose originator has no policyUid), leaving an orphaned timing on a deprovisioned device
api: POST /v1/timing facade now rejects creating a timing for a device that does not belong to an organization (deprovisioned or never provisioned) with 400 DEVICE_NOT_PROVISIONED_TO_TIMING_CREATE, instead of dispatching a CreateTiming that would create an orphaned timing on an unowned device
Device remote-control (kiosk mode / IR remote-control lock, PUT/GET /v1/device/:deviceUid/remote-control) now requires OrganizationEntitlements.DeviceManagement instead of RemoteDesktop. In 44.0.0 it was gated behind the premium Remote Desktop add-on, which returned 402 (INSUFFICIENT_REMOTE_DESKTOP_ENTITLEMENT) for organizations that lock devices into kiosk mode (e.g. via sos kiosk mode) without that add-on. Kiosk locking is core device management and is now consistent with the sibling device-security endpoint.
Device policy status endpoints (list, get by item type) require OrganizationEntitlements.DevicePolicy
API: Feature Entitlement (OrganizationEntitlements.ContentGuard, OrganizationEntitlements.Tags, OrganizationEntitlements.AIContentGuard) enforcement on all Content Guard endpoints — requests from organizations without the required entitlement now receive HTTP 402
Location endpoints (GET/POST/v1/location, GET/PUT/DELETE/v1/location/{locationUid}, add-attachment, remove-attachments, archive, unarchive, restore) now require the Locations feature entitlement on the caller's organization (402 otherwise)
Location organization-tag list endpoints (GET /v1/location/organization-tags, GET /v1/location/{locationUid}/organization-tag) require the Locations entitlement; assign/unassign (PUT/DELETE /v1/location/{locationUid}/organization-tag/{tagUid}) require Locations (caller organization) plus Tags (the tagged location's organization)
Plugin endpoints — GET/POST/PUT/DELETE /v1/plugin/:pluginUid and all Version and Version-Platform sub-resources — now require the OrganizationEntitlements.Plugins organization entitlement and return 402 without it; GET /v1/plugin (list) filters results to entitled organizations (402 only when the caller has no entitled organization)
Runner endpoints — GET/POST/PUT/DELETE /v1/runner/:runnerUid and all Version and Version-Platform sub-resources — now require the OrganizationEntitlements.Runners organization entitlement and return 402 without it; GET /v1/runner (list) filters results to entitled organizations (402 only when the caller has no entitled organization)
Bulk operation endpoints require the BulkActions feature entitlement on the caller's organization (402 otherwise)
Bulk provisioning recipe endpoints require the BulkProvisioning feature entitlement on the caller's organization (402 otherwise)
Device tag endpoints (GET /v1/device/tag, GET/POST/DELETE /v1/device/{deviceUid}/tag[/{tagUid}]) now require the Tags feature entitlement on the caller's organization (402 otherwise)
Device plugin set (PUT /v1/device/{deviceUid}/set-plugin/{pluginUid}) now requires Plugins, and device runner set (PUT /v1/device/{deviceUid}/set-runner/{runnerUid}) requires Runners, on the caller's organization (402 otherwise)
Device location assign/unassign (PUT/DELETE /v1/device/{deviceUid}/location/{locationUid}) now require the Locations feature entitlement on the caller's organization (402 otherwise)
Per-type latest device telemetry (GET /v1/device/{deviceUid}/telemetry/{telemetryType}/latest) now requires the telemetry type's entitlement on the device's organization (402 otherwise)
Custom script version and version-platform endpoints (/v1/custom-script/:customScriptUid/version**, …/version/:version/platform**) require the Scripts feature entitlement (402 otherwise)
Package and package version endpoints (/v1/package, /v1/package/:packageUid, /v1/package/:packageUid/version** incl. /file, /icon, /publish, /unpublish) require the PackagesManagement feature entitlement (402 otherwise)
Organization tag endpoints (/v1/organization-tag, /v1/organization-tag/:uid, /v1/organization-tag/tree, /v1/organization-tag/tree/latest) require the Tags feature entitlement (402 otherwise)
events: the event documentation generator now expands constrained generic event payloads to their real shapes instead of an empty {} — e.g. DeviceTelemetryRecordUpdated now documents name as the DeviceTelemetryType enum and data as the full MonitoringLogData payload union (with nested field descriptions preserved)
events: the event documentation generator now preserves field-level JSDoc through the type-fest identity wrappers Simplify/ReadonlyDeep/WritableDeep, so entity create/update events (ICreateEntityEvent/IUpdateEntityEvent) document their fields instead of dropping the descriptions during mapped-type reconstruction
events: documented every field of the public events (device connection, provisioning recipe and telemetry events); the shared type, uid and originator fields are described once on their base types (IEvent, IEntity, IOriginatorAwareEvent)
events: the event documentation generator now surfaces zod .describe() text — it overlays a zod-inferred type's runtime object- and field-level descriptions onto the generated schema, so zod schemas remain the single source of truth for documentation
types: documented the inline telemetry payload fields of MonitoringLogData, so the telemetry data shapes in the generated public event schema now carry field descriptions
events: Device.DeviceConnectionAdded and Device.DeviceConnectionDeleted are now classified as kind: telemetry (not domain) in the generated public event schema, matching their delivery over the telemetry stream
events: DeviceTelemetryRecordUpdated (all versions and its V1→V2→V3 upcasters) now constrains its telemetry-name parameter to DeviceTelemetryType and its payload to MonitoringLogData[N], instead of the previously widened string/unknown; consumers must instantiate it with concrete telemetry types
isMfaRequired field on CreateCompany command and CompanyCreated event (defaults to true), propagated through the platform consumer and persisted on the MongoDB organization model on company creation
hasMfaRequired field accepted in account.settings via the ChangeAccountSettings command and the account API settings object
events: deviceIdentityHash (the public, server-generated device id) added to the exported events Device.Verification.DevicePaired, Device.Verification.DeviceUnpaired, Device.DeviceConnectionAdded, Device.DeviceConnectionDeleted and Device.Telemetry.DeviceTelemetryRecordUpdated (all bumped to _rev: 3). The V2 → V3 upcasters inject deviceIdentityHash: null for historical events.
events: SENSITIVE_FIELDS_BY_EXPORTED_EVENT — a type-safe, exhaustive map (keyed by AllowedEventType) of the sensitive payload fields to strip per exported event, plus the stripSensitiveFields runtime helper. Single source of truth for both event-stream-distributor payload censoring and public-doc generation. Currently strips deviceUid from the six device-identified exported events.
command-handler: handlePairDeviceWithOrganizationAggregate (DevicePaired) and deviceDeprovisionAggregate (DeviceUnpaired) resolve the device and set deviceIdentityHash on emit, throwing if the device is missing.
events: the public event schema generator (generateEventSchemas.ts) now censors the strip-map fields from the public schema only (events-schema-public.json); the internal schema keeps them. Fixed the @public tag placement on DevicePaired/DeviceUnpaired so these exported events are now included in the public schema.
Bump @signageos/amqp to 0.13.x, which replaces the unbounded rejectedTimestamps Map with an LRU+TTL cache, fixing a memory leak in locked event consumption (CU-86c9c16yg)
api: POST /v1/device/verification now accepts an optional deviceName and policy to set on the device during pairing
Firmware versions support multiple device types: deviceTypes array replaces the single deviceType so one firmware record covers all compatible devices per (applicationType, version); new UpdateFirmwareVersionDeviceTypes command + FirmwareVersionDeviceTypesUpdated event allow updating device types of an existing firmware version; migration consolidates duplicated firmware records into one superset record
api: GET /v1/company now accepts a uids query parameter to filter companies by uid (intersected with the caller's accessible companies, so it never widens access)
api: Device telemetry history endpoint GET /v1/device/{deviceUid}/telemetry/{telemetryType} (and its /count companion) now accept since and until query parameters to filter records by createdAt (both inclusive, combinable)
API: Gate Event Stream exporter configuration endpoints (list/count/get/create/update/delete) behind the new EventStreams organization feature entitlement — returns 402 when the organization lacks it
API: Event Stream exporter responses now include per-exporter delivery observability (exported/errors/retries counters, lastSuccessfulExportAt, and the most recent failures on single-config reads)
types/user-domain-model: FROZEN_CONTENT content guard item type
command-handler: Restrict FROZEN_CONTENT content guard items to categories with FROZEN_CONTENT evaluationStrategy (and forbid other item types in such categories)
user-domain-model: Add deprecated field to content guard category model to support deprecating categories that are still referenced by alert rules
api: Add fields deprecated and evaluationStrategy to managed content guard category
Allow CloseDataReporter command to run when the previous run's receivedAt differs by less than 1 second from the 24-hour window boundary (fixes daily data-reporter skip due to millisecond timing jitter)
Platform Consumer no longer crashes on DeviceTelemetryRecordUpdated events with data: null; null telemetry is treated as non-compliant when evaluating policy status
Action log now records keepAppletRunning for proprietary timer changes, so the action-log row reflects the setting immediately instead of only after the device reports back
EventsInvalidatedCache passes correct options when creating rejected queues. This fixes the issues of the rejected queues being created as durable when they should be transient. This bug led to queues not being deleted when the service restarts, which caused a substantial load to RabbitMQ.
Fix platform-consumer event consumption race condition for Location CRUD and device assign/unassign by switching to bindEventBatch with organization/location lock keys
Add V2 Location events (LocationDeleted, LocationArchived, LocationUnarchived) and V3 device-location events (DeviceAssignedToLocation, DeviceUnassignedFromLocation) with organizationUid for proper event versioning
Location assign/unassign to/from device is now idempotent
command-handler: Allow users to be added to multiple companies without admin privileges
Better errors for firmware creation and publishing.
Added firmwareCommandModel to CQRS command models factory
Added semverVersion, deviceType, and description fields to firmware version entity
GET /v2/firmware new endpoint for listing firmware versions with advanced filtering (applicationType, semverVersion, deviceType, prefix/suffix matching)
GET /v2/firmware/count new endpoint for counting firmware versions
GET /v2/firmware/:firmwareVersionUid new endpoint for getting a single firmware version
Feature Entitlements — certain API endpoints now require the organization or company to have the appropriate entitlement; requests without it receive a 402 response
Support for Auth0 namespaced claims (https://signageos.io/sosAccountId) in JWT authentication, enabling CLI tools using Auth0 Device Flow
JWT auth fallback in organization-authenticated endpoints — when no clientId:secret is found, the middleware now verifies JWT tokens and resolves the organization from organizationUid query/body parameter
GET /v1/organization-tag returns assignedContentGuardItemsCount in response
PUT /v1/content-guard/item/{contentGuardItemUid}/tag/{tagUid} and DELETE /v1/content-guard/item/{contentGuardItemUid}/tag/{tagUid} endpoints for assigning and removing tags
Endpoint POST /v1/content-guard/ai-helper/upload-temp-image
Endpoint POST /v1/content-guard/ai-helper/generate/description
Endpoint POST /v1/content-guard/ai-helper/generate/prompt
Endpoint POST /v1/content-guard/ai-helper/finalize
GET /v1/js-api-version endpoint to get list of all available JS API versions
GET /v2/device now returns featureFlags field in device resources when set
GET /v2/device/:deviceUid now returns featureFlags field in device resource when set
PUT /v2/device/:deviceUid now accepts featureFlags field to enable/disable device features (screenshotCapture, systemLogs, cpuUsage, memoryUsage, customScripts, plugins, runners)
PUT /v1/account endpoint now supports updating favorites
accessLevel field to account resource in GET /v1/account
Connection to API DragonFly Redis database for request rate limiting and counting
New middleware that automatically detects white label settings by hostname and Auth0 status from JWT tokens, injecting client context into API requests
Integrated CQRS command models pattern into API endpoints with automatic client context injection and mock command dispatcher for testing
Endpoint /v1/device/:deviceUid/offline-range is no longer available in favor to /v1/device/:deviceUid/telemetry/latest, /v1/device/telemetry/latest or /v1/device/:deviceUid/telemetry/ONLINE_STATUS
Revert "Endpoint GET /v1/organization now supports account, organization and jwtToken authentication" due to increased number of 400 responses in production. Needs further investigation.
Improved error handling for policy creation and updates with encrypted values
Endpoint POST /v1/device/:deviceUid/connect - refactored to support account, organization and jwtToken authentication
Endpoint POST /v1/device/:deviceUid/disconnect - refactored to support account, organization and jwtToken authentication
Upgrade lib to v21.1.3 - Application won't exit if Redis connection is lost for a while. It will keep trying to reconnect forever or it apply provided retryStrategy instead
respond with SERVICE_UNAVAILABLE on redis error
Endpoint GET /v1/organization now supports account, organization and jwtToken authentication
Endpoint PUT /v1/device/:deviceUid/application/:applicationType/version - check that requested Android application version is not older than current application version (Android doesn't support downgrading applications)
Endpoint POST /v1/bulk-operation no longer produces an error about invalid body properties. Previously, it incorrectly required deviceIdentityHash to be included in the request body.
Endpoint POST /v1/:deviceUid/deprovision refactored to use new ACL
Endpoint GET /v1/device/:deviceUid/custom-script supports pagination, sorting and filtering by customScriptUids and versions
Endpoint DELETE /v1/device/:deviceUid/custom-script/:customScriptUid supports query parameter deleteVersions to delete all versions of the custom script as well
Endpoint GET /v1/device/:deviceUid/custom-script/:customScriptUid/version supports filtering by platforms
Endpoint GET /v1/device/:deviceUid/custom-script/:customScriptUid/version returns publishedAt and deprecatedAt
New endpoint GET /v1/device/:deviceUid/custom-script/count
New endpoint GET /v1/device/:deviceUid/custom-script/latest
Endpoint POST /v1/custom-script/:customScriptUid/version/:version/platform/:platform/archive converts base64 encoded md5 checksum to hexadecimal in the final file path.
Endpoints related to Device Telemetry are now loaded from MongoDB database instead of InfluxDB. This change is transparent to the user and should not affect the functionality of the API.
Calculating device current time for devices without specified timezone
Update privileges needed for endpoint POST /v1/policy/
Update privileges needed for endpoint PUT /v1/policy/
Occasional 404 on GET /v1/emulator after POST /v1/emulator call. (It was responding sooner than the emulator was written to the database)
Remove privilege check for reading the company. There are cases where a user needs access to a company's organizations but is only a guest in the parent company. The previous ACL check prevented this type of access. Removing the rule fixes the issue.
ACL provider respects company owner and company manager user roles and allows them to manage company child organizations entities for account based authentication
Endpoint POST /v1/policy allows organizationUid in body even if authenticated with organization token, as long as it matches the authenticated organization
GET /v1/applet/(:appletUid?/)?command endpoint. It's more scalable, because it doesn't require to specify deviceUid and it's possible to list commands for all devices in the organization.
Endpoint GET /v1/device/screenshot won't return screenshots of devices that have disabled screen capture.
Endpoints GET /v1/device/:deviceUid/screenshot and POST /v1/device/:deviceUid/screenshot fail with 403 Forbidden if device has disabled screen capture.
issue with PUT /v1/policy/:policyUid endpoint when policy name and items were specified, but it still was throwing error: MISSING_ITEMS_OR_NAME_FIELD_TO_UPDATE_POLICY
validation of policy items in PUT /v1/policy/:policyUid endpoint
Fixed conversion of OpenAPI to Postman collection, prefilling x-auth header with <string> instead of {{x-auth}} and other headers and variables similarly.
Removed a limitation in the DELETE device policy endpoint which required the deletion to be performed from the same organization the policy belonged to
Return a 404 Not Found code instead of a 500 Internal Server Error when the application version for the device is not found during the setting process.
PUT /v1/management/account/activate ignores authenticationStrategy parameter. Instead, it only uses the account's email to try to pair it with a Company by email domain, if it exists.
GET /v1/device/:deviceUid/applet/:appletUid/command and GET /v1/device/:deviceUid/applet/:appletUid/command/:commandUid response contains command field instead of commandPayload. This makes it consistent with JS API where the command event contains command field. It still contains commandPayload field as a duplicate for backwards compatibility.
/v1/device/:deviceUid/applet/:appletUid/command expects body to contain command field instead of commandPayload. This makes it consistent with JS API where the command event contains command field. The commandPayload field is still supported for backwards compatibility.
/admin/device/:deviceUid/organization api endpoint (only for internal use, admin privileges required)
Login with username and password via Auth0 for the first time, when the account existed prior in our system. Then Auth0 will call API to validate the email and password. If the email was a substring of another email that also exists in the system, sometimes it would validate for the wrong account.
(internal) Endpoint POST /v1/management/account/prepare now marks terms and conditions as agreed if parameter shouldAgreeWithTermsAndConditions (boolean) is provided in body of the request
POST /v1/organization has new limit for fields name from 50 to 255 characters and title from 100 to 255. Field name can contain only letters and numbers separated by dashes and it must start and end with a letter or a number
PUT /v1/organization/:organizationUid has new limit of for field title from 100 to 255 characters
PUT /v1/device/:deviceUid/peer-recovery attribute autoEnableTimeoutMs is required
PUT /v1/device/:deviceUid/auto-recovery attribute autoEnableTimeoutMs is required
Timing update endpoint handles old format of finishEvent object
Documentation for POST /v1/device/:deviceUid/screenshot contained explicit empty body which caused issues in Postman
Timing create/update changed these fields to optional - startsAt, endsAt, position, finishEvent, finishEventType, finishEventData. They are deprecated and shouldn't be used anymore
Endpoint /v1/organization-tag for getting organization tags
Endpoint /v1/device/:deviceUid/action-log/:actionUid for getting single action log
Endpoints for setting device properties (i.e. brightness, firmware type, etc.) respond with "Link" header that contains a link to the result of the operation, since it's asynchronous in nature
Endpoint device/:deviceUid/peer-recovery to enable/disable peer recovery on device and to fetch history of peer recovery settings
(internal) change of builder tool from webpack (4.40.2) to esbuild
(internal) speedup tests start by replacing builder from ts-node/register to esbuild-runner/register
(internal) check-types script removal from initial start of tests. Which speed up the process. check-types can be ran manually or it will be running in CI
(internal) Esbuild production build. Added external dependencies
(internal) Postman documentation update. Schema handling after access removal to dev account
(internal) Request rate limiter now doesn't throw 500 error, when invalid route is provided. But skips the request limitation instead
Location to organization tag relation (assign and unassign)
Organization tag get one, create, update and delete (archive)
Uptime computed by device or organization
Location create and update endpoints simplification of the feature parameter into two coordinates: { lat: number; long: number } and address: string. Where now the feature params is obtained programmatically from the mapbox API
Endpoint routeDateTime (/device/:deviceUid/time) now accept UNIX timestamp or date time. With or without offset, if with offset is ignored. Offset is now take from the time zone and then transformed into correct UNIX timestamp.