Device Tags API
Read and replace the device category list and the system list that device tags are built from.
Every physical unit on a project can carry a device tag such as DISP-003, built from your company's tag pattern. The pattern draws on two lists: device categories, which classify catalog products (Display / DISP), and systems, which equipment lines belong to (Audio Visual / AV). This page covers the two lists. For how tags work in the app, see Device Tags.
Where the Other Device Tag Endpoints Are
| Task | Endpoint | Documented in |
|---|---|---|
| Set or clear a product's category | PUT /api/v1/products/{id}/category | Products API |
| Set or clear the category on many products | POST /api/v1/products/set-category | Products API |
| Set or clear a line's system | PUT /api/v1/projects/{projectId}/rooms/{roomId}/items/{itemId}/system | Projects API |
| Set or clear the system on many lines | POST /api/v1/projects/{projectId}/items/set-system | Projects API |
| Tag every untagged unit in a project | POST /api/v1/projects/{projectId}/device-tags/assign | Projects API |
| Read units with their tags; set a unit's tag | GET …/items/{itemId}/units, PATCH …/items/{itemId}/units/{instanceId} | Line Item Attributes API |
| Map an attribute key to a CAD attribute tag | PUT /api/v1/line-item-attribute-keys/{id} | Line Item Attributes API |
The tag pattern, sequence digits and numbering mode are set on the Device Tags card in Global Config; they have no endpoint. The CAD block library, the CAD tag catalog and a project's CAD units are in the CAD Blocks API.
Rules for Both Lists
- A
nameis trimmed, required, at most 60 characters, and unique in its list ignoring letter case. - An
abbreviationis trimmed and upper-cased, then must be 1 to 8 letters or digits (A-Z,0-9). It is unique in its list. orderis the row's 1-based position. APUTsets it from the order of the items you send.- A
PUTreplaces the whole list in one save: an item with anidupdates that row, an item without one creates a row, and every row you leave out is deleted. Nothing is saved when any item is refused. - Changing a name or an abbreviation affects only tags assigned afterwards. Existing tags never change.
- The first read of a list that has no rows fills it with the default list. A
PUTwith an empty array therefore deletes every row, and the nextGETreturns the defaults.
GET /api/v1/cad-categories
Returns the device category list, in order.
Example Request
GET /api/v1/cad-categories
Authorization: Bearer YOUR_JWT_TOKEN
Response (200 - Success)
{
"success": true,
"data": [
{
"id": 7,
"name": "Display",
"abbreviation": "DISP",
"isDrawable": true,
"expectedOn": ["plan", "elevation"],
"order": 1,
"updatedAt": "2026-10-11T15:30:00Z",
"cadBlocks": [
{ "view": "plan", "blockId": 9, "visibilityState": "TOP" }
]
},
{
"id": 34,
"name": "Cable",
"abbreviation": "CBL",
"isDrawable": false,
"expectedOn": [],
"order": 28,
"updatedAt": "2026-10-11T15:30:00Z",
"cadBlocks": []
}
],
"message": null,
"timestamp": "2026-10-11T15:30:00Z"
}
| Field | Type | Description |
|---|---|---|
id | integer | The category's ID. Products carry it as cadCategoryId. |
name | string | The long name shown in dropdowns, for example Display |
abbreviation | string | The short form that goes into tags, for example DISP |
isDrawable | boolean | false for items that are not drawn as devices, such as cable and labor. GET /api/v1/projects/{projectId}/cad/units leaves their units out unless asked; tags are assigned either way. |
expectedOn | array of string | The views the category's units are expected on, in view order: plan, ceiling, elevation, schematic. The project page counts a unit as not drawn while no linked drawing of such a view places it. Empty when the category is not checked. |
order | integer | The row's 1-based position in the list |
updatedAt | string (ISO 8601) | When the row last changed |
cadBlocks | array of object | The category's default CAD block for each view it sets, in view order: view, blockId and visibilityState (null for the block's default state). A product of the category uses it for any view the product sets no block of its own. Empty when the category has none. A block archived since it was chosen is still listed but no longer used. |
PUT /api/v1/cad-categories
Replaces the whole device category list and returns the list as saved. A category you leave out is deleted, and the products that had it become unclassified; their existing device tags stay.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
categories | array of object | Yes | The full list, in the order you want it. An empty array deletes every category. |
Category Item
| Field | Type | Required | Description |
|---|---|---|---|
id | integer | No | The ID of the category to update. Leave it out to create a category. |
name | string | Yes | The category name |
abbreviation | string | Yes | The abbreviation used in tags |
isDrawable | boolean | No | Defaults to true |
expectedOn | array of string | No | View names, any letter case; repeats are dropped. Leave the field out (or send null) to keep the category's views as they are; a new category then starts with none. An empty array clears them. |
cadBlocks | array of object | No | The category's default blocks, each { "view", "blockId", "visibilityState" } as in PUT /api/v1/products/{id}/cad-blocks, replacing the ones it has, a new category included. Leave the field out to keep the category's blocks as they are; an empty array clears them all. |
Example Request
{
"categories": [
{ "id": 7, "name": "Display", "abbreviation": "DISP", "expectedOn": ["plan", "elevation"], "cadBlocks": [ { "view": "plan", "blockId": 9, "visibilityState": "TOP" } ] },
{ "id": 34, "name": "Cable", "abbreviation": "CBL", "isDrawable": false },
{ "name": "Lectern", "abbreviation": "LECT" }
]
}
Response (200 - Success)
{
"success": true,
"data": [
{ "id": 7, "name": "Display", "abbreviation": "DISP", "isDrawable": true, "expectedOn": ["plan", "elevation"], "order": 1, "updatedAt": "2026-10-11T15:30:00Z", "cadBlocks": [ { "view": "plan", "blockId": 9, "visibilityState": "TOP" } ] },
{ "id": 34, "name": "Cable", "abbreviation": "CBL", "isDrawable": false, "expectedOn": [], "order": 2, "updatedAt": "2026-10-11T15:42:00Z", "cadBlocks": [] },
{ "id": 61, "name": "Lectern", "abbreviation": "LECT", "isDrawable": true, "expectedOn": [], "order": 3, "updatedAt": "2026-10-11T15:42:00Z", "cadBlocks": [] }
],
"message": "Device categories saved",
"timestamp": "2026-10-11T15:42:00Z"
}
Error Responses
| Status | Code | Description |
|---|---|---|
| 400 | INVALID_INPUT | categories is missing or holds a null item, an id appears twice, or a name or abbreviation breaks a rule. The message names the row, for example Row 3: Abbreviation 'LECTERNS1' is not valid: use 1 to 8 letters or digits (A-Z, 0-9), such as DISP. A cadBlocks item that is null, has no view or an unknown one, has no blockId, or repeats a view is refused the same way, and so is an expectedOn name that is not a view: Row 1: expectedOn: 'bogus' is not a view. Use plan, ceiling, elevation or schematic. |
| 400 | UNKNOWN_FIELDS | A cadBlocks item names a field other than view, blockId and visibilityState |
| 400 | BLOCK_ARCHIVED, VIEW_MISMATCH, INVALID_VISIBILITY_STATE | A cadBlocks item breaks a block rule, as in PUT /api/v1/products/{id}/cad-blocks. The message names the row. Every item is checked before anything is saved, so nothing changes, neither the list nor any category's blocks. |
| 403 | READ_ONLY_ACCESS | The caller has read-only access |
| 404 | NOT_FOUND | An id is not one of your categories |
| 404 | BLOCK_NOT_FOUND | A cadBlocks item's blockId is not one of your blocks. Nothing is saved. |
| 409 | DUPLICATE_CATEGORY | Two items share a name (ignoring letter case) or an abbreviation |
GET /api/v1/cad-systems
Returns the system list, in order. The default list is Audio Visual / AV, Information Technology / IT and Security / SEC.
Example Request
GET /api/v1/cad-systems
Authorization: Bearer YOUR_JWT_TOKEN
Response (200 - Success)
{
"success": true,
"data": [
{ "id": 3, "name": "Audio Visual", "abbreviation": "AV", "order": 1, "updatedAt": "2026-10-11T15:30:00Z" },
{ "id": 4, "name": "Information Technology", "abbreviation": "IT", "order": 2, "updatedAt": "2026-10-11T15:30:00Z" },
{ "id": 5, "name": "Security", "abbreviation": "SEC", "order": 3, "updatedAt": "2026-10-11T15:30:00Z" }
],
"message": null,
"timestamp": "2026-10-11T15:30:00Z"
}
The fields are those of a category without isDrawable and expectedOn. Equipment lines carry the system's id as cadSystemId.
PUT /api/v1/cad-systems
Replaces the whole system list and returns the list as saved. A system you leave out is deleted, and every line that had it, in every version of every project, becomes unassigned; existing device tags stay.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
systems | array of object | Yes | The full list, in the order you want it. Each item takes id (leave it out to create a system), name and abbreviation. An empty array deletes every system. |
Example Request
{
"systems": [
{ "id": 3, "name": "Audio Visual", "abbreviation": "AV" },
{ "id": 5, "name": "Security", "abbreviation": "SEC" },
{ "name": "Lighting", "abbreviation": "LTG" }
]
}
Response (200 - Success)
{
"success": true,
"data": [
{ "id": 3, "name": "Audio Visual", "abbreviation": "AV", "order": 1, "updatedAt": "2026-10-11T15:30:00Z" },
{ "id": 5, "name": "Security", "abbreviation": "SEC", "order": 2, "updatedAt": "2026-10-11T15:42:00Z" },
{ "id": 9, "name": "Lighting", "abbreviation": "LTG", "order": 3, "updatedAt": "2026-10-11T15:42:00Z" }
],
"message": "Systems saved",
"timestamp": "2026-10-11T15:42:00Z"
}
Error Responses
| Status | Code | Description |
|---|---|---|
| 400 | INVALID_INPUT | systems is missing or holds a null item, an id appears twice, or a name or abbreviation breaks a rule. The message names the row. |
| 403 | READ_ONLY_ACCESS | The caller has read-only access |
| 404 | NOT_FOUND | An id is not one of your systems |
| 409 | DUPLICATE_SYSTEM | Two items share a name (ignoring letter case) or an abbreviation |
MCP clients reach the same lists with list_cad_categories and list_cad_systems; see the MCP Server page.
