Eviworx
Docs

Assets API

Die Assets API verwaltet Assets mit Typen, Kategorien und Standorten, Ausgabe und Übergabe mit QR/PDF-Protokoll, CMDB-Beziehungen, typbezogenen Berechtigungen, Duplikat-Erkennung für Hersteller/Modell und Inventur-Scan.

📦
Funktionen
✓ Model-Clustering (Duplikat-Erkennung)
✓ QR/PDF-Handover mit Bestätigung
✓ CMDB-Beziehungen, Graph & Impact-Analyse
✓ Nutzungsrichtlinien je Asset-Typ
✓ Typbezogene Berechtigungen
✓ Standorte & Kategorien
✓ Inventur-Sessions mit Workflow & Notifications
✓ Consumables (Mengenartikel)
✓ Kaufdaten & Abschreibung
✓ Ausgabe & Rücknahme (Checkout/Checkin)
✓ Export (CSV/PDF), Bulk-Operationen
✓ Aktivitätsprotokoll, Schadensberichte

Authentifizierung & Permissions

Alle Asset-Endpunkte akzeptieren eine Session (Benutzer) oder einen X-API-Key-Header (API-Key mit aktiver Rolle). In beiden Fällen gelten die Rechte der Rolle. Details siehe User Management & RBAC und Authentication API.

Bereich Permission-Keys (feature.action)
Lesenassets.viewAll, assets.viewOwn, assets.viewDeleted (Papierkorb), assets.viewHistory
Bearbeitenassets.create, assets.update, assets.delete, assets.restore, assets.checkout, assets.checkin
Bulk / Exportassets.bulkEdit, assets.bulkDelete, assets.export
Labels / Scanassets.generateLabel, assets.scan
Stammdatenassets.manageTypes, assets.manageCategories, assets.manageLocations, assets.manageClusters, assets.manageTypePermissions
Handoverassets.viewAllHandovers, assets.viewOwnHandovers, assets.initiateHandover, assets.confirmReceipt, assets.requestReturn, assets.managePolicies

Typ-Sperre: Zusätzlich zu den globalen Rechten kann ein Asset-Typ gesperrt werden. Für gesperrte Typen gelten dann die Freigaben pro Rolle oder Benutzer (der zugewiesene Benutzer sieht sein Asset immer); für offene Typen nur die globalen Rechte. Listen enthalten deshalb nur die Assets, die der Aufrufer sehen darf.

Dieselbe Sicht-Prüfung steht vor JEDER Mutation: Aktualisieren, Löschen, Aus-/Einchecken, Installieren/Deinstallieren, Etikett und Barcode-Aktionen antworten 403, wenn das Asset für den Aufrufer nicht sichtbar ist — ein globales Handlungsrecht allein genügt nicht. Für den Papierkorb sind zwei Rechte nötig: assets.viewDeleted, um ihn zu sehen, und assets.restore zum Wiederherstellen.

Kritische Rechte (werden bei jeder Anfrage neu geprüft, sodass ein Entzug sofort wirkt; abgelehnte Versuche werden protokolliert): assets.export, assets.managePolicies, assets.manageClusters und assets.manageTypePermissions.

Antwort-Formate: Alle Listen der Domäne liefern { data } bzw. { data, pagination } — Assets, Typen, Kategorien, Standorte, Übergaben und Aktivitäten. Einzelobjekte kommen ohne Hülle. Beträge (purchasePrice, depreciationRate) sind JSON-Zahlen. Zähler wie assetCount werden aktuell gezählt.

Lese-Umfang je nach Recht: Ohne Sicht auf das einzelne Asset liefert der Scan nur die Basis-Angaben (Tag, Name, Status, Typ, Standort) — eine Inventur funktioniert damit weiter, Seriennummer und Zuweisung bleiben aber außen vor. Die Typen-Liste zeigt allen die Anzeige-Felder; customFieldSchema, hasTypePermissions und assetCount nur mit viewAll, manageTypes oder manageTypePermissions. Kategorien und Standorte verlangen viewAll oder das jeweilige manage-Recht. Die globale Aktivitäts-Historie zeigt ausschließlich Assets, die der Leser sehen darf.

Endpoints Übersicht

Kern-CRUD

Method Endpoint Beschreibung
GET/api/assetsAlle Assets (mit Filtern); ?deleted=1 = Papierkorb (Recht assets.viewDeleted)
GET/api/assets/:idEinzelnes Asset
POST/api/assetsAsset erstellen
PATCH/api/assets/:idAsset aktualisieren
DELETE/api/assets/:idAsset löschen (Soft-Delete)

Handover (QR/PDF)

Method Endpoint Beschreibung
GET/api/assets/handoversAlle Handovers (IT-View)
GET/api/assets/handovers/pendingPending Handovers für aktuellen User
POST/api/assets/handoversNeues Handover erstellen (Checkout/Transfer)
POST/api/assets/handovers/:id/acceptHandover akzeptieren
POST/api/assets/handovers/:id/rejectHandover ablehnen
PATCH/api/assets/handovers/:id/expires-atRückgabedatum einer Ausgabe ändern
GET/api/assets/handovers/:id/pdfPDF-Protokoll generieren
GET/api/handovers/:id/publicPublic QR-Zugriff (ohne Auth, HMAC-Token)

Ein Vorgang je Asset: Gleichzeitige Vorgänge auf demselben Asset schließen sich aus — Ausgabe, Rücknahme, Übergabe anlegen, Rückgabe anfordern, Installieren, Deinstallieren und die Komponenten-Mitnahmen beanspruchen das Asset. Der zweite Aufruf bekommt 409 — so entstehen nie zwei Ausgaben desselben Geräts an verschiedene Empfänger. Ebenfalls 409: eine zweite Übergabe an einem Asset, das bereits eine offene hat. Sperrkonflikte der Datenbank melden 409 DEADLOCK_DETECTED.

