Documentation

Line Item Attributes API

Attach per-line key/value pairs to any line item — serial numbers, MAC addresses, firmware versions, warranty dates, asset tags. Attribute keys are managed at the tenant level (the catalog); entries are per-line-item.

Auth: Api-Key header. Same auth model as every other line-item endpoint.

Key Catalog (Tenant-Level)

Keys are the labels available for selection when adding attributes to a line item. Manage the catalog once at the tenant level; all projects share the same key list.

GET /api/v1/line-item-attribute-keys

List all active attribute keys for the tenant. Add ?includeInactive=true to also return deactivated keys. Each key carries cadAttributeTag, the AutoCAD block attribute it maps to, or null when it has none, and cadSyncDirection, how its value syncs with drawings: push, pull or both for a key with a CAD tag, none for one without. A tagged key whose direction was never chosen reads push.

Response

{
    "success": true,
    "data": [
        {
            "id": 7,
            "name": "Serial Number",
            "order": 0,
            "isActive": true,
            "cadAttributeTag": "SERIAL_NUMBER",
            "cadSyncDirection": "push",
            "usageCount": 12,
            "updatedAt": "2026-05-24T15:30:00Z"
        }
    ],
    "timestamp": "2026-05-24T17:35:00Z"
}

GET /api/v1/line-item-attribute-keys/{id}

Fetch a single key by its integer ID.

Response

{
    "success": true,
    "data": {
        "id": 7,
        "name": "Serial Number",
        "order": 0,
        "isActive": true,
        "cadAttributeTag": "SERIAL_NUMBER",
        "cadSyncDirection": "push",
        "usageCount": 12,
        "updatedAt": "2026-05-24T15:30:00Z"
    },
    "timestamp": "2026-05-24T17:35:00Z"
}

POST /api/v1/line-item-attribute-keys

Create a new key in the tenant's attribute key catalog.

Request Body

Field Type Required Description
name string Yes Display label for the key. Must be unique within the tenant.

Response (200 OK)

{
    "success": true,
    "data": {
        "id": 7,
        "name": "Serial Number",
        "order": 0,
        "isActive": true,
        "cadAttributeTag": null,
        "cadSyncDirection": "none",
        "usageCount": 0,
        "updatedAt": "2026-05-24T17:35:00Z"
    },
    "message": "Attribute key created",
    "timestamp": "2026-05-24T17:35:00Z"
}

PUT /api/v1/line-item-attribute-keys/{id}

Rename a key, set or clear its CAD attribute tag, choose how it syncs with drawings, and/or change its active status. All fields are optional — supply only what you want to change. The response is the key, as GET returns it.

Request Body

