.. _device_class_rest_api: Usage ===== 1) Overview ----------- Endpoints ^^^^^^^^^ - ``GET /lce/api/device_classes`` *(filters optional)* - ``GET /lce/api/device_classes/{device_class_identifier}`` *(ID, GUID, or name)* - ``POST /lce/api/device_classes`` *(create or update, keyed by GUID)* - ``DELETE /lce/api/device_classes/{device_class_identifier}`` *(ID, GUID, or name)* - ``GET /lce/api/icons`` *(the icons that can be assigned)* - ``GET /lce/api/device_classes/{device_class_identifier}/icon`` - ``PUT /lce/api/device_classes/{device_class_identifier}/icon`` *(assign or unassign)* The Device Classes API lists, creates, updates and deletes device classes. No other platform API authors device classes, so until now a PowerPack that needed one had to write the ``master.definitions_dev_classes`` table directly. This API replaces those direct database writes with validated, audited changes that keep the device class's PowerPack revision up to date. A device class is what discovery assigns a device to, and what Dynamic Applications are aligned with: - **Discovery** matches a device against the identifiers of the device classes (``class_identifier_1``, ``class_identifier_2``, ``discovery_qualifier`` and the agent identifiers) to decide which device class it belongs to - **Dynamic Applications** are aligned with a device class through its ``class_type``, a number allocated by the platform when the device class is created .. important:: **A device class is keyed by its GUID, not its id.** A device class authored on one system, for example as part of a PowerPack, keeps its GUID on every other system it is installed on, while its ``id`` and ``class_type`` are allocated locally and differ from system to system. ``POST`` therefore creates or updates by ``guid``: see :ref:`device_class_upsert`. Query parameters ^^^^^^^^^^^^^^^^ The query parameters behave as they do in the :ref:`Dynamic Applications API `, which documents them in full. ``pretty`` (GET + POST) """"""""""""""""""""""" All endpoints that return device classes support a ``pretty`` boolean query parameter. Defaults to true. - ``pretty=true`` - Enums are serialized as their **string names** (``"device_type": "COMPONENT"``) - ``pretty=false`` - Enums are serialized as their **integer values** (``"device_type": 2``) .. note:: Regardless of ``pretty``, request payloads accept enums as either integers or string names (case-insensitive). ``fields`` and ``exclude`` (GET + POST) """"""""""""""""""""""""""""""""""""""" ``fields`` is an optional comma-separated **allowlist** of the fields to include in the response, and ``exclude`` an optional comma-separated **blocklist** of the fields to remove from it. When both are provided, ``fields`` is applied first, then ``exclude``. On ``POST`` they only apply to the ``entities`` returned with ``retrieve=true``. - Return only the identifiers of every device class: ``fields=id,guid,class_name,description`` - Leave out the system-specific fields: ``exclude=id,class_type,last_edited`` ``fields`` and ``exclude`` affect response serialization only. A path that does not exist is ignored (no error). ``validation_errors`` is always included when present, whatever ``fields`` and ``exclude`` specify. ``retrieve`` (POST only) """""""""""""""""""""""" ``POST /lce/api/device_classes`` supports a ``retrieve`` boolean query parameter. Defaults to false. - ``retrieve=true``: the response includes an ``entities`` list with each device class as it is stored after the request, in payload order. This is how to read back the ``id``, ``guid`` and ``class_type`` allocated to a new device class. - ``retrieve=false``: the response includes only the ``results`` list. ``commit`` (POST + DELETE) """""""""""""""""""""""""" ``POST /lce/api/device_classes`` and ``DELETE /lce/api/device_classes/{device_class_identifier}`` support a ``commit`` boolean query parameter. Defaults to true. - ``commit=true``: the changes are committed. - ``commit=false``: the request runs exactly as it would otherwise, validation included, and is then rolled back, so nothing is changed. The response is the one the committed request would have returned. Combine it with ``retrieve=true`` on ``POST`` to preview the resulting device classes, or use it on ``DELETE`` to check whether a device class can be deleted. ``require_all_filters`` and ``filter[...]`` (GET list only) """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" ``GET /lce/api/device_classes`` accepts filters of the forms: .. code-block:: text filter[][]= filter[][not][]= The supported operators are ``is``, ``contains``, ``gt`` (single value only), ``lt`` (single value only) and ``search`` (single value only, not negatable). Values may be comma-separated to express multi-value matching, and for ``is`` the special value ``null`` (case-insensitive) is a SQL ``NULL`` check on the fields that accept ``null``, such as ``pp_guid``. When multiple filters are provided, ``require_all_filters`` controls how they are combined: - ``require_all_filters=true``: all filters must match (**AND** logic). This is the default. - ``require_all_filters=false``: any filter may match (**OR** logic). Every stored field of a device class can be filtered on: ``id``, ``guid``, ``class_name``, ``description``, ``category`` (by id), ``device_type``, ``is_snmp``, ``class_identifier_1``, ``class_identifier_2``, ``discovery_qualifier``, ``agent_identifier_1``, ``agent_identifier_2``, ``root_device``, ``weight``, ``tier_id``, ``image``, ``pp_guid``, ``class_type`` and ``last_edited``. ``category_name`` is not a stored field and cannot be filtered on. .. note:: **Unlike the other listing endpoints of this service, filters are optional.** With no ``filter[...]`` parameter, ``GET /lce/api/device_classes`` lists every device class. When filters are provided and match nothing, the response is **404**; with no filters, an empty system returns **200** with an empty list. ``limit`` and ``offset`` (GET list only) """""""""""""""""""""""""""""""""""""""" ``GET /lce/api/device_classes`` pages through the device classes, ordered by id: - ``limit``: the maximum number of device classes to return. Defaults to all of them. - ``offset``: the number of matching device classes to skip. Defaults to ``0``. Both must be non-negative integers; anything else is rejected with **400**. An empty value, such as ``limit=``, is the same as leaving the parameter out. Paging applies after the filters, and a page past the last match is an empty ``device_classes`` list rather than a **404**, whether or not filters are provided. For example, the second page of 100: .. code-block:: text GET /lce/api/device_classes?limit=100&offset=100 ---- 2) Authentication & Authorization --------------------------------- Authentication ^^^^^^^^^^^^^^ Requires a valid Skylar One user session. Access Hooks ^^^^^^^^^^^^ To call ``GET /lce/api/device_classes``, ``GET /lce/api/device_classes/{device_class_identifier}``, ``GET /lce/api/icons`` or ``GET /lce/api/device_classes/{device_class_identifier}/icon`` you must have the following Access Hook: - ``DEV_CLASS_VIEW`` To call ``POST /lce/api/device_classes`` or ``DELETE /lce/api/device_classes/{device_class_identifier}`` you must have the following Access Hooks: - ``DEV_CLASS_VIEW`` - ``SYS_CUSTOM_DEV_CLASS_PAGE`` To call ``PUT /lce/api/device_classes/{device_class_identifier}/icon`` you must have the following Access Hooks: - ``DEV_CLASS_VIEW`` - ``DEV_CLASS_EDIT`` ``DEV_CLASS_EDIT`` is the hook the GraphQL ``setDeviceClassIcon`` mutation checks, not ``SYS_CUSTOM_DEV_CLASS_PAGE``. See :ref:`device_class_icons`. ---- 3) The device class ------------------- A device class is stored in ``master.definitions_dev_classes``. The API names its fields rather than exposing the column names, several of which are misspelled or misleading in the table. .. list-table:: :header-rows: 1 :widths: 18 14 14 18 36 * - Field - Type - Column - Required / default - Description * - ``id`` - integer - ``id`` - read-only - The numeric id of the device class on this system. Allocated by the database; it is not portable between systems. * - ``guid`` - GUID - ``devtype_guid`` - generated when omitted - The identity of the device class: 32 hexadecimal characters, in either case (many device classes shipped with the platform have lowercase GUIDs). The value is kept as provided, and lookups by GUID ignore case. It cannot change once the device class exists. See :ref:`device_class_upsert`. * - ``class_name`` - string (1 to 64) - ``class`` - **required** on create - The class of the device class, typically the vendor or platform, such as ``Microsoft``. * - ``description`` - string (1 to 64) - ``descript`` - **required** on create - The description of the device class, typically the model or type, such as ``Windows Server 2019``. * - ``category`` - integer - ``family`` - **required** on create - The id of the device category the device class belongs to. A payload may provide the id, the GUID or the exact name of the category. See :ref:`device_class_categories`. * - ``device_type`` - enum - ``virtual`` - ``PHYSICAL`` - Whether the device class describes physical, virtual or component devices. See :ref:`device_class_device_type`. * - ``is_snmp`` - boolean - ``is_snmp`` - ``false`` - Whether devices of the device class are discovered over SNMP. See :ref:`device_class_identifier_rules`. * - ``class_identifier_1`` - string (0 to 255) - ``identifyer_1`` - ``""`` - The first identifier discovery matches devices against: the sysObjectID of an SNMP device class, or the first class identifier of a component device class. * - ``class_identifier_2`` - string (0 to 255) - ``identifyer_3`` - ``""`` - The second identifier discovery matches devices against: the second class identifier of a component device class, or the value the ``discovery_qualifier`` OID must have for an SNMP device class. Stored in the ``identifyer_3`` column. * - ``discovery_qualifier`` - string (0 to 255) - ``identifyer_2`` - ``""`` - The OID whose value must equal ``class_identifier_2`` for an SNMP device to match the device class. * - ``agent_identifier_1`` - string (0 to 255) - ``agent_identifier_1`` - ``""`` - The first identifier agent discovery matches devices against. * - ``agent_identifier_2`` - string (0 to 255) - ``agent_identifier_2`` - ``""`` - The second identifier agent discovery matches devices against. * - ``root_device`` - boolean - ``root_device`` - ``false`` - Whether devices of the device class are the root of a component tree. * - ``weight`` - integer (1 to 10) - ``weight`` - ``1`` - The precedence of the device class when several device classes match a device. * - ``tier_id`` - integer (0 or more) - ``tier_id`` - ``0`` - The device class tier the device class belongs to: the id of a row of ``master.definitions_dev_class_tiers``, or ``0`` for "No Tier". * - ``image`` - string (1 to 32) - ``image`` - ``"_generic_unknown.png"`` - The legacy PNG image file of the device class. Its icon is set with its own endpoint, see :ref:`device_class_icons`. * - ``pp_guid`` - GUID or ``null`` - ``ppguid`` - ``null`` - The GUID of the PowerPack the device class belongs to, in either case. It must be the GUID of a PowerPack installed on the system. * - ``class_type`` - integer - ``class_type`` - read-only - The number Dynamic Applications are aligned with. Allocated when the device class is created and never changed. See :ref:`device_class_class_type`. * - ``last_edited`` - string or ``null`` - ``date_edit`` - read-only - When the device class was last edited, as an ISO 8601 timestamp such as ``2026-01-05T10:00:00``. ``null`` for a device class that has never been edited. * - ``category_name`` - string or ``null`` - *(from the category)* - read-only - The name of the category the device class belongs to, for display. * - ``validation_errors`` - array - *(none)* - read-only - Only present on a device class whose stored values fail validation. See :ref:`device_class_errors`. String values have leading and trailing whitespace removed. A field the model does not define is rejected with **422**, and text that cannot be stored as latin1 is rejected with **422**. .. note:: The platform shows a device class by its **name**: its ``class_name`` and ``description`` joined by ``" | "``, such as ``Microsoft | Windows Server 2019``. The name is not a field of the device class, but it is what the ``name`` of the ``results`` of ``POST`` and ``DELETE`` contains, and it is accepted wherever a device class identifier is. Names are not unique: several device classes can share a class and description, told apart by their identifiers. See :ref:`device_class_identifiers`. .. _device_class_device_type: Device types ^^^^^^^^^^^^ ``device_type`` is stored in the ``virtual`` column as an integer. It is serialized by name with ``pretty=true`` (the default) and by value with ``pretty=false``, and either is accepted in a payload: .. list-table:: :header-rows: 1 :widths: 20 10 70 * - Name - Value - Meaning * - ``PHYSICAL`` - ``0`` - A physical device, typically discovered by its sysObjectID. * - ``VIRTUAL`` - ``1`` - A virtual device, created rather than discovered. * - ``COMPONENT`` - ``2`` - A component device, discovered by a Dynamic Application of its parent device. Class identifiers ^^^^^^^^^^^^^^^^^ The identifiers are what discovery matches a device against to choose its device class. What each one means depends on the kind of device class: - **SNMP device class** (``is_snmp`` true): ``class_identifier_1`` is the sysObjectID devices are matched against. To tell apart devices that share a sysObjectID, a *discovery qualifier* can be added: ``discovery_qualifier`` is an OID, and ``class_identifier_2`` the value it must have. - **Component device class** (``device_type`` ``COMPONENT``): ``class_identifier_1`` and ``class_identifier_2`` are the class identifiers the discovering Dynamic Application reports. - **Agent discovery**: ``agent_identifier_1`` and ``agent_identifier_2`` are matched against what the agent reports. All of them default to an empty string. Note the column names: ``class_identifier_2`` is stored in ``identifyer_3``, and ``discovery_qualifier`` in ``identifyer_2``. For example, an SNMP device class qualified by the value of ``sysDescr``: .. code-block:: json { "class_name": "Cisco Systems", "description": "Catalyst 9300", "category": "Network", "device_type": "PHYSICAL", "is_snmp": true, "class_identifier_1": "1.3.6.1.4.1.9.1.2494", "discovery_qualifier": "1.3.6.1.2.1.1.1.0", "class_identifier_2": "Catalyst 9300" } .. _device_class_identifier_rules: Identifier rules ^^^^^^^^^^^^^^^^ The identifiers must be consistent with the kind of device class. These are the rules of the platform's own device class editor, and a device class that breaks one is rejected with **422**: - A ``COMPONENT`` device class cannot have ``is_snmp`` true. - A ``PHYSICAL`` device class with ``is_snmp`` true must have a numeric OID as its ``class_identifier_1`` (such as ``1.3.6.1.4.1.9.1.2494``, a leading dot is allowed), unless ``agent_identifier_1`` is set. - On such a device class, when ``discovery_qualifier`` or ``class_identifier_2`` is set, ``discovery_qualifier`` must be a numeric OID. - A device class that is neither SNMP nor a component must have an empty ``class_identifier_1``. The rules are checked against the complete result of a request, so a stored device class that breaks one is rejected on every update, whatever field the update changes, until the payload fixes it. Every broken rule is listed in the error message. .. _device_class_categories: Categories ^^^^^^^^^^ Every device class belongs to a device category (``master.definitions_dev_cats``), such as ``Servers`` or ``Network``. In a payload, ``category`` accepts any of: - the **id** of the category, as an integer or a string of digits (``901`` or ``"901"``) - the **GUID** of the category (its ``family_guid``), in either case, such as ``"B3A8C1E0D0F4C1A2B3C4D5E6F7A8B9C0"`` - the **exact name** of the category, such as ``"Servers"`` A response always returns ``category`` as the id, together with the read-only ``category_name``. The category's GUID is stored with the device class (``family_guid``) and kept in step whenever the category changes. A request is rejected with **422** when no category matches, or when the name matches several categories (category names are not unique); provide the id or the GUID of the category instead. .. note:: The id of a category is local to the system, like the id of a device class. When copying a device class to another system, set ``category`` to the GUID or the name of the category rather than sending the retrieved id, which may name a different category there, or none. ``category_name`` is read-only and is discarded from a payload, so it cannot be used to set the category. .. _device_class_class_type: Class types ^^^^^^^^^^^ ``class_type`` is what Dynamic Applications are aligned with, and what devices record as their device class. It is allocated by the service when a device class is created, as the next number after the highest ``class_type`` in use, and it never changes afterwards. Concurrent creations wait for one another rather than allocating the same number. ``class_type`` is read-only: it is returned by every read, and discarded from every payload. A device class keeps its ``guid`` across systems but not its ``class_type``. ---- .. _device_class_identifiers: 4) Identifying a device class ----------------------------- ``GET`` and ``DELETE`` on ``/lce/api/device_classes/{device_class_identifier}`` accept any of: - the numeric **id**, such as ``5001``. A numeric identifier is treated as an id, unless it is a 32-digit GUID. - the **GUID**, such as ``1F2E3D4C5B6A79881F2E3D4C5B6A7988``, in either case - the **name**: the class and description joined by ``" | "``, such as ``Microsoft | Windows Server 2019`` The same applies to ``GET`` and ``PUT`` on ``/lce/api/device_classes/{device_class_identifier}/icon``. .. important:: **Requests that change a device class only accept an exact name.** ``DELETE /lce/api/device_classes/{device_class_identifier}`` and ``PUT /lce/api/device_classes/{device_class_identifier}/icon`` accept an id, a GUID or an exact name, and are never fuzzy matched, so a typo cannot select another device class. A name without an exact match returns **404**: .. code-block:: json { "error": "Device Class Not Found", "matches": [], "message": "No device class found named exactly: Microsoft | Windows Server 2091", "success": false } The ``GET`` endpoints still match names approximately, as described below. A name is looked up by exact match first. When exactly one device class has the name, it is used. Names are not unique, and when several device classes have the name, the response is **404** with their GUIDs in ``matches``; use one of them instead: .. code-block:: json { "error": "Device Class Not Found", "matches": [ "2A3B4C5D6E7F80912A3B4C5D6E7F8091", "a7c3e9f1b5d2486c9e0f1a2b3c4d5e6f" ], "message": "Several device classes are named 'Cisco Systems | Catalyst 9300'. Provide the GUID of the device class instead. Their GUIDs are in the matches field", "success": false } On the ``GET`` endpoints, when there is no exact match and the name is at most 64 characters long, it is matched approximately, and a confident fuzzy match is used. When the fuzzy match is not confident, the response is **404** with the closest names in ``matches``: .. code-block:: json { "error": "Device Class Not Found", "matches": [ "Microsoft | Windows Server 2019", "Microsoft | Windows Server 2022" ], "message": "Device Class name is too ambiguous. There were multiple Device Classes that matched. Please provide a more specific name. Provided name: Microsoft Windows Server. Similar matches found in matches field", "success": false } A name longer than 64 characters (after removing surrounding whitespace) is only looked up by exact match, and returns **404** with no ``matches`` when there is none: ``No device class found named exactly: ``. .. note:: A name is used as-is in the path, URL-encoded as usual. It contains spaces and a ``|``: ``Microsoft | Windows Server 2019`` is ``Microsoft%20%7C%20Windows%20Server%202019``. A name may also contain a ``/``, as hundreds of the platform's device classes do (such as ``3Com | 3Com NB:G crew dualwide w/Ae``); the path accepts it. Encoding it as ``%2F`` makes no difference, as the server decodes the path before routing it. The one exception is a name that ends in ``/icon``, which the path cannot tell apart from the icon endpoints of section 8: address such a device class by its id or GUID. Prefer the GUID in automation: it is exact, unique and portable. ---- 5) Retrieving device classes ---------------------------- ``GET /lce/api/device_classes`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Lists the device classes, ordered by id. With no ``filter[...]`` parameter every device class is listed; with filters, only the device classes that match. .. code-block:: json { "device_classes": [ { "agent_identifier_1": "", "agent_identifier_2": "", "category": 901, "category_name": "Servers", "class_identifier_1": "1.3.6.1.4.1.311.1.1.3.1.3", "class_identifier_2": "", "class_name": "Microsoft", "class_type": 5001, "description": "Windows Server 2019", "device_type": "PHYSICAL", "discovery_qualifier": "", "guid": "1F2E3D4C5B6A79881F2E3D4C5B6A7988", "id": 5001, "image": "_generic_unknown.png", "is_snmp": true, "last_edited": "2026-01-05T10:00:00", "pp_guid": "6450441E71B0EB1933D18A6C6252CF28", "root_device": false, "tier_id": 0, "weight": 1 } ] } ``GET /lce/api/device_classes/{device_class_identifier}`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Returns one device class, by id, GUID or name (see :ref:`device_class_identifiers`), as a single object in the same form as an entry of ``device_classes``. Reads never fail because of stored data. A device class written outside this API can hold values that fail validation, such as an empty ``description``; it is returned as stored, with a ``validation_errors`` list describing each problem: .. code-block:: json "validation_errors": [ {"field": "description", "message": "String should have at least 1 character"} ] .. _device_class_round_trip: A retrieved device class is a valid payload ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Every device class returned by a ``GET`` (or in the ``entities`` of a ``POST``) can be sent straight back to ``POST /lce/api/device_classes``. The read-only fields ``category_name``, ``class_type``, ``last_edited`` and ``validation_errors`` are discarded from payloads, and the other fields are compared with what is stored, so sending back an unchanged device class writes nothing. To change a device class, retrieve it, edit the fields that need to change, and post it back. A device class with ``validation_errors`` is rejected with **422** until the invalid values are corrected in the payload. ---- .. _device_class_upsert: 6) Creating and updating device classes --------------------------------------- ``POST /lce/api/device_classes`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Creates or updates one or more device classes. The request body is any of: - a single device class object - an array of device class objects - an object with a ``device_classes`` array, which is the form ``GET /lce/api/device_classes`` returns The request must be sent as JSON with a ``Content-Type`` of ``application/json``. GUID-keyed upsert ^^^^^^^^^^^^^^^^^ Each device class of the payload is matched to a stored device class by its ``guid``: .. list-table:: :header-rows: 1 :widths: 30 70 * - Payload - Result * - ``guid`` of an existing device class, in either case - **Updates** that device class. Its stored GUID is kept, and returned, whatever the case of the payload's. Only the fields provided are applied, merged with the stored values, and only the fields whose value actually changes are written. Any ``id`` is ignored. * - ``guid`` that does not exist on the system - **Creates** a device class with that GUID, kept in the case provided. Any ``id`` is ignored. * - no ``guid`` - **Creates** a device class with a generated GUID. * - ``id`` without a ``guid`` - Rejected with **400**. A device class is not updated by id: provide the ``guid`` of the existing device class to update it, or omit the ``id`` to create one. This is checked before anything else about the device class, such as its category. The GUID is the key of a device class and its ``id`` is local to a system, so a provided ``id`` never selects or changes anything: a device class copied from another system keeps its GUID but not its id. ``class_name``, ``description`` and ``category`` are required to create a device class; every other field has a default (see the table in section 3). An update needs only the ``guid`` and the fields that change: .. code-block:: json { "guid": "1F2E3D4C5B6A79881F2E3D4C5B6A7988", "weight": 10 } The whole payload is handled in a single transaction. Every device class is validated before any is written, and if any device class fails, nothing is written. Validation rules ^^^^^^^^^^^^^^^^ Beyond the type and range of each field, a device class must satisfy, against the complete result (the stored values updated with the payload): - **Its identifiers are consistent with its kind.** See :ref:`device_class_identifier_rules`. - **Its category exists.** ``category`` must resolve to exactly one category (see :ref:`device_class_categories`). - **Its tier exists.** ``tier_id``, when not ``0`` ("No Tier"), must be the id of a row of ``master.definitions_dev_class_tiers``. - **Its PowerPack exists.** ``pp_guid``, when not ``null``, must be the GUID of a PowerPack installed on the system. - **Its text can be stored.** Every string must be encodable as latin1. - **Its GUIDs are unique in the payload.** The same ``guid`` may not appear twice, compared without regard to case (**400**). Device classes may share a ``class_name`` and ``description``, as they do on the platform; the name is not a uniqueness rule. A rule that fails rejects the whole request with **422** (**400** for a duplicate GUID), naming the device class and the rule. PowerPack revisions and the audit log ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Every device class that is created or changed is recorded in the audit log, such as ``User 'em7admin' added a Device Class named 'Acme | Gadget Component', ID: 5004``. The revision of each PowerPack whose device classes change is incremented, by the number of its device classes that changed. A device class moved from one PowerPack to another increments both. A PowerPack that is no longer installed is skipped, so a device class whose PowerPack was removed can still be updated and deleted. A device class that is posted unchanged writes nothing: it is not audited, it does not increment a revision, and its ``last_edited`` is not updated. Its entry in ``results`` has ``"new": false``, like an update. Response ^^^^^^^^ .. code-block:: json { "results": [ { "guid": "7C1D9E2B4A6F48B1A3C5D7E9F0B2C4D6", "id": 5004, "name": "Acme | Gadget Component", "new": true } ], "success": true } ``results`` holds one entry per device class, in payload order: its ``id``, ``guid``, ``name`` and whether it is ``new``. With ``retrieve=true``, the response also holds an ``entities`` list with each device class as stored, in the same order. ---- 7) Deleting a device class -------------------------- ``DELETE /lce/api/device_classes/{device_class_identifier}`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Deletes a device class, identified by id, GUID or name (see :ref:`device_class_identifiers`). .. code-block:: json { "result": { "guid": "3B4C5D6E7F8091A23B4C5D6E7F8091A2", "id": 5003, "name": "Acme | Widget Component" }, "success": true } .. important:: **A device class that is in use cannot be deleted.** When devices belong to the device class, or Dynamic Applications are aligned with it (both through its ``class_type``), the request is refused with **409** and a ``references`` object listing what still uses it: .. code-block:: json { "error": "Device Class In Use", "message": "Device class 'Cisco Systems | Catalyst 9300' cannot be deleted while it is in use: 1 device(s) belong to it and 1 Dynamic Application(s) are aligned to it", "references": { "devices": 1, "dynamic_applications": [ { "guid": "0F1E2D3C4B5A69780F1E2D3C4B5A6978", "id": 1207, "name": "Cisco: Catalyst Inventory" } ] }, "success": false } ``devices`` is the number of devices of the device class, and ``dynamic_applications`` the ``id``, ``guid`` and ``name`` of each Dynamic Application aligned with it. Reassign or delete the devices and unalign the Dynamic Applications before deleting the device class. .. note:: This is stricter than the platform's own device class editor, which refuses a deletion only when devices belong to the device class, and silently unaligns its Dynamic Applications. This API also refuses while Dynamic Applications are aligned, so that an alignment is never removed as a side effect of a deletion. Deleting a device class also deletes its icon assignment (its row in ``master_ap2.dev_class_icon``) and its legacy fallback icon mapping (its row in ``master.definitions_dev_class_svg_icons``), invalidates the platform's device class cache (the ``ent_device_class`` entry of ``master.cache_data``), as the platform's editor does, records the deletion in the audit log and increments the revision of its PowerPack, unless that PowerPack is no longer installed. The device class cache is invalidated on deletion only. Creating and updating a device class leave it alone, as the platform's device class editor does. With ``commit=false``, the deletion is validated, the response is the one a committed deletion would return (including a **409**), and nothing is deleted. This is how to check whether a device class can be deleted. ---- .. _device_class_icons: 8) Device class icons --------------------- The icon of a device class is not one of its fields: it is an assignment in ``master_ap2.dev_class_icon``, keyed by the device class's GUID, made through three endpoints of their own. The ``image`` field is the legacy PNG image file name, not the icon. - ``GET /lce/api/icons`` lists the icons that can be assigned - ``GET /lce/api/device_classes/{device_class_identifier}/icon`` reads the icon of a device class - ``PUT /lce/api/device_classes/{device_class_identifier}/icon`` assigns or unassigns it Together with the device class endpoints, this lets a device class be created and given its icon entirely through this service. Assigning an icon does exactly what the GraphQL ``setDeviceClassIcon`` mutation does, which remains an equivalent alternative. .. note:: **Creating icons is not part of this API.** An icon must already exist to be assigned. Upload new icons with the GraphQL ``createIcon`` mutation, which validates and compresses the SVG. Listing icons ^^^^^^^^^^^^^ ``GET /lce/api/icons`` lists the icons in ``master_ap2.icons``, ordered by name. The optional ``search`` query parameter keeps only the icons whose name contains it, ignoring case. It is matched literally: ``%`` and ``_`` are not wildcards. Surrounding whitespace is ignored, so a blank ``search`` lists every icon. ``size`` is the size of the icon in bytes: .. code-block:: json { "icons": [ { "id": "ckcwaww0000000widgetcomponent1", "name": "Widget", "size": 42 }, { "id": "ckcwaww0000000windowsserver01", "name": "Windows Server", "size": 42 } ] } Reading the icon of a device class ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ``GET /lce/api/device_classes/{device_class_identifier}/icon`` identifies the device class by id, GUID or name (see :ref:`device_class_identifiers`) and returns: .. code-block:: json { "device_class": { "guid": "1F2E3D4C5B6A79881F2E3D4C5B6A7988", "id": 5001, "name": "Microsoft | Windows Server 2019" }, "fallback_icon": { "id": "A1B2C3D4E5F60718293A4B5C6D7E8F90", "name": "Server" }, "icon": { "id": "ckcwaww0000000windowsserver01", "name": "Windows Server" } } - ``icon`` is the icon assigned to the device class (``master_ap2.dev_class_icon``), or ``null`` when none is assigned. - ``fallback_icon`` is the legacy icon of the device class (``master.definitions_dev_class_svg_icons`` joined to ``master.definitions_default_svg_icons``), which the platform shows when no icon is assigned, or ``null``. It is reported for information only: this API never changes it, except that deleting the device class removes it. Icons are assigned by GUID, so a device class without a GUID is rejected with **422**. Assigning an icon ^^^^^^^^^^^^^^^^^ ``PUT /lce/api/device_classes/{device_class_identifier}/icon`` takes a body naming the icon by its ``id`` or its exact ``name``: .. code-block:: json { "icon": "Windows Server" } or ``{"icon": null}`` to unassign the icon. Like ``setDeviceClassIcon``, it replaces the assignment of the device class and records the change in the audit log. It does **not** increment the revision of the device class's PowerPack, and it leaves ``fallback_icon`` as it is. It supports ``commit=false``, which validates the change and rolls it back. Unlike ``setDeviceClassIcon``, which always rewrites the assignment, assigning the icon a device class already has, or unassigning when no icon is assigned, changes nothing and writes no audit entry. The response is the same. The response holds the icons of the device class after the change, in the same form as the ``GET``: .. code-block:: json { "result": { "device_class": { "guid": "1F2E3D4C5B6A79881F2E3D4C5B6A7988", "id": 5001, "name": "Microsoft | Windows Server 2019" }, "fallback_icon": { "id": "A1B2C3D4E5F60718293A4B5C6D7E8F90", "name": "Server" }, "icon": { "id": "ckcwaww0000000windowsserver01", "name": "Windows Server" } }, "success": true } An icon is looked up by id first, then by exact name. When several icons share the name, the request is rejected with **422** and the message lists their ids; pass the id instead. An icon that does not exist is also rejected with **422**. The body must be sent as JSON with a ``Content-Type`` of ``application/json`` (**415** otherwise, as for ``POST``). Malformed JSON is rejected with **400** ``Invalid JSON payload provided``, and JSON that is not an object with an ``icon`` that is a non-empty string or ``null`` with **400** ``Invalid Icon Request``. .. important:: ``PUT /lce/api/device_classes/{device_class_identifier}/icon`` requires the ``DEV_CLASS_EDIT`` access hook, which is what the ``setDeviceClassIcon`` mutation checks. It is **not** the ``SYS_CUSTOM_DEV_CLASS_PAGE`` hook that creating, updating and deleting device classes require, so a user can be allowed to do one without the other. ---- 9) Examples ----------- Every example uses the same Skylar One session as every other endpoint in this service. Replace ``$SL1_HOST`` and ``$SL1_COOKIE`` with your host and session cookie. Listing device classes ^^^^^^^^^^^^^^^^^^^^^^ Every device class: .. code-block:: bash curl -sS -b "$SL1_COOKIE" "https://$SL1_HOST/lce/api/device_classes" The component device classes whose class contains ``Acme``, returning only their identifiers: .. code-block:: bash curl -sS -g -b "$SL1_COOKIE" \ "https://$SL1_HOST/lce/api/device_classes?filter[device_type][is]=COMPONENT&filter[class_name][contains]=Acme&fields=id,guid,class_name,description" .. code-block:: json { "device_classes": [ { "class_name": "Acme", "description": "Widget Component", "guid": "3B4C5D6E7F8091A23B4C5D6E7F8091A2", "id": 5003 } ] } ``-g`` stops curl from interpreting the ``[`` and ``]`` of the filters. Retrieving one device class ^^^^^^^^^^^^^^^^^^^^^^^^^^^ By GUID: .. code-block:: bash curl -sS -b "$SL1_COOKIE" \ "https://$SL1_HOST/lce/api/device_classes/3B4C5D6E7F8091A23B4C5D6E7F8091A2" By name, URL-encoded: .. code-block:: bash curl -sS -b "$SL1_COOKIE" \ "https://$SL1_HOST/lce/api/device_classes/Acme%20%7C%20Widget%20Component" .. code-block:: json { "agent_identifier_1": "", "agent_identifier_2": "", "category": 902, "category_name": "Component", "class_identifier_1": "acme_widget", "class_identifier_2": "", "class_name": "Acme", "class_type": 5003, "description": "Widget Component", "device_type": "COMPONENT", "discovery_qualifier": "", "guid": "3B4C5D6E7F8091A23B4C5D6E7F8091A2", "id": 5003, "image": "_generic_unknown.png", "is_snmp": false, "last_edited": "2026-01-05T10:00:00", "pp_guid": null, "root_device": false, "tier_id": 0, "weight": 1 } Creating a device class ^^^^^^^^^^^^^^^^^^^^^^^ Create a component device class in the ``Component`` category, by category name, and read it back with ``retrieve=true`` to learn its allocated ``class_type``: .. code-block:: bash curl -sS -X POST "https://$SL1_HOST/lce/api/device_classes?retrieve=true" \ -H "Content-Type: application/json" \ -b "$SL1_COOKIE" \ -d '{ "class_name": "Acme", "description": "Gadget Component", "category": "Component", "device_type": "COMPONENT", "class_identifier_1": "acme_gadget", "pp_guid": "6450441E71B0EB1933D18A6C6252CF28" }' .. code-block:: json { "entities": [ { "agent_identifier_1": "", "agent_identifier_2": "", "category": 902, "category_name": "Component", "class_identifier_1": "acme_gadget", "class_identifier_2": "", "class_name": "Acme", "class_type": 5004, "description": "Gadget Component", "device_type": "COMPONENT", "discovery_qualifier": "", "guid": "7C1D9E2B4A6F48B1A3C5D7E9F0B2C4D6", "id": 5004, "image": "_generic_unknown.png", "is_snmp": false, "last_edited": "2026-10-05T14:21:37", "pp_guid": "6450441E71B0EB1933D18A6C6252CF28", "root_device": false, "tier_id": 0, "weight": 1 } ], "results": [ { "guid": "7C1D9E2B4A6F48B1A3C5D7E9F0B2C4D6", "id": 5004, "name": "Acme | Gadget Component", "new": true } ], "success": true } Align Dynamic Applications with ``class_type`` ``5004``. To create the device class with a known GUID instead, for example the one it has in a PowerPack, include ``"guid"`` in the payload. Updating a device class ^^^^^^^^^^^^^^^^^^^^^^^ Provide the ``guid`` and the fields that change. Preview the result first with ``commit=false``: .. code-block:: bash curl -sS -X POST "https://$SL1_HOST/lce/api/device_classes?commit=false&retrieve=true" \ -H "Content-Type: application/json" \ -b "$SL1_COOKIE" \ -d '{"guid": "7C1D9E2B4A6F48B1A3C5D7E9F0B2C4D6", "class_identifier_2": "acme_gadget_v2", "weight": 5}' Then send the same request without ``commit=false`` to apply it. The ``results`` entry has ``"new": false``. Copying device classes to another system ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Retrieve the device classes of a PowerPack from one system: .. code-block:: bash curl -sS -g -b "$SL1_COOKIE" \ "https://$SL1_HOST/lce/api/device_classes?filter[pp_guid][is]=6450441E71B0EB1933D18A6C6252CF28" \ -o device_classes.json The retrieved ``id`` values can stay in the file: the target system ignores them and matches each device class by its GUID. Replace each ``category`` id with the category's name or GUID (see :ref:`device_class_categories`), then post the file to the other system, where the PowerPack must be installed. Each device class is created with its original GUID, or updated if that GUID is already there: .. code-block:: bash curl -sS -X POST "https://$OTHER_HOST/lce/api/device_classes" \ -H "Content-Type: application/json" \ -b "$OTHER_COOKIE" \ -d @device_classes.json Deleting a device class ^^^^^^^^^^^^^^^^^^^^^^^ Check that the device class can be deleted, then delete it: .. code-block:: bash curl -sS -X DELETE -b "$SL1_COOKIE" \ "https://$SL1_HOST/lce/api/device_classes/7C1D9E2B4A6F48B1A3C5D7E9F0B2C4D6?commit=false" curl -sS -X DELETE -b "$SL1_COOKIE" \ "https://$SL1_HOST/lce/api/device_classes/7C1D9E2B4A6F48B1A3C5D7E9F0B2C4D6" Giving a device class an icon ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Find the icon: .. code-block:: bash curl -sS -b "$SL1_COOKIE" "https://$SL1_HOST/lce/api/icons?search=windows" .. code-block:: json { "icons": [ { "id": "ckcwaww0000000windowsserver01", "name": "Windows Server", "size": 42 } ] } Assign it by id, or by its exact name, then read it back: .. code-block:: bash curl -sS -X PUT "https://$SL1_HOST/lce/api/device_classes/1F2E3D4C5B6A79881F2E3D4C5B6A7988/icon" \ -H "Content-Type: application/json" \ -b "$SL1_COOKIE" \ -d '{"icon": "ckcwaww0000000windowsserver01"}' curl -sS -b "$SL1_COOKIE" \ "https://$SL1_HOST/lce/api/device_classes/1F2E3D4C5B6A79881F2E3D4C5B6A7988/icon" Unassign it, so the platform shows the ``fallback_icon`` again: .. code-block:: bash curl -sS -X PUT "https://$SL1_HOST/lce/api/device_classes/1F2E3D4C5B6A79881F2E3D4C5B6A7988/icon" \ -H "Content-Type: application/json" \ -b "$SL1_COOKIE" \ -d '{"icon": null}' ---- .. _device_class_errors: 10) Validation & error responses -------------------------------- Status codes ^^^^^^^^^^^^ .. list-table:: :header-rows: 1 :widths: 8 30 62 * - Status - ``error`` - When * - **400** - ``Invalid Device Class Request`` - A malformed ``filter[...]``, a filter on an unknown field or with an unsupported operator; a payload that is not a device class, a list of them or a ``device_classes`` list, or holds no device classes; the same ``guid`` twice in one payload; an ``id`` without a ``guid``; a ``limit`` or ``offset`` that is not a non-negative integer; or an identifier that is not a usable name. * - **400** - ``Invalid Icon Request`` - A ``PUT .../icon`` body that is not an object with an ``icon`` that is a non-empty string or ``null``. * - **400** - ``Invalid JSON payload provided`` - The ``POST`` or ``PUT .../icon`` body is not valid JSON. * - **400** - ``No JSON payload provided`` - The ``POST`` body is empty, ``{}`` or ``[]``. * - **401** - *(authentication error)* - Missing or invalid authentication. * - **403** - ``Forbidden`` - The user lacks an access hook the endpoint requires. This body has ``error`` and ``message`` only, without ``success``. * - **404** - ``Device Class Not Found`` - No device class matches the identifier, several device classes share the name given (their GUIDs are in ``matches``), or filters match no device class (with an ``offset`` of ``0``). ``DELETE`` and ``PUT .../icon`` only accept an exact name, and return ``No device class found named exactly: `` for any other. A fuzzy name lookup on a ``GET`` lists the closest names in ``matches``. * - **409** - ``Device Class In Use`` - ``DELETE`` of a device class that devices or Dynamic Applications reference. The ``references`` object lists them. * - **415** - ``Unsupported Media Type`` - The ``POST`` or ``PUT .../icon`` body is not sent with a ``Content-Type`` of ``application/json``. * - **422** - ``Device Class(es) Failed Validation`` - A device class fails validation: a missing required field, a value of the wrong type or out of range, an unknown field, identifiers that break a rule of :ref:`device_class_identifier_rules`, a category that does not resolve, a ``tier_id`` that does not exist, a ``pp_guid`` that is not an installed PowerPack, text that cannot be stored as latin1, or a stored value that fails validation and is not corrected in the payload. A ``pp_guid`` is only checked when it is new or changed, so a device class whose stored PowerPack was since uninstalled can still be updated. For the icon endpoints: the device class has no valid GUID. * - **422** - ``Invalid Icon`` - ``PUT .../icon``: the icon does not exist, or several icons share the name given (the message lists their ids; pass the id instead). * - **500** - ``Could not get Device Classes``, ``Could not get Device Class``, ``Could not update Device Class(es)``, ``Could not delete Device Class``, ``Could not list Icons``, ``Could not get the Icon of a Device Class``, ``Could not set the Icon of a Device Class`` - An unexpected error, such as a database failure. Nothing is written. Error body ^^^^^^^^^^ .. code-block:: json { "error": "Device Class(es) Failed Validation", "message": "Device class 'Acme | Gadget Component' has invalid identifiers: A component device class cannot be an SNMP device class", "success": false } Every error body has ``success``, ``error`` and ``message``. A **404** adds ``matches``: the closest names, the GUIDs of the device classes that share a name, or empty, and a **409** adds ``references``. A field that fails validation is named in the message, with nested locations joined by ``" -> "``: .. code-block:: json { "error": "Device Class(es) Failed Validation", "message": "weight: Input should be less than or equal to 10", "success": false }