Nicht sichtbare Übergaben: Detail, PDF und Rückgabedatum antworten mit 404, wenn die Übergabe für den Aufrufer nicht sichtbar ist — dieselbe Antwort wie auf eine erfundene ID, damit nicht erkennbar ist, ob die Übergabe existiert. Wer sie sehen, aber nicht ändern darf, bekommt 403.

Öffentliches Protokoll: Der QR-/Share-Link führt auf eine datenminimierte Fassung: Sie nennt die Namen der Beteiligten, aber keine E-Mail-Adressen — weder in der JSON-Antwort noch im PDF. Das interne PDF bleibt vollständig. Die Fußzeile beider PDFs nennt die Übergabe-Nummer (HO-00042), nicht die interne Datenbank-ID.

Model-Clustering

Method Endpoint Beschreibung
GET/api/asset-model-clustersAlle Cluster (Duplikat-Kandidaten)
GET/api/asset-model-clusters/statsCluster-Statistiken
PATCH/api/asset-model-clusters/:id/canonicalCanonical-Werte setzen
POST/api/asset-model-clusters/:id/approveCluster bestätigen (bereit zum Merge)
POST/api/asset-model-clusters/:id/mergeCluster mergen (Duplikate bereinigen)
POST/api/asset-model-clusters/:id/rejectCluster ablehnen (kein Duplikat)
DELETE/api/asset-model-clusters/:idCluster löschen

Relations & Inventory

Method Endpoint Beschreibung
POST/api/asset-relationsCMDB-Relation erstellen
DELETE/api/asset-relations/:idRelation löschen
GET/api/asset-relations/asset/:idAlle Relationen eines Assets
GET/api/asset-relations/asset/:id/graphCMDB-Graph (Visualisierung)
GET/api/asset-relations/typesVerfügbare Relation-Typen

Erweiterte Operationen

Method Endpoint Beschreibung
GET/api/assets/statsAsset-Statistiken
GET/api/assets/exportCSV/PDF-Export
PATCH/api/assets/bulkBulk-Update (mehrere Assets)
DELETE/api/assets/bulkBulk-Delete (Soft-Delete)
POST/api/assets/:id/restoreGelöschtes Asset wiederherstellen (verlangt assets.restore UND assets.viewDeleted)
POST/api/assets/:id/checkoutAsset auschecken (User zuweisen)
POST/api/assets/:id/checkinAsset einchecken (zurückgeben)
POST/api/assets/:id/installLOCATION-Asset installieren (→ Standort)
POST/api/assets/:id/deinstallLOCATION-Asset deinstallieren
GET/api/assets/:id/labelQR-Label generieren (PDF)
GET/api/assets/:id/damage-reportSchadensbericht
GET/api/assets/my-consumablesMeine Verbrauchsartikel
GET/api/assets/suggestionsAuto-Complete-Vorschläge
GET/api/asset-activitiesAktivitäten aller sichtbaren Assets

Inventur-Sessions

Inventuren laufen über eigene Sessions unter /api/inventory-sessions: Beim Start entsteht ein Snapshot der erwarteten Assets, danach wird per QR/Barcode erfasst und am Ende gegen das Soll ausgewertet. Sie tragen eigene Rechte (inventory.*), eine Frist samt Erinnerungen und einen Papierkorb. Endpunkte, Felder und Rechte stehen vollständig auf der eigenen Seite: Inventory API.

Die Inventur-Routen bewegen selbst keine Assets — Aktionen an gescannten, unerwarteten oder fehlenden Geräten laufen über die Asset-Endpunkte dieser Seite (checkout, checkin, install, deinstall) und über die assets.*-Rechte.

Asset-Typen (CRUD + Policies)

Method Endpoint Beschreibung
GET/api/asset-typesAlle Asset-Typen
POST/api/asset-typesAsset-Typ erstellen (inkl. Policies)
PATCH/api/asset-types/:idAsset-Typ aktualisieren
DELETE/api/asset-types/:idAsset-Typ löschen
GET/api/asset-types/:id/permissionsTyp-Berechtigungen abrufen
PUT/api/asset-types/:id/permissions/users/:userIdUser-Berechtigung setzen
PUT/api/asset-types/:id/permissions/roles/:roleIdRollen-Berechtigung setzen

Asset-Typ-Felder (POST/PATCH)

FeldTypBeschreibung
name / displayNameStringInterner Name (unique) / Anzeigename
trackingModeEnum (Default PERSON)PERSON | LOCATION | CONSUMABLE. Legt u. a. fest, ob der Typ Verbrauchsmaterial ist. Unveränderlich, sobald der Typ Assets hat → sonst HTTP 409.
standaloneBoolean (Default true)false = Einbau-Komponente (RAM/SSD): keine eigenständige Ausgabe/Handover, folgt dem Container via Co-Move. CONSUMABLE ist immer standalone (erzwungen). Frei umschaltbar (audit-pflichtig).
requiresConfirmationBoolean (Default false)Handover mit Empfänger-Bestätigung als Typ-Default.
hasTypePermissionsBooleanTyp-Sperre: aktiviert typbezogene Berechtigungen (siehe unten).
icon / color / customFieldSchemaString / JSONBUI-Icon, Farbe, Custom-Field-Schema (JSON).
Semantik der Modi (Anker, Aktionen, Statusmengen) siehe Asset-Lebenszyklus.

Kategorien & Standorte (CRUD)

Method Endpoint Beschreibung
GET/api/asset-categoriesAlle Kategorien inkl. Hierarchie (parentId); Recht: assets.viewAll oder manageCategories
POST/api/asset-categoriesKategorie erstellen
PATCH/api/asset-categories/:idKategorie aktualisieren
DELETE/api/asset-categories/:idKategorie löschen
GET/api/asset-locationsAlle Standorte inkl. Hierarchie (parentId); Recht: assets.viewAll oder manageLocations
POST/api/asset-locationsStandort erstellen
PATCH/api/asset-locations/:idStandort aktualisieren
DELETE/api/asset-locations/:idStandort löschen

