Contracts & Licenses API
Die Contracts & Licenses API verwaltet Verträge und Software-Lizenzen mit AES-256-GCM-verschlüsselten License-Keys, TCO-Tracking, Seat-Management, Renewal-Monitoring, Parent-Child-Hierarchien (parentId), Software-Katalog (Publisher/Products), Asset-/User-Verknüpfungen und vollständigem Audit-Trail.
Authentifizierung & Permissions
Alle Endpunkte akzeptieren eine Session oder einen X-API-Key mit Rolle; jede Aktion hat ein eigenes Recht unter contracts.* bzw. licenses.*. Bei Verträgen unterscheidet das Bearbeitungsrecht zwischen allen Verträgen (editAll) und eigenen als Verantwortlicher (editOwn). Bei Lizenzen gilt jedes Recht für alle Lizenzen. Details: User Management & RBAC.
User-Kontext vs. API-Key: Stammdaten-CRUD, Export und Statistiken akzeptieren User UND API-Keys. Zuweisungen/Verknüpfungen (License↔Asset/User, Contract↔Asset), die Activity-History sowie Bulk-Status/Delete verlangen dagegen einen eingeloggten Benutzer — ein API-Key erhält hier 403 ("requires a logged-in user account, not an API key").
Endpoints Übersicht
Contracts
| Method | Endpoint | Beschreibung | Permission |
|---|---|---|---|
GET | /api/contracts | Paginierte Liste ({data, pagination}) | contracts.viewAll/viewOwn |
GET | /api/contracts/:id | Einzelner Vertrag (Objekt direkt, mit ETag) | contracts.viewAll/viewOwn |
GET | /api/contracts/stats | Statistiken (Zähler je Status/Typ, Ablauf, Volumen) | contracts.viewAll/viewOwn |
GET | /api/contracts?deleted=1 | Papierkorb — derselbe Listen-Endpunkt mit deleted=1 (nur dieser Wert, sonst 400). Filter, Suche, Sortierung und gespeicherte Ansichten gelten dort genauso, ebenso die Sichtbarkeitsregeln. | contracts.viewDeleted |
GET | /api/contracts/:id/children | Unterverträge ({data, pagination}) | contracts.viewAll/viewOwn |
GET | /api/contracts/:id/licenses | Verknüpfte Licenses ({data}) | contracts.viewAll/viewOwn |
GET | /api/contracts/:id/activities | Historie des Vertrags ({data, pagination}, ?limit/?offset) | contracts.viewHistory |
POST | /api/contracts/:id/activities | Kommentar hinzufügen → 201 (User-only) | contracts.editAll/editOwn |
GET | /api/contracts/export | CSV/XLSX/PDF | contracts.export |
POST | /api/contracts | Erstellen → 201 | contracts.create |
PATCH | /api/contracts/:id | Aktualisieren (editAll oder editOwn; Status und Verantwortlicher mit eigenem Recht; version Pflicht) | contracts.editAll/editOwn |
DELETE | /api/contracts/:id | Soft-Delete → 204 | contracts.delete |
POST | /api/contracts/:id/restore | Wiederherstellen | contracts.restore + viewDeleted |
PATCH | /api/contracts/bulk/status | Bulk-Status (User-only, max. 100) | contracts.bulkUpdate |
DELETE | /api/contracts/bulk | Bulk-Delete (User-only, max. 100) | contracts.delete |
Sichtbarkeit vor Aktion: contracts.delete und contracts.viewHistory gelten für alle Verträge, unabhängig vom Verantwortlichen. Löschen und Historie prüfen deshalb zusätzlich die Sicht auf genau diesen Vertrag — wer ihn nicht sehen darf, bekommt 404, sodass nicht erkennbar ist, ob er existiert. Im Papierkorb gelten zwei Rechte: contracts.viewDeleted öffnet den Papierkorb, und darin gelten weiter die normalen Sichtbarkeitsregeln (Verantwortlicher + Vorgesetzten-Kette). Wiederherstellen verlangt beide Rechte und die Sicht auf den Vertrag — so zeigen Papierkorb und Wiederherstellung dieselbe Menge. Auch das Massen-Löschen hält sich daran: Ein nicht sichtbarer Vertrag kommt als Fehlzeile mit CONTRACT_NOT_FOUND zurück — bewusst derselbe Code wie für unbekannte IDs, damit die Antwort nicht verrät, welche anderen Verträge existieren.
Kritische Aktionen: contracts.delete, contracts.restore und contracts.export werden bei jeder Nutzung neu geprüft, sodass ein entzogenes Recht sofort wirkt; verweigerte Versuche werden protokolliert.
Contract-Asset-Verknüpfung (User-only)
| Method | Endpoint | Permission |
|---|---|---|
GET | /api/contracts/:id/assets | contracts.viewAll/viewOwn (je Zeile zusätzlich das Sichtrecht auf das Asset — sonst ein Platzhalter) |
POST | /api/contracts/:id/assets | contracts.linkAssets |
PATCH | /api/contracts/:id/assets/:assetId | contracts.linkAssets |
DELETE | /api/contracts/:id/assets/:assetId | contracts.unlinkAssets |
POST | /api/contracts/:id/assets/bulk | contracts.bulkLinkAssets |
Der Assets-Tab zeigt nur, was der Aufrufer auch sehen darf: Wer den Vertrag sehen darf, sieht damit nicht automatisch seine Assets. Jede Zeile trägt entweder das volle Asset oder einen Platzhalter aus id, assetTag, status und restricted: true — etwa bei einem gesperrten Asset-Typ ohne Freigabe. Die Zeile bleibt, damit der Zähler stimmt; Name, Typ, Standort und Zuweisung fehlen, und die Zeile führt nicht ins Asset-Detail.
Eine Verknüpfung hat keinen eigenen Typ; die Vertragsart ergibt sich aus dem contractType des Vertrags. isPrimary gilt PRO ASSET, nicht pro Vertrag: ein Asset hat höchstens einen primären Vertrag — setzt man einen neuen, stuft der Server den bisherigen automatisch herab. Verträge in den Zuständen CANCELLED und EXPIRED nehmen keine neuen Verknüpfungen an.
Für die Liste der verknüpften Assets gelten dieselben Sichtbarkeitsregeln wie für das Detail (inklusive Vorgesetzten-Kette): Wer den Vertrag nicht sehen darf, bekommt 404, sodass nicht erkennbar ist, ob er existiert.
Licenses
| Method | Endpoint | Beschreibung | Permission |
|---|---|---|---|
GET | /api/licenses | Liste (Scope) | licenses.viewAll/viewOwn |
GET | /api/licenses/:id | Einzelne License | licenses.viewAll/viewOwn |
GET | /api/licenses/:id/key | Lizenzschlüssel im Klartext (jeder Abruf wird protokolliert) | licenses.viewKeys |
GET | /api/licenses/stats | Statistiken (Seats, Kosten) | licenses.viewAll/viewOwn |
GET | /api/licenses?deleted=1 | Papierkorb: gelöschte Licenses (?deleted=1 auf der Liste; Einträge im Papierkorb tragen deletedAt/deletedBy) | licenses.viewDeleted |
GET | /api/licenses/export | CSV/XLSX/PDF | licenses.export |
POST | /api/licenses | Erstellen | licenses.create |
PATCH | /api/licenses/:id | Aktualisieren | licenses.update |
DELETE | /api/licenses/:id | Soft-Delete | licenses.delete |
POST | /api/licenses/:id/restore | Wiederherstellen | licenses.restore + viewDeleted |
POST | /api/licenses/:id/link-contract | Mit Contract verknüpfen | licenses.linkToContract |
POST | /api/licenses/:id/unlink-contract | Von Contract trennen | licenses.unlinkFromContract |
POST | /api/licenses/bulk/link-contract | Bulk-Link | licenses.bulkLinkToContract |
POST | /api/licenses/bulk/unlink-contract | Bulk-Unlink | licenses.bulkUnlinkFromContract |
PATCH | /api/licenses/bulk/status | Bulk-Status | licenses.bulkUpdate |
DELETE | /api/licenses/bulk | Bulk-Delete | licenses.delete |
License-Zuweisungen (User-only)
| Method | Endpoint | Permission |
|---|---|---|
GET | /api/licenses/:id/assets | licenses.viewAll/viewOwn |
POST | /api/licenses/:id/assets (+ /bulk) | licenses.assignToAsset |
DELETE | /api/licenses/:id/assets/:assetId (+ /bulk) | licenses.unassignFromAsset |
GET | /api/licenses/:id/users | licenses.viewAll/viewOwn |
POST | /api/licenses/:id/users (+ /bulk) | licenses.assignToUser |
DELETE | /api/licenses/:id/users/:userId (+ /bulk) | licenses.unassignFromUser |
Software-Katalog
| Method | Endpoint | Permission |
|---|---|---|
GET | /api/software-publishers (+ /search [q/per], /:id) | licenses.viewAll/viewOwn ‖ manage* |
POST/PATCH/DELETE | /api/software-publishers | licenses.managePublishers |
GET | /api/software-products (+ /:id) | licenses.viewAll/viewOwn ‖ manage* |
POST/PATCH/DELETE | /api/software-products | licenses.manageProducts |
Activities
| Method | Endpoint | Beschreibung |
|---|---|---|
GET / POST | /api/contracts/:id/activities | Verlauf/Kommentar je Vertrag — nur für Benutzer, die den Vertrag sehen dürfen. Übergreifend im Audit-Log. |
GET / POST | /api/licenses/:id/activities | Verlauf/Kommentar je Lizenz — nur für Benutzer, die die Lizenz sehen dürfen. Übergreifend im Audit-Log. |
Verlaufseinträge tragen neben den Feld-Änderungen einen Textschlüssel mit Parametern (bodyKey, bodyParams). Die Oberfläche setzt daraus den Text in der Sprache des Betrachters zusammen, statt einen fest gespeicherten Satz anzuzeigen.
Antwort-Formate
| Antwort | Form |
|---|---|
| Listen (Verträge, Papierkorb, Unterverträge, Historie) | { data, pagination } |
| Sub-Listen (verknüpfte Lizenzen, verknüpfte Assets) | { data } |
| Einzelobjekt und Mutationen | Objekt direkt, ohne Hülle |
| Löschen | 204 ohne Body |
| Massen-Operationen | { processed, failed, errors[] } — je Fehlzeile id, errorCode und message |
| Fehler | { error, errorCode, details? } |
Die Pagination der Vertragsliste trägt page, limit, total, totalPages und hasMore; die Historie blättert dagegen per total, limit und offset. Der Verantwortliche (owner) und der Ersteller kommen in der Liste nur mit ID und Name — die E-Mail-Adresse trägt allein das Detail, wo die Seitenleiste sie anzeigt.
ETag: Das Vertrags-Detail liefert einen ETag und beantwortet ein passendes If-None-Match mit 304. Der Wert deckt neben dem Vertrag selbst auch seine abgeleiteten Zähler (Lizenzen, Assets, Unterverträge) ab — sonst bliebe eine frisch verknüpfte Lizenz im Browser unsichtbar, weil sich der Vertrag selbst nicht geändert hat.
Contract-Typen (contractType)
| Type | Beschreibung |
|---|---|
LICENSE_SUBSCRIPTION | Software-Abo (Microsoft 365, Adobe CC) |
LICENSE_VOLUME | Volumenvertrag (z.B. Microsoft EA) |
MAINTENANCE | Hardware-Wartungsvertrag |
SUPPORT | Support-Vertrag |
SLA | Service Level Agreement |
LEASE | Leasing-Vertrag |
OTHER | Sonstige |
Contract-Status
| Status | Beschreibung |
|---|---|
DRAFT | Entwurf, noch nicht aktiv |
ACTIVE | Aktiv laufend |
EXPIRED | Abgelaufen |
CANCELLED | Gekündigt |
SUSPENDED | Pausiert |
RENEWED | Erneuert |
Erlaubte Status-Übergänge
| Von | Nach |
|---|---|
DRAFT | ACTIVE · CANCELLED |
ACTIVE | SUSPENDED · EXPIRED · CANCELLED · RENEWED |
SUSPENDED | ACTIVE · CANCELLED · EXPIRED |
EXPIRED | RENEWED |
RENEWED | ACTIVE · EXPIRED · CANCELLED |
CANCELLED | — (Endzustand) |
Ein unzulässiger Wechsel endet mit 400 INVALID_CONTRACT_STATUS_TRANSITION. Für den Status-Wechsel braucht es ein eigenes Recht: zusätzlich zur Bearbeitungs-Berechtigung contracts.changeStatus — das gilt auch beim Anlegen, sobald ein anderer Status als DRAFT (der Vorgabewert) gesetzt wird. Ebenso ist das Setzen oder Wechseln des Verantwortlichen (ownerId) an contracts.assign gebunden.
Als „läuft bald ab" gilt ein ACTIVE-Vertrag, dessen Enddatum höchstens 30 Tage entfernt ist — der Ablauftag selbst zählt dazu. Die Liste liefert dafür das abgeleitete Feld isExpiringSoon; als Filter dient das Flag ?expiringSoon=true.
License-Typen (licenseType)
| Type | Beschreibung |
|---|---|
PERPETUAL | Einmalkauf, unbefristet |
SUBSCRIPTION | Abo-Modell (mit Contract verknüpft) |
VOLUME | Volumenlizenzen |
OEM | An Hardware gebunden |
SITE | Standortlizenz (unbegrenzte User) |
USER | Named-User-Lizenz |
DEVICE | Device-Lizenz |
CONCURRENT | Concurrent/Floating |
TRIAL | Testversion |
FREEWARE | Kostenlos |
OPEN_SOURCE | Open Source |
OTHER | Sonstige |
License-Keys werden nur für PERPETUAL und OEM erzwungen — Subscription/Volume sind oft account-basiert ohne klassischen Key.
License-Status
| Status | Beschreibung |
|---|---|
PENDING | Noch nicht aktiviert (Default) |
ACTIVE | Aktiv und nutzbar |
EXPIRED | Abgelaufen |
SUSPENDED | Temporär deaktiviert |
CANCELLED | Gekündigt |
RETIRED | Ausgemustert |
Kosten, Abrechnung & Währung
Contracts: oneTimeCost, recurringCost, totalValue + billingCycle — alle Beträge sind JSON-Zahlen. Licenses: purchasePrice, recurringCost + billingInterval (Int) und billingUnit. costPerSeat ist ein Boolean-Flag (gibt an, ob der Betrag pro Seat oder gesamt gilt) — NICHT der Seat-Preis selbst.
| Feld | Werte |
|---|---|
billingCycle (Contract) | MONTHLY, QUARTERLY, SEMI_ANNUALLY, ANNUALLY, BIENNIAL, TRIENNIAL, ONE_TIME, ON_DEMAND |
billingUnit (License) | WEEK, MONTH, YEAR |
billingInterval (License) | Int (z.B. 1 = pro billingUnit) |
Gesamtwert (totalValue) ist kein Eingabefeld: Der Server berechnet ihn als Einmalkosten plus alle wiederkehrenden Zahlungen über die Vertragslaufzeit (ohne Enddatum: hochgerechnet auf ein Jahr). Ein im Request mitgeschickter Wert wird verworfen. Genau dieser Wert steht in Liste, Detail, Kennzahlen und Export. Er wird beim Anlegen und bei jedem Speichern berechnet; ein Vertrag mit totalValue = null erhält ihn beim nächsten Speichern.
Währung: Weder Contract noch License haben ein eigenes Währungsfeld. Alle Beträge werden in der globalen Systemwährung (general settings: systemCurrency, keine Umrechnung) interpretiert; das gilt auch für Exporte. Siehe Settings & Global Search API. Kostenzuordnung erfolgt über costCenterId (Contract & License) — siehe Cost Centers API.
AES-256-GCM Encryption (License-Keys)
License-Keys werden verschlüsselt in der Datenbank gespeichert:
• AES-256-GCM = Authenticated Encryption (AEAD) - Key: 32 Bytes (256 Bit), hex-encoded (LICENSE_ENCRYPTION_KEY) - IV: 16 Bytes (random pro Encryption) - AuthTag: 16 Bytes (verhindert Tampering) Speicherung in DB: "iv:authTag:encrypted" (hex) Sicherheit:• Key nie in API-Response — nur licenseKeyMasked• Voller Key nur via GET /:id/key (licenses.viewKeys) + Audit-Log (IP, UserAgent)
# Generate key (exactly 32 bytes!)
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
# In docker-compose.yaml:
LICENSE_ENCRYPTION_KEY=46a0bb175f00dadf828a90042bfbb3cada81b385f73162c66059a32035826f0a
KRITISCH: LICENSE_ENCRYPTION_KEY NIEMALS ändern nach ersten Lizenzen — sonst sind alle bestehenden Keys nicht mehr entschlüsselbar. Für Key-Rotation müssten alle Keys re-encrypted werden.
API-Beispiele
Contract erstellen
POST /api/contracts
{
"name": "Microsoft 365 Enterprise Agreement 2026",
"contractNumber": "MS-EA-2026-001",
"contractType": "LICENSE_VOLUME",
"status": "ACTIVE",
"vendor": "Microsoft Corporation",
"publisher": "Microsoft",
"startDate": "2026-01-01",
"endDate": "2027-12-31",
"renewalDate": "2027-10-01",
"autoRenew": true,
"noticePeriodDays": 90,
"billingCycle": "ANNUALLY",
"oneTimeCost": 0,
"recurringCost": 450000,
"purchaseOrder": "PO-2026-0123",
"costCenterId": "clx-cost-center-id",
"budgetCode": "CC-IT-001",
"ownerId": "clx-user-id",
"department": "IT",
"tags": ["microsoft", "office365", "enterprise"]
}
License erstellen (mit Encryption)
POST /api/licenses
{
"name": "Microsoft 365 E5 - Pool License",
"licenseType": "USER",
"status": "ACTIVE",
"publisherId": "clx-publisher-id",
"productId": "clx-product-id",
"productName": "Microsoft 365 E5",
"productVersion": "2026",
"licenseKey": "XXXXX-YYYYY-ZZZZZ-AAAAA-BBBBB",
"quantityPurchased": 500,
"purchasePrice": 22950.00,
"recurringCost": 22950.00,
"billingInterval": 1,
"billingUnit": "MONTH",
"costPerSeat": false,
"contractId": "clx-contract-id",
"costCenterId": "clx-cost-center-id",
"purchaseDate": "2026-01-15",
"expirationDate": "2027-01-14",
"tags": ["microsoft", "office365", "e5"]
}
Hinweis: Der licenseKey wird AES-256-GCM verschlüsselt gespeichert. Responses enthalten nur licenseKeyMasked; den vollen Key liefert GET /api/licenses/:id/key (mit Audit-Logging). publisherId/productId referenzieren den Software-Katalog; productName/productVersion sind freie Felder.
License zu User zuweisen
POST /api/licenses/:id/users
{
"userId": "clx-user-id",
"activationDate": "2026-01-28",
"userEmail": "john.doe@company.com",
"userAccount": "john.doe",
"notes": "Assigned for sales team onboarding"
}
Export
GET /api/contracts/export?format=xlsx&f.status=ACTIVE&expiringSoon=true
Der Export übernimmt Filter und Suche der Liste (Formate csv, xlsx, pdf; Vorgabe csv) und ist auf 10.000 Datensätze begrenzt. Spaltenköpfe, Blattname sowie PDF-Titel und -Fußzeile folgen der Sprache des ausführenden Kontos (Benutzersprache, sonst die Standardsprache der Installation); API-Key- und System-Exporte laufen auf Englisch. Datumswerte in CSV und XLSX sind maschinenlesbar im Format JJJJ-MM-TT, das PDF zeigt sie lokalisiert. Jeder Export wird protokolliert.
Bevorstehende Verlängerungen von Verträgen UND Lizenzen zeigt der Renewal-Kalender der Reports-Fläche. Der Invest-Plan dort rechnet die Kosten aktueller Verträge und Lizenzen einschließlich automatischer Verlängerungen über 1–10 Jahre voraus (Vorgabe 5). Reports API
Hinweise vor dem Ablauf verschickt der mitgelieferte CronJob „Expiry Monitor" (täglich um 07:00, bei Auslieferung deaktiviert): 30, 7, 3 und 0 Tage vor dem Vertragsende (endDate) bzw. dem Ablaufdatum der Lizenz (expirationDate), je Stufe nur einmal. Bei Verträgen geht der Hinweis an den Vertragsverantwortlichen (ownerId). CronJobs API →
Parent-Child Hierarchie
Verträge können über parentId hierarchisch verschachtelt werden (z.B. übergeordneter Volumenvertrag mit Unterverträgen je Produkt). Die Unterverträge eines Vertrags liefert GET /api/contracts/:id/children; gefiltert wird per parentId / hasParent.
// 1. Parent contract
POST /api/contracts
{ "name": "Microsoft EA 2026-2028", "contractType": "LICENSE_VOLUME", "status": "ACTIVE", "startDate": "2026-01-01", "endDate": "2028-12-31", "billingCycle": "ANNUALLY", "recurringCost": 500000 }
// 2. Sub-contract (child)
POST /api/contracts
{ "name": "Microsoft 365 E5 Subscription", "contractType": "LICENSE_SUBSCRIPTION", "parentId": "clx-parent-id", "startDate": "2026-01-01", "endDate": "2027-12-31", "billingCycle": "ANNUALLY", "recurringCost": 225000 }
Filtering
Contract-Filters
| Parameter | Beschreibung |
|---|---|
f.contractType / f.status / f.billingCycle | Enum-Filter (eq/neq/in/notIn) |
f.contractNumber / f.name / f.vendor | Text-Filter (eq/contains/startsWith) |
f.ownerId / f.costCenterId | Verantwortlicher / Kostenstelle (eq/in/isNull/isNotNull) |
f.totalValue / f.recurringCost / f.oneTimeCost | Kosten-Filter (gt/gte/lt/lte/between/isNull/isNotNull) |
f.startDate / f.endDate / f.createdAt / f.updatedAt | Zeit-Filter (gt/gte/lt/lte/between/relative) |
f.autoRenew | Automatische Verlängerung (true/false) |
q | Suche über Nummer, Name, Vendor, Publisher, Beschreibung |
page / per / sort | Paginierung und Sortierung (per Default 25, maximal 200) |
expiringSoon | true = ACTIVE und Enddatum höchstens 30 Tage entfernt |
myTeam / myDepartment | Tab-Flags: eigenes Team bzw. eigene Abteilung |
parentId / hasParent | Hierarchie-Filter |
GET /api/contracts?f.status=in:ACTIVE,SUSPENDED&f.totalValue=gte:10000&f.endDate=relative:next_30_days&sort=endDate:asc&page=1&per=50
Ein Filter hat die Form f.<feld>=<operator>:<wert>; ohne Operator-Präfix gilt Gleichheit (f.status=ACTIVE). Mehrwertige Operatoren nehmen eine Komma-Liste (in:A,B — Kommas im Wert werden kodiert), isNull und isNotNull stehen ohne Wert. Sortiert wird mit sort=<feld>:asc|desc, mehrstufig per Komma.
Liste und Export akzeptieren für Paginierung, Suche und Sortierung nur page, per, q und sort; limit, offset, search, sortBy und sortDirection werden mit 400 LEGACY_QUERY_PARAM_REMOVED abgelehnt. Der Papierkorb ist dieselbe Liste mit deleted=1; dort gelten Filter, Suche, Sortierung und gespeicherte Ansichten ebenso. Die Untervertrags-Liste ist schlanker: sie nimmt limit/offset und sortBy aus einer festen Feldliste (name, contractNumber, contractType, status, startDate, endDate, updatedAt; Default startDate absteigend), aber keine gespeicherten Ansichten. Unbekannte Werte werden mit 400 abgelehnt.
License-Filters
| Parameter | Beschreibung |
|---|---|
f.licenseType | PERPETUAL, SUBSCRIPTION, VOLUME, OEM, SITE, USER, DEVICE, CONCURRENT, TRIAL, FREEWARE, OPEN_SOURCE, OTHER (eq/neq/in/notIn) |
f.status | PENDING, ACTIVE, EXPIRED, SUSPENDED, CANCELLED, RETIRED (eq/neq/in/notIn) |
f.publisherId / f.productId / f.contractId | Publisher / Produkt / Vertrag (eq/in/isNull/isNotNull) |
f.name | Text-Filter (eq/contains/startsWith) |
f.quantityPurchased / f.quantityUsed / f.purchasePrice / f.recurringCost | Mengen- und Kosten-Filter (gt/gte/lt/lte/between) |
f.expirationDate / f.createdAt / f.updatedAt | Zeit-Filter (gt/gte/lt/lte/between/relative) |
q | Suche über Name, Beschreibung, Seriennummer, Produkt- und Publisher-Name |
page / per / sort | Paginierung und Sortierung |
overAssigned / expiringSoon / myTeam / myDepartment | Flags (true): überbuchte Lizenzen, bald ablaufend, eigenes Team, eigene Abteilung |
Permissions
| contracts.* | licenses.* |
|---|---|
viewAll, viewOwn, viewDeleted, viewHistory | viewAll, viewOwn, viewDeleted, viewHistory, viewKeys |
create, editAll, editOwn, delete, restore | create, update, delete, restore |
changeStatus, assign, bulkUpdate, export, reporting | bulkUpdate, export, reporting |
linkAssets, unlinkAssets, bulkLinkAssets | assignToAsset, unassignFromAsset, assignToUser, unassignFromUser |
| — | linkToContract, unlinkFromContract, bulkLinkToContract, bulkUnlinkFromContract, managePublishers, manageProducts |
Error-Handling
| Error Code | HTTP | Beschreibung |
|---|---|---|
CONTRACT_NOT_FOUND | 404 | Contract existiert nicht — oder ist für den Aufrufer nicht sichtbar (Löschen, Historie, Assets-Liste) |
CONTRACT_NUMBER_CONFLICT | 409 | Contract-Number bereits vergeben |
CONTRACT_VERSION_CONFLICT | 409 | Gleichzeitige Änderung durch jemand anderen (version ist beim Update Pflicht) |
INVALID_CONTRACT_STATUS_TRANSITION | 400 | Status-Wechsel laut Matrix nicht erlaubt |
CONTRACT_HAS_LINKED_ASSETS · CONTRACT_HAS_LINKED_LICENSES | 409 | Vertrag trägt noch Verknüpfungen |
CONTRACT_CIRCULAR_REFERENCE · CONTRACT_MAX_DEPTH | 400 | Hierarchie: Zyklus bzw. maximale Verschachtelung überschritten |
CONTRACT_INACTIVE_STATUS | 400 | Verknüpfen an einem gekündigten oder abgelaufenen Vertrag |
ASSET_CONTRACT_LINK_EXISTS | 409 | Asset ist bereits mit diesem Vertrag verknüpft |
ASSET_CONTRACT_LINK_NOT_FOUND | 404 | Verknüpfung existiert nicht |
LEGACY_QUERY_PARAM_REMOVED | 400 | limit/offset an der Vertragsliste (dort gelten page/per) |
LICENSE_NOT_FOUND | 404 | License existiert nicht |
LICENSE_VERSION_CONFLICT | 409 | Gleichzeitige Änderung durch jemand anderen |
LICENSE_HAS_ASSIGNMENTS | 409 | License hat Assignments (zuerst entfernen) |
SESSION_ONLY | 403 | Aktion erfordert User-Kontext (kein API-Key) |
Attachments
Contracts & Licenses nutzen das zentrale Anhang-System (signierte Verträge, Nachträge, Rechnungen):
POST /api/attachments/CONTRACT/:contractId
POST /api/attachments/LICENSE/:licenseId
GET /api/attachments/CONTRACT/:contractId
GET /api/attachments/LICENSE/:licenseId
Details: Siehe Attachments & File Settings API für Virenscan, Datei-Einstellungen und Aufbewahrungsfristen.
Cost Centers API → Kostenstellen für die Kostenzuordnung von Verträgen & Lizenzen.