Field Type Required Description
name string No New display label. Must be unique if provided.
isActive boolean No Set to false to deactivate (hides from dropdown but preserves existing entries).
cadAttributeTag string No The AutoCAD block attribute this key maps to, for example SERIAL_NUMBER. Omit it or send null to leave it unchanged; send "" to clear it.
cadSyncDirection string No How the key's value syncs with drawings, in any case: push (AVstackr's value is written to the drawing), pull (the drawing's value is copied into AVstackr) or both for a key with a CAD tag; none for one without. Omit it or send null to leave it unchanged. See the field ownership on the CAD Drawings API.

A CAD attribute tag is upper-cased, spaces and hyphens become _, and other characters are dropped; the result must start with a letter and be at most 32 characters of A-Z, 0-9 and _. It must be unique across your keys, deactivated ones included, and it cannot be one of the names reserved for built-in fields: AVSTACKR_ID, DEVICETAG, MANUFACTURER, MODEL, PARTNUMBER, DESCRIPTION, CATEGORY, LOCATION, SYSTEM, UNIT, QTY, RACKUNITS, HEIGHT, WIDTH, DEPTH, WEIGHT, PROJECT. A refused tag returns 400 INVALID_INPUT with the reason. Renaming a key keeps its tag. POST does not take a tag; set it with PUT after creating the key.

The fields are applied in the order name, cadAttributeTag, cadSyncDirection, isActive, each saved on its own, so a refused tag leaves a rename in the same request saved. Giving a key without a direction a tag sets push, and clearing the tag sets none. A cadSyncDirection other than the four words is refused with 400 INVALID_INPUT before anything is saved; one that does not fit the key, none on a tagged key or a direction on a key left without a tag, returns 400 INVALID_SYNC_DIRECTION with the name and tag already saved.

Deactivate vs. Delete

Deactivating a key hides it from the dropdown on new entries but leaves existing entries intact. Deletion is permanent — use it only when no line items reference the key (the DELETE endpoint returns a 409 with a usage count if the key is still in use).


DELETE /api/v1/line-item-attribute-keys/{id}

Permanently delete a key. Succeeds only if no line-item entries reference it.

409 KEY_IN_USE

If the key is in use, deletion is blocked and the response includes a usage count:

{
    "success": false,
    "error": {
        "code": "KEY_IN_USE",
        "message": "Attribute key 7 is used on 7 line item entries. Deactivate it (set isActive=false) or remove those entries first.",
        "details": "usage_count=7"
    },
    "timestamp": "2026-05-24T17:35:00Z"
}

Entries (Per-Line-Item)

Each line item has its own set of attribute entries — key/value pairs drawn from the tenant's key catalog.

GET /api/v1/projects/{projectId}/rooms/{roomId}/items/{itemId}/attributes

List all attribute entries for a line item.

Response

{
    "success": true,
    "data": [
        {
            "id": 4471,
            "equipmentId": 1234,
            "keyId": 7,
            "keyName": "Serial Number",
            "keyIsActive": true,
            "value": "SN-00142",
            "order": 0
        }
    ],
    "timestamp": "2026-05-24T17:35:00Z"
}

POST /api/v1/projects/{projectId}/rooms/{roomId}/items/{itemId}/attributes

Add a new attribute entry to a line item. Supply either keyId (the key's integer ID) or keyName (the key's display label) — keyId wins if both are provided.

Request Body

Field Type Required Description
keyId integer One of keyId / keyName Integer ID of the attribute key from the tenant catalog. Takes priority over keyName.
keyName string One of keyId / keyName Display name of the attribute key (case-insensitive match). Only active keys are searched.
value string No The attribute value. May be empty.

Response (200 OK)

{
    "success": true,
    "data": {
        "id": 4471,
        "equipmentId": 1234,
        "keyId": 7,
        "keyName": "Serial Number",
        "keyIsActive": true,
        "value": "SN-00142",
        "order": 0
    },
    "message": "Attribute entry created",
    "timestamp": "2026-05-24T17:35:00Z"
}

PUT /api/v1/projects/{projectId}/rooms/{roomId}/items/{itemId}/attributes/{entryId}

Update the value and/or key of an existing entry. All fields are optional — omit any field to leave it unchanged.

Request Body

Field Type Required Description
keyId integer No Change which key this entry uses (by integer ID). Takes priority over keyName if both supplied.
keyName string No Change which key this entry uses (by display label, case-insensitive). Only active keys are searched.
value string No New value for the entry. Preserved unchanged if omitted.

DELETE /api/v1/projects/{projectId}/rooms/{roomId}/items/{itemId}/attributes/{entryId}

Delete a single attribute entry. The key in the tenant catalog is not affected.

Per-Unit Attributes (Device Instances)

A line item with quantity > 1 has one physical unit per copy, each with its own attributes (serial numbers, MAC addresses). Units are keyed by a project-stable identity, so their attributes survive version revisions as a single source of truth. Duplicate lines of the same make/model in a room share one unit pool sized to their summed quantity. Change-order lines that have no live line item are not addressable by itemId here.

GET /api/v1/projects/{projectId}/rooms/{roomId}/items/{itemId}/units

List the physical units of a line item and each unit's attributes. Units are created on first access, and a new unit gets its device tag at once when the tag pattern can be filled in. A quantity-1 line has one unit. Returns an array of { instanceId, instanceKey, unitNumber, deviceTag, attributes: [{ id, keyId, keyName, value, order }] }, where instanceKey is the unit's permanent GUID and deviceTag is its tag, such as DISP-003, or null while it is untagged.

PATCH /api/v1/projects/{projectId}/rooms/{roomId}/items/{itemId}/units/{instanceId}

Sets one unit's device tag by hand, replacing the tag it has or giving an untagged unit one. The unit is found by its instanceId and must belong to the project in the path. A tag cannot be cleared, only replaced. A tag that fits the unit's own tag pattern, such as DISP-010 on a display, counts in the project's numbering, so later automatic tags continue after it.

Path Parameters

Field Type Required Description
projectIdintegerYesThe project's ID
roomIdintegerYesThe room of the line
itemIdintegerYesThe line item's ID
instanceIdintegerYesThe unit's instanceId, from the units list

Request Body

Field Type Required Description
deviceTag string Yes The new tag: 1 to 50 characters of letters, digits and . _ / -, starting with a letter or digit, and unique within the project. Surrounding spaces are trimmed.

Example Request

{
    "deviceTag": "DISP-010"
}

Response (200 - Success)

{
    "success": true,
    "data": {
        "instanceId": 5012,
        "instanceKey": "8f14e45f-ceea-467a-9575-1b2f6b0c3a21",
        "unitNumber": 2,
        "deviceTag": "DISP-010"
    },
    "message": "Device tag set",
    "timestamp": "2026-10-11T15:30:00Z"
}

Error Responses

StatusCodeDescription
400INVALID_INPUTdeviceTag is missing or blank
400INVALID_DEVICE_TAGThe tag breaks the character rule
400DEVICE_TAG_IN_USEAnother unit in the project has the tag. The message names that unit, its device and its room.
400UNKNOWN_FIELDSThe body names a field other than deviceTag
403READ_ONLY_ACCESSThe caller has read-only access
404UNIT_NOT_FOUNDNo unit with that instanceId in the project

POST /api/v1/projects/{projectId}/rooms/{roomId}/items/{itemId}/units/{instanceId}/attributes

Add one attribute entry to a single unit by its instanceId (from the units list). Body: { "keyId": <int> } or { "keyName": "<active key name>" } plus an optional "value". Returns the created entry with its id.

POST /api/v1/projects/{projectId}/rooms/{roomId}/items/{itemId}/units/by-number/{unitNumber}/attributes

One-call variant: add an attribute to a unit by its 1-based unit number (e.g. .../units/by-number/2/attributes for the second unit) without first looking up its instanceId. The unit is created if it doesn't exist yet. Same body and response as the by-instance variant.

PUT /api/v1/projects/{projectId}/rooms/{roomId}/items/{itemId}/units/{instanceId}/attributes/{entryId}

Update one unit attribute. Sparse — supply value to change it (omit to leave unchanged), and optionally keyId/keyName to repoint the key.

DELETE /api/v1/projects/{projectId}/rooms/{roomId}/items/{itemId}/units/{instanceId}/attributes/{entryId}

Delete one unit attribute entry. The tenant key catalog is not affected.

Error Codes

Code HTTP When
INVALID_INPUT 400 Blank name, missing required field, neither keyId nor keyName supplied, or a refused cadAttributeTag (invalid, reserved, or used by another key)
NOT_FOUND 404 Attribute key not found (catalog endpoints)
LINE_ITEM_NOT_FOUND 404 Line item not found or belongs to another tenant
ATTRIBUTE_NOT_FOUND 404 Attribute entry not found or belongs to a different line item
KEY_NOT_FOUND 404 The keyId or keyName on an entry create/update doesn't resolve to an active key
KEY_INACTIVE 409 POST entries only — keyId points at a key that exists but is deactivated
DUPLICATE_KEY_NAME 409 Case-insensitive name collision when creating or renaming a key
KEY_IN_USE 409 Hard delete attempted on a key that still has entries; error.details contains usage_count=N
INVALID_DEVICE_TAG 400 A unit's new device tag breaks the character rule
DEVICE_TAG_IN_USE 400 Another unit in the project already has the device tag
UNIT_NOT_FOUND 404 The unit's instanceId is not a unit of the project

Endpoint Summary

Method Endpoint Description
GET /api/v1/line-item-attribute-keys List tenant attribute keys
GET /api/v1/line-item-attribute-keys/{id} Get one key
POST /api/v1/line-item-attribute-keys Create a key
PUT /api/v1/line-item-attribute-keys/{id} Rename / set CAD tag / activate / deactivate a key
DELETE /api/v1/line-item-attribute-keys/{id} Hard-delete a key (blocked if in use)
GET /api/v1/projects/{p}/rooms/{r}/items/{i}/attributes List entries on a line item
POST /api/v1/projects/{p}/rooms/{r}/items/{i}/attributes Add an entry
PUT /api/v1/projects/{p}/rooms/{r}/items/{i}/attributes/{entryId} Update an entry
DELETE /api/v1/projects/{p}/rooms/{r}/items/{i}/attributes/{entryId} Delete an entry