Asset-Status

Es gibt 11 Lebenszyklus-Status. Vollständige Semantik, Status-Gruppen, Anker-Regeln und die Übergangsmatrix stehen auf der Seite Asset-Lebenszyklus-Seite.

Status Beschreibung
ORDEREDBestellt, noch nicht geliefert
RECEIVEDGeliefert, noch nicht einsatzbereit
AVAILABLEVerfügbar/einsatzbereit (Lager), frei zuweisbar
RESERVEDVorgemerkt — noch keine Ausgabe
PENDING_ACCEPTANCEPersonen-Handover läuft, wartet auf Empfänger-Bestätigung
IN_USEIn Betrieb — PERSON: beim User / LOCATION: am Standort installiert
MAINTENANCEIn Wartung/Instandsetzung (Anker kann bleiben)
RETURN_PENDINGRückgabe läuft, wartet auf IT-Bestätigung
RETIREDAußer Betrieb (reaktivierbar)
LOSTVerloren/gestohlen (Pflicht-Grund in statusNote)
DISPOSEDEntsorgt/Verkauft (final)

Asset erstellen

Reguläres Asset (z.B. Laptop)

POST /api/assets
{
  "name": "Dell XPS 15",
  "description": "Developer laptop with 32GB RAM",
  "serialNumber": "SN123456789",
  "manufacturer": "Dell",
  "model": "XPS 15 9520",
  "typeId": "clx-laptop-type",
  "categoryId": "clx-hardware-category",
  "locationId": "clx-office-munich",
  "status": "RECEIVED",
  "criticality": "HIGH",

  "purchaseDate": "2026-01-15",
  "purchasePrice": 2499.00,
  "purchaseOrder": "PO-2026-001",
  "vendor": "Dell Direct",
  "costCenterId": "clx-cost-center-id",

  "warrantyEnd": "2029-01-15",
  "usefulLifeMonths": 36,
  "depreciationMethod": "LINEAR",

  "customFields": {
    "ramGB": 32,
    "storageGB": 1024,
    "cpu": "Intel i7-12700H",
    "display": "15.6\" 4K OLED"
  },

  "tags": ["developer", "high-performance", "mobile"]
}

Consumable (Mengenartikel, z.B. USB-Kabel)

Ein Consumable entsteht durch einen Asset-Typ mit trackingMode=CONSUMABLE (eine Eigenschaft des Typs, nicht des einzelnen Assets). Das Asset trägt quantity/minQuantity; ausgegeben wird über ConsumableAssignment, nicht über Status/Assignee.

{
  "name": "USB-C to USB-A Cable (1m)",
  "typeId": "clx-consumable-type",
  "categoryId": "clx-cables-category",
  "status": "RECEIVED",
  "quantity": 50,
  "minQuantity": 10,

  "purchasePrice": 5.99,
  "purchaseOrder": "PO-2026-002",

  "tags": ["cable", "usb-c", "consumable"]
}

Consumables: Der Typ (trackingMode=CONSUMABLE) trackt quantity; SerialNumber ist optional. Ausgabe dekrementiert die Menge via ConsumableAssignment (User ODER Standort) statt Status/Assignee zu ändern. Erlaubte Status: ORDERED, RECEIVED, AVAILABLE, RETIRED, DISPOSED. Kein Handover, keine CMDB-Relations.

Response (201 Created)

{
  "id": "clx...",
  "assetTag": "00042",
  "name": "Dell XPS 15",
  "serialNumber": "SN123456789",
  "manufacturer": "Dell",
  "model": "XPS 15 9520",
  "status": "RECEIVED",
  "type": {
    "id": "clx...",
    "name": "Laptop",
    "displayName": "Laptop"
  },
  "category": {
    "id": "clx...",
    "name": "Hardware",
    "color": "#3b82f6"
  },
  "location": {
    "id": "clx...",
    "name": "Munich Office - 2nd Floor"
  },
  "createdAt": "2026-01-27T17:00:00.000Z"
}

QR/PDF-Handover-Workflow

Der Handover ist der formelle Ausgabe-Weg mit Empfänger-Bestätigung (→ PENDING_ACCEPTANCE), QR-Code, PDF-Protokoll und Mail. Er gilt NUR für trackingMode=PERSON (LOCATION nutzt Install, CONSUMABLE hat keinen Handover) und unterliegt denselben Prüfungen wie Checkout/Checkin. Annehmen → IN_USE; Ablehnen oder Abbrechen stellt den vorherigen Zustand wieder her. Der Direkt-Checkout (siehe unten) ist der schnelle Weg ohne Bestätigung.

Schritt 1: Handover erstellen (IT)

POST /api/assets/handovers
{
  "recipientId": "clx-user-id",
  "assetIds": ["clx-asset1", "clx-asset2"],
  "quantities": { "clx-asset1": 1 },
  "note": "New laptop for developer onboarding",
  "expiresAt": "2026-12-31T23:59:59Z"
}

Handover-Typ & Policy: Der Handover-Typ (CHECKOUT_WITH_CONFIRMATION, CHECKOUT_DIRECT, RETURN, RETURN_DIRECT) und eine ggf. erforderliche Policy-Bestätigung werden serverseitig aus der Asset-/Typ-Konfiguration abgeleitet — nicht im Request gesetzt. quantities ist optional (Mengenartikel/Consumables).

Response (201 Created)

{
  "id": "clx...",
  "handoverNumber": "HO-00042",
  "type": "CHECKOUT_WITH_CONFIRMATION",
  "status": "PENDING",
  "qrUrl": "https://your-domain.com/handover/clx.../verify?token=abc123...",
  "pdfUrl": "/api/assets/handovers/clx.../pdf",
  "initiatedBy": { "name": "IT Admin" },
  "recipient": { "name": "John Doe", "email": "john@example.com" },
  "items": [
    {
      "asset": {
        "assetTag": "00042",
        "name": "Dell XPS 15",
        "serialNumber": "SN123456789"
      }
    }
  ],
  "initiatedAt": "2026-01-27T17:00:00.000Z"
}

