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.

Response

{
    "success": true,
    "data": [
        {
            "id": 7,
            "name": "Serial Number",
            "order": 0,
            "isActive": true,
            "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,
        "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,
        "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 and/or change its active status. All fields are optional — supply only what you want to change.

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).
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. Returns an array of { instanceId, unitNumber, attributes: [{ id, keyId, keyName, value, order }] }. Empty for a quantity-1 line (use the line attribute endpoints).

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
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

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 / 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