Schritt 2: User scannt QR-Code

Der QR-Code enthält eine URL mit HMAC-Token für sicheren Zugriff OHNE Authentifizierung:

# PUBLIC endpoint (no JWT needed!)
GET /api/handovers/:id/public?token=HMAC_TOKEN
{
  "handoverNumber": "HO-00042",
  "type": "CHECKOUT_WITH_CONFIRMATION",
  "status": "PENDING",
  "initiatedBy": { "name": "IT Admin" },
  "recipient": { "name": "John Doe" },
  "items": [
    {
      "asset": {
        "assetTag": "00042",
        "name": "Dell XPS 15",
        "serialNumber": "SN123456789",
        "type": { "name": "Laptop" }
      }
    }
  ],
  "policies": [
    { "id": "clx...", "version": 3, "title": "Laptop Usage Policy", "acceptedAt": "2026-01-27T17:15:00.000Z" }
  ],
  "companyName": "Your Company"
}

Security: Der Public-Endpoint gibt KEINE internen IDs, E-Mail-Adressen oder sensiblen Daten zurück. Nur minimale Informationen für Verifizierung.

Schritt 3: User akzeptiert Handover

POST /api/assets/handovers/:id/accept
{
  "note": "Asset received in good condition. All components present.",
  "policyAccepted": true,
  "policyLinkOpened": true
}

Response

{
  "id": "clx...",
  "handoverNumber": "HO-00042",
  "status": "CONFIRMED",
  "confirmedAt": "2026-01-27T17:15:00.000Z",
  "confirmedBy": { "name": "John Doe" },
  "recipientNote": "Asset received in good condition..."
}

Richtlinien werden je Version quittiert: Eine Übergabe kann Geräte mehrerer Asset-Typen enthalten — die Bestätigung quittiert daher jede aktive Richtlinie der beteiligten Typen. Die Antworten tragen dazu policies[] (Listen: id, version, title, acceptedAt; ausstehende Übergaben und das Detail zusätzlich den Volltext und den Quittungs-Stand des Empfängers je Version). Protokoll, PDF und die öffentliche Seite listen alle quittierten Richtlinien mit Version und Zeitpunkt. Bereits quittierte Versionen muss derselbe Empfänger nicht erneut abhaken.

Erscheint später eine neue Version einer Richtlinie, werden die Empfänger laufender Übergaben dieses Typs benachrichtigt (In-App und E-Mail). Die offenen Fassungen liefert GET /api/assets/handovers/policy-updates; quittiert werden sie über POST /api/assets/handovers/:id/reaccept-policy — das verlangt assets.confirmReceipt, betrifft nur bestätigte Übergaben und quittiert alle offenen Versionen in einem Vorgang. Welche Fassung gilt, bestimmt ausschließlich der Server.

Automatisch: Asset-Status wechselt zu IN_USE. assignedToId (Empfänger) bleibt gesetzt, deployedAt wird gesetzt.

Schritt 4: PDF-Protokoll generieren

GET /api/assets/handovers/:id/pdf

Generiert PDF-Protokoll mit QR-Code, Asset-Details, Unterschriften (digital), Policy-Text. Das erwartete Rückgabedatum stammt aus dem Protokoll selbst — ein später geändertes Datum am Asset verändert ein altes Protokoll nicht.

Rückgabedatum ändern

PATCH /api/assets/handovers/:id/expires-at
{
  "expiresAt": "2027-06-30T12:00:00Z",
  "reason": "Projektlaufzeit verlaengert"
}

Gilt für Ausgabe-Protokolle im Status PENDING oder ACCEPTED. expiresAt=null macht die Ausgabe unbefristet; ein Datum muss in der Zukunft liegen (sonst 400 VALIDATION_ERROR). reason ist optional. Bei einer bestätigten Ausgabe wandert das Datum zugleich an die enthaltenen Assets (expectedCheckinAt), offene Erinnerungen zum alten Termin verfallen und der Empfänger wird benachrichtigt (In-App und E-Mail).

Ein Rückgabe-Protokoll hat kein Rückgabedatum — der Aufruf antwortet dort 400 HANDOVER_EXPIRY_NOT_APPLICABLE; jeder andere Status als PENDING oder ACCEPTED ergibt 409 HANDOVER_ALREADY_PROCESSED. Erlaubt ist der Aufruf mit assets.initiateHandover oder — bei gesperrten Typen — mit der Typ-Freigabe für das Initiieren von Übergaben, und zwar für jeden beteiligten Asset-Typ; nicht nur der ursprüngliche Aussteller darf ändern.

Im Verlauf des Assets steht die Änderung als ein Wertepaar „erwartetes Rückgabedatum: alt → neu" im Format des Benutzers, dazu die Übergabe-Nummer und — falls angegeben — der Grund. Der Audit-Eintrag trägt dasselbe Wertepaar.

Model-Clustering (Duplikat-Erkennung)

Eviworx erkennt automatisch Duplikate in Manufacturer/Model-Schreibweisen und schlägt Bereinigung vor.

Wie funktioniert Clustering?

  1. Erkennung: CronJob läuft periodisch (z.B. täglich)
  2. Analyse: Ähnliche Manufacturer/Model-Kombinationen werden gefunden (String-Similarity)
  3. Cluster: Varianten werden gruppiert (z.B. "DELL", "Dell", "dell")
  4. Review: Admin prüft Cluster und setzt canonical-Werte
  5. Merge: Alle Assets im Cluster bekommen canonical-Werte

Cluster abrufen

GET /api/asset-model-clusters?status=PENDING

Response

{
  "data": [
    {
      "id": "clx...",
      "canonicalManufacturer": null,
      "canonicalModel": null,
      "variants": [
        { "manufacturer": "DELL", "model": "XPS 15", "count": 15 },
        { "manufacturer": "Dell", "model": "XPS 15", "count": 23 },
        { "manufacturer": "dell", "model": "xps 15", "count": 5 }
      ],
      "assetCount": 43,
      "variantCount": 3,
      "status": "PENDING",
      "detectedAt": "2026-01-27T08:00:00.000Z"
    }
  ],
  "pagination": { "page": 1, "limit": 25, "total": 12, "totalPages": 1, "hasMore": false }
}

Die Zähler je Status liefert GET /api/asset-model-clusters/stats als Objekt status → { count, assetCount }.

Canonical-Werte setzen

PATCH /api/asset-model-clusters/:id/canonical
{
  "canonicalManufacturer": "Dell",
  "canonicalModel": "XPS 15"
}

Cluster mergen

POST /api/asset-model-clusters/:id/merge

Automatisch: Alle 43 Assets bekommen manufacturer="Dell" und model="XPS 15". Cluster-Status → MERGED.

Cluster ablehnen (kein Duplikat)

POST /api/asset-model-clusters/:id/reject

Cluster-Status → REJECTED. Der Cluster erscheint danach nicht unter den offenen Duplikat-Kandidaten.

CMDB-Relations, Graph & Impact

Assets werden als Configuration Items über typisierte Beziehungen verknüpft. Jede Beziehung wird einmal gespeichert und ist von beiden Assets aus sichtbar; eine Beziehung, die es in Gegenrichtung schon gibt, wird als Duplikat abgelehnt.

Endpunkte

MethodEndpointBeschreibung
POST/api/asset-relationsRelation erstellen
DELETE/api/asset-relations/:relationIdRelation löschen
GET/api/asset-relations/asset/:assetIdRelationen eines Assets
GET/api/asset-relations/asset/:assetId/graph?depth=1..3Transitiver Beziehungs-Graph
GET/api/asset-relations/typesVerfügbare Relation-Typen
GET/api/assets/:id/impact?targetStatus=XImpact-/Dependency-Analyse (read-only)
POST/api/assets/:id/impact/applyGeführte Mit-Aktualisierung der Nachbarn
GET/api/assets/:id/impact/recoveryRecovery beim Wartungsende
POST/api/assets/impact/batchAggregierter Impact über eine Bulk-Auswahl
GET/api/assets/:id/containmentVerbaute Komponenten (transitiv)

Relation-Typen

  • CONNECTED_TO – Bidirektional: physisch verbunden (Laptop ↔ Monitor)
  • INSTALLED_ON – Software auf Hardware
  • PART_OF – Komponente ist Teil von (RAM → Server)
  • DEPENDS_ON – Funktionale Abhängigkeit (VM → Host)
  • DOCKING_STATION – Bidirektional: Laptop-Docking
  • BACKUP_OF – Backup-/Redundanz-Beziehung
  • REPLACES – Ersetzt (Hardware-Tausch)
  • OTHER – Sonstiges mit Freitext

Relation erstellen

POST /api/asset-relations
{
  "parentAssetId": "clx-laptop-id",
  "childAssetId": "clx-monitor-id",
  "relationType": "CONNECTED_TO",
  "description": "DisplayPort cable"
}

Zuerst wird geprüft, ob der Aufrufer beide Assets sehen und bearbeiten darf, erst danach die fachlichen Regeln — so verraten Fehlermeldungen nichts über fremde Assets. Für jedes der beiden Assets entsteht ein Audit-Eintrag. Abgelehnt werden: Selbst-Referenz, Duplikat (auch reverse), gelöschte Enden, CONSUMABLE-Enden (Verbrauchsmaterial ist kein CI) sowie Containment-Zyklen (PART_OF/INSTALLED_ON in die Gegenrichtung → ASSET_RELATION_CYCLE, HTTP 409).

Beziehungs-Graph

GET /api/asset-relations/asset/:assetId/graph?depth=1..3

Beziehungs-Graph über 1–3 Ebenen, nach Ebenen angeordnet. Assets, die der Aufrufer nicht sehen darf, erscheinen ohne Details. Beziehungen anlegen und löschen ist in der Oberfläche nur in der Desktop-Ansicht möglich.

Impact-/Dependency-Awareness

Beim Statuswechsel eines Assets zeigt die Impact-Analyse die transitiv betroffenen Nachbarn — unter Beachtung von Richtung und Beziehungstyp (PART_OF wirkt nur in eine Richtung), bis zu 10 Ebenen weit; nicht sichtbare Assets erscheinen ohne Details. Betroffene Nachbarn werden nur geändert, wenn sie im Apply-Schritt bestätigt werden.

GET /api/assets/:id/impact?targetStatus=MAINTENANCE
{
  "impacted": [
    { "assetId": "clx...", "assetTag": "00099", "name": "App-Server", "distance": 1, "propagates": true }
  ],
  "counts": { "total": 1, "propagating": 1, "hints": 0, "redacted": 0 }
}

Geführte Mit-Aktualisierung (je Asset gilt das Bearbeitungsrecht; einzelne Assets können scheitern, ohne die übrigen aufzuhalten):

POST /api/assets/:id/impact/apply
{
  "items": [ { "assetId": "clx...", "version": 3, "status": "MAINTENANCE" } ],
  "reason": "Host in Wartung — abhängige VMs mit"
}

Weiter: GET /api/assets/:id/impact/recovery liefert Nachbarn in MAINTENANCE, die beim Wartungsende dieses Assets reaktiviert werden können; POST /api/assets/impact/batch { assetIds, targetStatus } aggregiert den Impact über eine Bulk-Auswahl (read-only, mit counts.inSelection). Die Mit-Aktualisierung selbst erfolgt immer vom einzelnen Asset aus.

Verbaute Komponenten (Containment & Co-Move)

Ein Asset-Typ mit standalone=false ist eine Einbau-Komponente (RAM/SSD/PCIe): keine eigenständige Ausgabe/Handover — sie folgt ihrem Container. GET /api/assets/:id/containment liefert die transitiv verbauten Komponenten (über PART_OF/INSTALLED_ON; nicht sichtbare Komponenten werden nur mitgezählt).

  • Co-Move / Co-Locate / Co-Return: checkout, checkin, install, deinstall, confirmReturn und PATCH /api/assets/:id (Standortwechsel) nehmen die Komponenten in einem Schritt mit (coMoveAssetIds / coLocateAssetIds / coReturnAssetIds — Items im selben Protokoll bzw. am selben Standort). Fehler: CO_ITEM_NOT_CONTAINED / CO_ITEM_INVALID_STATE.
  • Co-LOST: beim „Container verloren melden" werden die verbauten Komponenten optional mit als verloren gemeldet (Default angehakt, derselbe Pflicht-Grund) — per API ist das für jede Komponente ein eigener Aufruf nach dem Container.
  • Inventory-Auto-Account: eine nicht gescannte Komponente gilt als erfasst, wenn ihr Container in der Session gescannt wurde. Details auf der Seite Inventory API.

Verknüpfungs-Regeln: Assets außer Betrieb (RETIRED/LOST/DISPOSED) können nicht neu mit Lizenzen/Verträgen verknüpft werden (Entfernen bleibt immer erlaubt); Ausmustern und Entsorgen sind blockiert, solange aktive Vertrags-/Lizenz-Verknüpfungen bestehen. Asset↔Asset-Beziehungen zu Assets außer Betrieb bleiben bewusst erlaubt (CMDB-Historie). Verbrauchsmaterial (CONSUMABLE) ist von CMDB-Beziehungen ausgeschlossen.

Typbezogene Berechtigungen

Berechtigungen lassen sich pro Asset-Typ festlegen (z. B. dürfen nur bestimmte Rollen Laptops ausgeben).

Permission erstellen

Typ-ID und Rollen-/User-ID stehen im Pfad; der Body enthält nur die Capability-Flags:

PUT /api/asset-types/:id/permissions/roles/:roleId
{
  "canView": true,
  "canCreate": true,
  "canEdit": true,
  "canCheckout": true,
  "canCheckin": true,
  "canDelete": true,
  "canInitiateHandover": true
}

Oder für spezifischen User:

PUT /api/asset-types/:id/permissions/users/:userId
{
  "canView": true,
  "canEdit": true,
  "canDelete": false
}

Permission-Check

Beim Asset-Zugriff prüft das System automatisch:

  1. Globale Asset-Permissions (RBAC)
  2. Typbezogene Berechtigungen (falls vorhanden)
  3. User-spezifische Overrides (höchste Priorität)

Inventur-Scan (Bulk-Upload)

Für Hardware-Inventuren können Assets via Barcode/QR-Scanner erfasst werden.

Zwei Mechanismen: Einzel-Lookup eines gescannten Codes via GET /api/assets/scan und die vollständige, mehrstufige Inventur via Inventur-Sessions (siehe Abschnitt „Inventur-Sessions" oben).

Einzel-Lookup (Code → Asset)

GET /api/assets/scan?code=00042

Löst einen gescannten assetTag oder eine Seriennummer zum Asset auf (z.B. um es während der Inventur in eine Session aufzunehmen). Erfordert die Permission assets.scan.

Scan in eine Inventur-Session

POST /api/inventory-sessions/:id/scan
{
  "code": "00042",
  "quantity": 1
}

Entweder code (der gescannte QR-/Barcode-Wert) oder assetId — eines von beiden ist Pflicht. quantity ist optional (Default 1) und zählt bei Verbrauchsmaterial.

lastSeenAt: Wird bei jedem Scan aktualisiert. Assets ohne lastSeenAt in den letzten X Monaten können als "vermisst" markiert werden.

Locations & Categories

Locations (Standorte)

GET /api/asset-locations
{
  "data": [
    {
      "id": "clx...",
      "name": "Munich Office - 2nd Floor",
      "address": "Sample Street 1, 80333 Munich",
      "building": "Main Building",
      "floor": "2",
      "room": "201",
      "isActive": true,
      "assetCount": 42
    }
  ]
}

Categories (Kategorien)

GET /api/asset-categories
{
  "data": [
    {
      "id": "clx...",
      "name": "Hardware",
      "description": "Physical hardware devices",
      "color": "#3b82f6",
      "isActive": true,
      "assetCount": 156
    },
    {
      "id": "clx...",
      "name": "Software",
      "description": "Software licenses and subscriptions",
      "color": "#8b5cf6",
      "isActive": true,
      "assetCount": 89
    }
  ]
}

Checkout / Checkin & Install / Deinstall (Flow-Endpunkte)

Zuweisungen laufen ausschließlich über diese Endpunkte — ein PATCH, der assignedToId zusammen mit dem Wechsel nach IN_USE setzt, wird mit ASSET_ASSIGN_VIA_FLOW_ONLY abgelehnt. PERSON-Assets nutzen Checkout/Checkin, LOCATION-Assets Install/Deinstall. Jeder Endpunkt schreibt einen Aktivitätseintrag und prüft die Anker-Regeln (siehe Asset-Lebenszyklus).

Checkout (PERSON → IN_USE)

POST /api/assets/:id/checkout
{
  "userId": "clx-user-id",
  "expectedCheckin": "2026-12-31",
  "note": "Issued for home office setup",
  "coMoveAssetIds": ["clx-ram-id", "clx-ssd-id"]
}

Setzt status=IN_USE + assignedToId (CHECKOUT_DIRECT). Quelle: AVAILABLE, RESERVED oder MAINTENANCE (user-los). Externe Empfänger statt userId: externalFirstName / externalLastName / externalEmail (legt einen END_USER an + Mail). coMoveAssetIds nimmt verbaute Komponenten im selben Protokoll mit. expectedCheckin ist das erwartete Rückgabedatum; es steht am Asset (expectedCheckinAt) und am erzeugten Ausgabe-Protokoll und lässt sich dort später ändern (siehe Handover-Workflow).

Checkin (Rückgabe → AVAILABLE | MAINTENANCE | RETIRED)

POST /api/assets/:id/checkin
{
  "status": "AVAILABLE",
  "note": "Returned in good condition",
  "damageReport": "Minor scratch on lid",
  "coReturnAssetIds": ["clx-ram-id"]
}

Leert assignedToId, checkedOutAt, expectedCheckinAt, checkoutNote und deployedAt. status Default AVAILABLE (erlaubt: AVAILABLE, MAINTENANCE, RETIRED); LOST/DISPOSED laufen nicht über Checkin. Beim Ziel RETIRED gelten die Regeln fürs Ausmustern (blockiert bei aktiven Vertrags-/Lizenz-Verknüpfungen). damageReport wird am Handover-Record gespeichert (Overview-Karte).

Install (LOCATION → IN_USE)

POST /api/assets/:id/install
{
  "locationId": "clx-serverroom-b12",
  "note": "Rack 4, Slot 12",
  "coLocateAssetIds": []
}

Nur für trackingMode=LOCATION. Setzt status=IN_USE + locationId (kein User), deployedAt=now. Kein Handover, keine Mail — reine IT-Aktion mit Activity-Log.

Deinstall (LOCATION → AVAILABLE | MAINTENANCE)

POST /api/assets/:id/deinstall
{
  "status": "AVAILABLE",
  "keepLocation": false,
  "note": "Decommissioned"
}

Leert deployedAt und (Default) locationId; keepLocation=true lässt den Standort stehen (Gerät vor Ort, außer Betrieb). status AVAILABLE (default) oder MAINTENANCE.

Formelle Übergabe: Für Übergaben mit Empfänger-Bestätigung, PDF/QR und Mail dient der Handover-Workflow (nur PERSON, siehe oben). Feldbelegung & Anker-Regeln: Asset-Lebenszyklus.

Kaufdaten & Abschreibung

Felder

Feld Typ Beschreibung
purchaseDateDateTimeKaufdatum
purchasePriceDecimalKaufpreis
purchaseOrderStringBestellnummer
vendorStringLieferant
warrantyEndDateTimeGarantieende
maintenanceEndDateTimeWartungsende
bookValueDecimal (berechnet)Aktueller Buchwert — nur lesbar, bei jedem Abruf aus purchasePrice − Abschreibung berechnet
depreciationMethodStringLINEAR, DEGRESSIVE, NONE
usefulLifeMonthsIntNutzungsdauer in Monaten
depreciationStartDateDateTimeAbschreibungs-Start

Währung & Brutto/Netto: Assets speichern nur einen einzelnen purchasePrice (Decimal) — es gibt kein währungs- oder Brutto/Netto-Feld pro Asset. Die Systemwährung (general settings: systemCurrency, Default EUR, keine Umrechnung) und der Preismodus (priceTaxMode: net | gross, Default net — reine Kennzeichnung, keine Steuerberechnung) werden global in den Allgemeinen Einstellungen konfiguriert und auf alle Beträge angewendet. Siehe Settings & Global Search API.

Beispiel: Abschreibung

{
  "purchaseDate": "2026-01-15",
  "purchasePrice": 2499.00,
  "depreciationMethod": "LINEAR",
  "usefulLifeMonths": 36,
  "depreciationStartDate": "2026-01-15"
}

Berechnung: Bei linearer Abschreibung über 36 Monate: Monatliche Abschreibung = €2.499 / 36 = €69,42. Nach 12 Monaten: bookValue = €1.665,96.

Custom Fields

Felder, die je Asset-Typ im customFieldSchema definiert sind, werden im Objekt customFields gespeichert:

Beispiel: Laptop

{
  "customFields": {
    "ramGB": 32,
    "storageGB": 1024,
    "storagetype": "NVMe SSD",
    "cpu": "Intel i7-12700H",
    "display": "15.6\" 4K OLED",
    "gpu": "NVIDIA RTX 3050 Ti",
    "battery": "86 Wh",
    "weight": "2.0 kg"
  }
}

Beispiel: Server

{
  "customFields": {
    "rackUnit": "42U",
    "position": "Rack A, U15-U18",
    "cpuCores": 32,
    "ramGB": 128,
    "storageType": "SAS RAID 10",
    "networkPorts": 4,
    "ipAddress": "192.168.1.100",
    "powerSupply": "Redundant 800W"
  }
}

Filtern: Nach jedem Custom Field lässt sich in der Asset-Liste filtern: f.customField.<key>=<operator>:<wert> mit den Operatoren eq, neq, contains, isNull und isNotNull (z. B. f.customField.cpu=contains:i7). Die Volltextsuche q durchsucht Asset-Tag, Name, Seriennummer und Beschreibung, nicht die Custom Fields.

Sensible Custom-Fields

Ein Feld kann im customFieldSchema als sensitive markiert werden (z.B. Zugangsdaten, Schlüssel). Sensible Werte werden in ALLEN Lese-Pfaden (Detail, Liste, Create-Prefill) sowie in Activity-/Audit-Einträgen mit einer festen Maske überschrieben — der Klartext verlässt die normale Antwort nie.

MethodEndpointBeschreibung
GET/api/assets/:id/custom-fields/:field/revealKlartext EINES sensiblen Feldes; Recht: Sicht auf das Asset (viewAll/viewOwn oder canView des Asset-Typs); jeder Abruf wird protokolliert (wie /licenses/:id/key)

Speichern sensibler Felder: Bei einem sensiblen Feld gilt: Key weglassen = Bestand bleibt, null = leeren, den Masken-Wert zurücksenden = 400. So kann eine maskierte Anzeige nie versehentlich als echter Wert gespeichert werden.

Assets abrufen (Liste)

Request

GET /api/assets?f.status=IN_USE&f.typeId=clx-laptop&per=50

Query Parameters

Parameter Beschreibung
qVolltextsuche (Asset-Tag, Name, Seriennummer, Beschreibung)
f.statusNach Status filtern, z. B. f.status=in:AVAILABLE,IN_USE
f.typeId / f.categoryId / f.locationIdNach Asset-Typ, Kategorie oder Standort filtern
f.assignedToIdNach zugewiesenem Benutzer filtern
f.criticalityNach Kritikalität filtern (LOW, MEDIUM, HIGH, CRITICAL)
f.manufacturer / f.modelNach Hersteller oder Modell filtern
f.type.trackingModeNach Tracking-Modus filtern (PERSON, LOCATION, CONSUMABLE)
f.customField.<key>Nach einem Custom Field filtern (siehe Custom Fields)
page / perSeite und Seitengröße (per Default 25, max. 100)
sortSortierung, z. B. sort=createdAt:desc
mine / myTeam / myDepartment=1: nur Assets des Aufrufers, seines Teams bzw. seiner Abteilung
lowStock=1: nur Verbrauchsmaterial auf oder unter dem Mindestbestand
notLinkedToLicenseIdLizenz-ID: nur Assets, die dieser Lizenz nicht zugewiesen sind — die Kandidatenliste für eine Lizenz-Zuweisung
deleted=1: Papierkorb (Recht assets.viewDeleted)

Lifecycle-Management

Assets tracken ihren kompletten Lifecycle:

Feld Beschreibung
createdAtErstellt in System
deployedAtErstmals in Betrieb genommen
retiredAtAußer Betrieb genommen
disposedAtEntsorgt/Verkauft
deletedAtSoft-Delete (Papierkorb)
🔍
Model-Clustering
Erkennt „DELL“/„Dell“/„dell“ als Duplikate.
📄
QR/PDF-Handover
Öffentlicher QR-Link mit signiertem Token, PDF-Protokoll, Richtlinien-Bestätigung.
🔗
CMDB-Beziehungen
8 Beziehungstypen, von beiden Seiten sichtbar.

Code-Beispiel: Kompletter Handover-Flow

// ===================================================
// ASSET HANDOVER - FROM CREATION TO CONFIRMATION
// ===================================================

const API_URL = 'https://your-instance.com/api';
// Auth via HttpOnly Cookies (credentials: 'include')

// 1. IT creates handover (issue laptop)
const handover = await fetch(`${API_URL}/assets/handovers`, {
  method: 'POST',
  credentials: 'include', // HttpOnly cookie auth
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    recipientId: 'clx-john-doe',
    assetIds: ['clx-laptop-id'],
    note: 'Laptop for new developer',
    expiresAt: '2026-12-31T23:59:59Z'
  })
}).then(r => r.json());

console.log('Handover created:', handover.handoverNumber); // HO-00042
console.log('QR URL:', handover.qrUrl);

// 2. Generate PDF protocol
const pdfBlob = await fetch(
  `${API_URL}/assets/handovers/${handover.id}/pdf`,
  {
    credentials: 'include' // HttpOnly cookie auth
  }
).then(r => r.blob());

// Save or print PDF
// PDF contains QR code + asset details + policy text

// 3. User scans QR code (opens handover.qrUrl in browser)
// NO authentication required! HMAC token in URL
const publicData = await fetch(handover.qrUrl)
  .then(r => r.json());

console.log('Public handover data:', publicData);
// Shows: Asset details, initiator name, policy text
// NO internal IDs, NO emails

// 4. User confirms handover (in UI after QR scan)
const confirmed = await fetch(
  `${API_URL}/assets/handovers/${handover.id}/accept`,
  {
    method: 'POST',
    credentials: 'include',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      note: 'Laptop received. All components present.',
      policyAccepted: true
    })
  }
).then(r => r.json());

console.log('Handover confirmed:', confirmed.handoverNumber);
console.log('Status:', confirmed.status); // CONFIRMED

// Asset status is now IN_USE
// assignedToId = John Doe
// checkedOutAt = now

Code-Beispiel: Model-Clustering

// ===================================================
// MODEL CLUSTERING - DEDUPLICATE MANUFACTURER/MODEL
// ===================================================

// 1. Get pending clusters (detected by CronJob)
const clusters = await fetch(`${API_URL}/asset-model-clusters?status=PENDING`, {
  credentials: 'include'
}).then(r => r.json());

console.log('Pending clusters:', clusters.data.length);

// Example cluster:
// {
//   "variants": [
//     { "manufacturer": "DELL", "model": "XPS 15", "count": 15 },
//     { "manufacturer": "Dell", "model": "XPS 15", "count": 23 },
//     { "manufacturer": "dell", "model": "xps 15", "count": 5 }
//   ],
//   "assetCount": 43,
//   "variantCount": 3,
//   "status": "PENDING"
// }

const cluster = clusters.data[0];

// 2. Set canonical values
await fetch(`${API_URL}/asset-model-clusters/${cluster.id}/canonical`, {
  method: 'PATCH',
  credentials: 'include',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    canonicalManufacturer: 'Dell',
    canonicalModel: 'XPS 15'
  })
});

// 3. Merge cluster (apply canonical values to all 43 assets)
await fetch(`${API_URL}/asset-model-clusters/${cluster.id}/merge`, {
  method: 'POST',
  credentials: 'include'
});

console.log('Merged! All 43 assets now have manufacturer="Dell", model="XPS 15"');

// Cluster status is now MERGED
// All assets updated in single transaction
// Activity log created for each asset

Attachments

Assets nutzen das zentrale Anhang-System für Bestellungen, Garantiebelege, Rechnungen usw.:

# Upload file to asset
POST /api/attachments/ASSET/:assetId

# All attachments of an asset
GET /api/attachments/ASSET/:assetId

# Download
GET /api/attachments/:id/download
Details: Siehe Attachments & File Settings API für Virenscan, Datei-Einstellungen und Aufbewahrungsfristen.
Nächster Schritt

Workflows API → Erfahre mehr über die Workflows API
Entity Linking API → Assets mit Tickets/Problems/Incidents/Changes/Verträgen verknüpfen