{"openapi":"3.0.3","info":{"title":"Contrixt Public API","version":"1.0.0","description":"REST-API für den lesenden und schreibenden Zugriff auf Ihre Contrixt-Daten. Authentifizierung per API-Key (Bearer-Token), den Administratoren unter Einstellungen → API-Keys anlegen. Jeder Key trägt je Datentyp Lese- oder Lese-und-Schreibrechte."},"servers":[{"url":"https://app.contrixt.com/api/v1"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API-Key im Format cxk_… als Bearer-Token"}},"schemas":{"CostCenter":{"type":"object","properties":{"id":{"type":"string"},"code":{"type":"string","example":"IT-1000"},"name":{"type":"string","example":"IT Infrastruktur"},"parentCode":{"type":"string","nullable":true,"description":"Code der übergeordneten Kostenstelle"}}},"CostCenterCreate":{"type":"object","required":["code","name"],"properties":{"code":{"type":"string","maxLength":40},"name":{"type":"string","maxLength":160},"parentCode":{"type":"string","nullable":true}}},"CostCenterUpdate":{"type":"object","properties":{"name":{"type":"string","maxLength":160},"parentCode":{"type":"string","nullable":true}}},"Category":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string","example":"Software-Lizenzen"}}},"CategoryCreate":{"type":"object","required":["name"],"properties":{"name":{"type":"string","maxLength":160}}},"CategoryUpdate":{"type":"object","properties":{"name":{"type":"string","maxLength":160}}},"FiscalYear":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string","example":"2026"},"startDate":{"type":"string","format":"date","example":"2026-01-01"},"endDate":{"type":"string","format":"date","example":"2026-12-31"},"closed":{"type":"boolean","description":"Abgeschlossene Jahre nehmen keine neuen Buchungen mehr auf"}}},"FiscalYearCreate":{"type":"object","required":["label","startDate","endDate"],"properties":{"label":{"type":"string","maxLength":40},"startDate":{"type":"string","format":"date","example":"2026-01-01"},"endDate":{"type":"string","format":"date","example":"2026-12-31"}}},"FiscalYearUpdate":{"type":"object","description":"Der Zeitraum ist unveränderlich; änderbar sind Bezeichnung und Abschluss.","properties":{"label":{"type":"string","maxLength":40},"closed":{"type":"boolean"}}},"BudgetCell":{"type":"object","description":"Eine Zelle der Budget-Matrix: Kostenstelle × Kategorie im Fiskaljahr.","properties":{"costCenter":{"type":"string","example":"IT-1000","description":"Code der Kostenstelle"},"costCenterName":{"type":"string","example":"IT Infrastruktur"},"category":{"type":"string","example":"Software-Lizenzen","description":"Name der Kategorie"},"plan":{"type":"number","description":"Planwert"},"actual":{"type":"number","description":"Summe der Ist-Buchungen"},"committed":{"type":"number","description":"Offenes Obligo"},"contractBound":{"type":"number","description":"Durch laufende Verträge gebundener Betrag"},"forecast":{"type":"number","description":"Hochrechnung zum Jahresende"},"remaining":{"type":"number","description":"plan - actual - committed"}}},"BudgetPlanSet":{"type":"object","required":["fiscalYear","costCenterCode","categoryName","planAmount"],"properties":{"fiscalYear":{"type":"string","example":"2026","description":"Bezeichnung des Fiskaljahres"},"costCenterCode":{"type":"string","example":"IT-1000"},"categoryName":{"type":"string","example":"Software-Lizenzen"},"planAmount":{"type":"number","example":12000}}},"BudgetPlanResult":{"type":"object","properties":{"id":{"type":"string"},"fiscalYear":{"type":"string","example":"2026"},"costCenter":{"type":"string","example":"IT-1000"},"category":{"type":"string","example":"Software-Lizenzen"},"plan":{"type":"number","example":12000}}},"ActualListItem":{"type":"object","description":"Ist-Buchung in der Listenansicht.","properties":{"date":{"type":"string","format":"date","example":"2026-03-15"},"amount":{"type":"number","example":1250.5},"description":{"type":"string"},"costCenter":{"type":"string","example":"IT-1000","description":"Code der Kostenstelle"},"category":{"type":"string","example":"Software-Lizenzen","description":"Name der Kategorie"},"vendor":{"type":"string","nullable":true},"invoiceRef":{"type":"string","nullable":true},"source":{"type":"string","enum":["MANUAL","CSV_IMPORT","CONTRACT","LICENSE","DEPRECIATION","CLOUD","INVOICE"],"description":"Herkunft der Buchung; nur MANUAL ist über die API änderbar"}}},"Actual":{"type":"object","description":"Ist-Buchung in der Einzelansicht (Listenfelder plus id und Fiskaljahr).","properties":{"id":{"type":"string"},"date":{"type":"string","format":"date","example":"2026-03-15"},"amount":{"type":"number","example":1250.5},"description":{"type":"string"},"costCenter":{"type":"string","example":"IT-1000"},"category":{"type":"string","example":"Software-Lizenzen"},"fiscalYear":{"type":"string","example":"2026"},"vendor":{"type":"string","nullable":true},"invoiceRef":{"type":"string","nullable":true},"source":{"type":"string","enum":["MANUAL","CSV_IMPORT","CONTRACT","LICENSE","DEPRECIATION","CLOUD","INVOICE"]}}},"ActualCreate":{"type":"object","required":["date","amount","costCenterCode","categoryName","description"],"properties":{"date":{"type":"string","format":"date","example":"2026-03-15"},"amount":{"type":"number","exclusiveMinimum":0},"costCenterCode":{"type":"string","example":"IT-1000"},"categoryName":{"type":"string","example":"Software-Lizenzen"},"description":{"type":"string","maxLength":300},"vendor":{"type":"string","maxLength":160},"invoiceRef":{"type":"string","maxLength":120}}},"ActualUpdate":{"type":"object","properties":{"date":{"type":"string","format":"date","description":"Muss im Fiskaljahr der Buchung liegen"},"amount":{"type":"number","exclusiveMinimum":0},"description":{"type":"string","maxLength":300},"vendor":{"type":"string","maxLength":160,"nullable":true},"invoiceRef":{"type":"string","maxLength":120,"nullable":true}}},"ActualBulkRequest":{"type":"object","required":["rows"],"properties":{"rows":{"type":"array","minItems":1,"maxItems":1000,"items":{"$ref":"#/components/schemas/ActualCreate"},"description":"Maximal 1000 Zeilen je Request."},"dryRun":{"type":"boolean","default":false,"description":"Probelauf: nur zählen und prüfen, es wird nichts geschrieben (keine Budgets, keine Buchungen, kein Audit-Eintrag)."},"dedupe":{"type":"string","enum":["skip","error"],"default":"skip","description":"\"skip\" (Default) übergeht Dubletten stillschweigend (Zählwerk in skipped); \"error\" meldet sie stattdessen als Zeilenfehler (Code duplicate_row). Eine Dublette ist eine Zeile mit demselben importKey wie eine bereits importierte Buchung oder eine andere Zeile desselben Requests (identisches Datum, Betrag, Text, Kostenstelle, Kategorie, Lieferant und Belegreferenz)."}}},"ActualBulkRowError":{"type":"object","properties":{"line":{"type":"integer","description":"1-basierte Position in rows","example":3},"code":{"type":"string","enum":["invalid_row","invalid_date","cost_center_not_found","category_not_found","no_open_fiscal_year","duplicate_row"]},"message":{"type":"string"}}},"ActualBulkResult":{"type":"object","properties":{"imported":{"type":"integer","description":"Angelegte (bzw. bei dryRun: importierbare) Buchungen"},"skipped":{"type":"integer","description":"Übersprungene Dubletten (nur bei dedupe=skip, dem Default)"},"errors":{"type":"array","items":{"$ref":"#/components/schemas/ActualBulkRowError"}},"dryRun":{"type":"boolean"}}},"Commitment":{"type":"object","properties":{"id":{"type":"string"},"date":{"type":"string","format":"date","example":"2026-03-15"},"amount":{"type":"number","example":4800},"description":{"type":"string"},"costCenter":{"type":"string","example":"IT-1000","description":"Code der Kostenstelle"},"category":{"type":"string","example":"Software-Lizenzen","description":"Name der Kategorie"},"fiscalYear":{"type":"string","example":"2026"},"open":{"type":"boolean","description":"Nur offenes Obligo belastet das Budget"},"approvalStatus":{"type":"string","example":"NONE","description":"Freigabestatus; nur NONE und APPROVED zählen in die Obligo-Summe"},"source":{"type":"string","enum":["MANUAL","CONTRACT"],"description":"Aus einem Vertrag erzeugtes Obligo (CONTRACT) ist über die API nicht änderbar"}}},"CommitmentCreate":{"type":"object","required":["date","amount","costCenterCode","categoryName","description"],"properties":{"date":{"type":"string","format":"date","example":"2026-03-15"},"amount":{"type":"number","exclusiveMinimum":0},"costCenterCode":{"type":"string","example":"IT-1000"},"categoryName":{"type":"string","example":"Software-Lizenzen"},"description":{"type":"string","maxLength":300}}},"CommitmentUpdate":{"type":"object","properties":{"date":{"type":"string","format":"date","description":"Muss im Fiskaljahr des Obligos liegen"},"amount":{"type":"number","exclusiveMinimum":0},"description":{"type":"string","maxLength":300},"open":{"type":"boolean","description":"false schließt die Verpflichtung (belastet das Budget nicht mehr)"}}},"Contract":{"type":"object","description":"Vertrag mit abgeleiteten Radar-Feldern (nächstes Vertragsende, letzter Kündigungstermin, Ampel).","properties":{"id":{"type":"string"},"name":{"type":"string","example":"Microsoft 365 E3"},"vendor":{"type":"string","example":"Microsoft Ireland"},"contractNo":{"type":"string","nullable":true},"costCenter":{"type":"string","example":"IT-1000","description":"Code der Kostenstelle"},"category":{"type":"string","example":"Software-Lizenzen","description":"Name der Budget-Kategorie"},"status":{"type":"string","enum":["ACTIVE","CANCELLED","EXPIRED"]},"amount":{"type":"number","description":"Betrag je Abrechnungszeitraum in Mandantenwährung"},"billingPeriod":{"type":"string","enum":["MONTHLY","QUARTERLY","YEARLY","ONCE"]},"yearlyAmount":{"type":"number","description":"Auf ein Jahr hochgerechneter Betrag"},"startDate":{"type":"string","format":"date"},"endDate":{"type":"string","format":"date","nullable":true,"description":"Nächstes reguläres Vertragsende (bei Auto-Verlängerung die aktuell laufende Periode)"},"cancelBy":{"type":"string","format":"date","nullable":true,"description":"Letzter Kündigungstermin unter Berücksichtigung der Kündigungsfrist"},"radar":{"type":"string","enum":["ok","soon","urgent","passed","none"],"description":"Ampel des Kündigungs-Radars auf Basis der Mandanten-Schwellenwerte"},"radarDaysLeft":{"type":"integer","nullable":true,"description":"Tage bis zum letzten Kündigungstermin"},"renewalDecision":{"type":"string","nullable":true,"enum":["RENEW","RENEGOTIATE","CANCEL"]},"autoRenew":{"type":"boolean"},"noticePeriodDays":{"type":"integer"}}},"ContractCreate":{"type":"object","required":["name","costCenterCode","categoryName","amount","billingPeriod","startDate"],"properties":{"name":{"type":"string","minLength":2,"maxLength":150},"vendor":{"type":"string","maxLength":100,"description":"Lieferantenname als Freitext"},"contractNo":{"type":"string","maxLength":60},"costCenterCode":{"type":"string","example":"IT-1000","description":"Code einer bestehenden Kostenstelle"},"categoryName":{"type":"string","example":"Software-Lizenzen","description":"Name einer bestehenden Budget-Kategorie"},"amount":{"type":"number","minimum":0,"description":"Nettobetrag je Abrechnungszeitraum"},"billingPeriod":{"type":"string","enum":["MONTHLY","QUARTERLY","YEARLY","ONCE"]},"startDate":{"type":"string","format":"date","example":"2026-01-01"},"endDate":{"type":"string","format":"date","nullable":true},"noticePeriodDays":{"type":"integer","minimum":0,"maximum":730,"default":90},"autoRenew":{"type":"boolean","default":true}}},"ContractUpdate":{"type":"object","description":"Teilfelder eines Vertrags — nur übertragene Felder werden geändert. MSP-Verrechnungsfelder (Kundenzuordnung, Aufschlag, Spiegelvertrag) sind über die API nicht änderbar.","properties":{"name":{"type":"string","minLength":2,"maxLength":150},"vendor":{"type":"string","maxLength":100},"contractNo":{"type":"string","maxLength":60,"nullable":true},"costCenterCode":{"type":"string"},"categoryName":{"type":"string"},"amount":{"type":"number","minimum":0},"billingPeriod":{"type":"string","enum":["MONTHLY","QUARTERLY","YEARLY","ONCE"]},"startDate":{"type":"string","format":"date"},"endDate":{"type":"string","format":"date","nullable":true},"noticePeriodDays":{"type":"integer","minimum":0,"maximum":730},"autoRenew":{"type":"boolean"}}},"Supplier":{"type":"object","description":"Lieferanten-Stammdatensatz. Der Name ist je Mandant eindeutig.","properties":{"id":{"type":"string"},"name":{"type":"string","example":"Microsoft Ireland Operations Ltd."},"legalName":{"type":"string","maxLength":200,"nullable":true,"description":"Firmierung laut Handelsregister"},"number":{"type":"string","maxLength":40,"nullable":true,"description":"Kreditorennummer"},"category":{"type":"string","enum":["SOFTWARE","HARDWARE","CLOUD","TELCO","SERVICES","CONSULTING","OTHER"],"default":"OTHER"},"status":{"type":"string","enum":["ACTIVE","INACTIVE","BLOCKED"],"default":"ACTIVE"},"vatId":{"type":"string","maxLength":30,"nullable":true,"description":"Umsatzsteuer-Identifikationsnummer"},"website":{"type":"string","maxLength":200,"nullable":true},"email":{"type":"string","maxLength":150,"nullable":true},"phone":{"type":"string","maxLength":50,"nullable":true},"street":{"type":"string","maxLength":150,"nullable":true},"zip":{"type":"string","maxLength":20,"nullable":true},"city":{"type":"string","maxLength":100,"nullable":true},"country":{"type":"string","maxLength":80,"nullable":true},"contactName":{"type":"string","maxLength":120,"nullable":true},"contactEmail":{"type":"string","maxLength":150,"nullable":true},"contactPhone":{"type":"string","maxLength":50,"nullable":true},"paymentTermsDays":{"type":"integer","minimum":0,"maximum":365,"nullable":true,"description":"Zahlungsziel in Tagen"},"riskLevel":{"type":"string","enum":["LOW","MEDIUM","HIGH"],"default":"LOW"},"dpaSigned":{"type":"boolean","default":false,"description":"Auftragsverarbeitungsvertrag unterzeichnet"},"notes":{"type":"string","maxLength":2000,"nullable":true}}},"SupplierCreate":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":2,"maxLength":150},"legalName":{"type":"string","maxLength":200,"nullable":true,"description":"Firmierung laut Handelsregister"},"number":{"type":"string","maxLength":40,"nullable":true,"description":"Kreditorennummer"},"category":{"type":"string","enum":["SOFTWARE","HARDWARE","CLOUD","TELCO","SERVICES","CONSULTING","OTHER"],"default":"OTHER"},"status":{"type":"string","enum":["ACTIVE","INACTIVE","BLOCKED"],"default":"ACTIVE"},"vatId":{"type":"string","maxLength":30,"nullable":true,"description":"Umsatzsteuer-Identifikationsnummer"},"website":{"type":"string","maxLength":200,"nullable":true},"email":{"type":"string","maxLength":150,"nullable":true},"phone":{"type":"string","maxLength":50,"nullable":true},"street":{"type":"string","maxLength":150,"nullable":true},"zip":{"type":"string","maxLength":20,"nullable":true},"city":{"type":"string","maxLength":100,"nullable":true},"country":{"type":"string","maxLength":80,"nullable":true},"contactName":{"type":"string","maxLength":120,"nullable":true},"contactEmail":{"type":"string","maxLength":150,"nullable":true},"contactPhone":{"type":"string","maxLength":50,"nullable":true},"paymentTermsDays":{"type":"integer","minimum":0,"maximum":365,"nullable":true,"description":"Zahlungsziel in Tagen"},"riskLevel":{"type":"string","enum":["LOW","MEDIUM","HIGH"],"default":"LOW"},"dpaSigned":{"type":"boolean","default":false,"description":"Auftragsverarbeitungsvertrag unterzeichnet"},"notes":{"type":"string","maxLength":2000,"nullable":true}}},"SupplierUpdate":{"type":"object","description":"Teilfelder eines Lieferanten — nur übertragene Felder werden geändert, null löscht den Wert.","properties":{"name":{"type":"string","minLength":2,"maxLength":150},"legalName":{"type":"string","maxLength":200,"nullable":true,"description":"Firmierung laut Handelsregister"},"number":{"type":"string","maxLength":40,"nullable":true,"description":"Kreditorennummer"},"category":{"type":"string","enum":["SOFTWARE","HARDWARE","CLOUD","TELCO","SERVICES","CONSULTING","OTHER"],"default":"OTHER"},"status":{"type":"string","enum":["ACTIVE","INACTIVE","BLOCKED"],"default":"ACTIVE"},"vatId":{"type":"string","maxLength":30,"nullable":true,"description":"Umsatzsteuer-Identifikationsnummer"},"website":{"type":"string","maxLength":200,"nullable":true},"email":{"type":"string","maxLength":150,"nullable":true},"phone":{"type":"string","maxLength":50,"nullable":true},"street":{"type":"string","maxLength":150,"nullable":true},"zip":{"type":"string","maxLength":20,"nullable":true},"city":{"type":"string","maxLength":100,"nullable":true},"country":{"type":"string","maxLength":80,"nullable":true},"contactName":{"type":"string","maxLength":120,"nullable":true},"contactEmail":{"type":"string","maxLength":150,"nullable":true},"contactPhone":{"type":"string","maxLength":50,"nullable":true},"paymentTermsDays":{"type":"integer","minimum":0,"maximum":365,"nullable":true,"description":"Zahlungsziel in Tagen"},"riskLevel":{"type":"string","enum":["LOW","MEDIUM","HIGH"],"default":"LOW"},"dpaSigned":{"type":"boolean","default":false,"description":"Auftragsverarbeitungsvertrag unterzeichnet"},"notes":{"type":"string","maxLength":2000,"nullable":true}}},"Invoice":{"type":"object","description":"Eingangsrechnung mit den extrahierten Feldern und dem Ergebnis des Vertrags-Matchings.","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["RECEIVED","MATCHED","BOOKED","DISMISSED"],"description":"Bearbeitungsstand im Rechnungseingang"},"source":{"type":"string","enum":["UPLOAD","EMAIL"],"description":"Herkunft der Rechnung"},"fileName":{"type":"string","description":"Ursprünglicher Dateiname"},"supplierId":{"type":"string","nullable":true,"description":"Id des zugeordneten Stammdaten-Lieferanten"},"supplierName":{"type":"string","nullable":true,"description":"Lieferant aus den Stammdaten, sonst der erkannte Name"},"invoiceNumber":{"type":"string","nullable":true},"invoiceDate":{"type":"string","format":"date","nullable":true},"amountNet":{"type":"number","nullable":true},"amountGross":{"type":"number","nullable":true},"vatRate":{"type":"number","nullable":true,"description":"Umsatzsteuersatz in Prozent"},"deviationPct":{"type":"number","nullable":true,"description":"Abweichung gegenüber dem erwarteten Vertragswert in Prozent (positiv = teurer)"},"eInvoiceFormat":{"type":"string","nullable":true,"description":"Erkanntes E-Rechnungsformat (CII oder UBL)"},"contractId":{"type":"string","nullable":true,"description":"Gematchter Vertrag"},"contract":{"type":"string","nullable":true,"description":"Name des gematchten Vertrags"},"note":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"}}},"Investment":{"type":"object","description":"Investition (Anlagegut) mit Abschreibungsplan.","properties":{"id":{"type":"string"},"name":{"type":"string","example":"Serverschrank Rechenzentrum"},"vendor":{"type":"string","nullable":true},"invoiceRef":{"type":"string","nullable":true,"description":"Rechnungsreferenz"},"amount":{"type":"number","description":"Anschaffungswert netto"},"acquiredAt":{"type":"string","format":"date","description":"Anschaffungsdatum"},"depreciationMonths":{"type":"integer","description":"Abschreibungsdauer in Monaten"},"interval":{"type":"string","enum":["MONTHLY","QUARTERLY","SEMIANNUAL","YEARLY"],"description":"Buchungsintervall der Abschreibung"},"costCenter":{"type":"string","example":"IT-1000","description":"Code der Kostenstelle"},"category":{"type":"string","example":"Hardware","description":"Name der Budget-Kategorie"},"bookedPeriods":{"type":"integer","description":"Bereits gebuchte Abschreibungsperioden"},"approvalStatus":{"type":"string","description":"Stand der Investitionsfreigabe"},"notes":{"type":"string","nullable":true}}},"InvestmentCreate":{"type":"object","required":["name","amount","acquiredAt","depreciationMonths","costCenterCode","categoryName"],"properties":{"name":{"type":"string","minLength":2,"maxLength":150},"vendor":{"type":"string","maxLength":100,"nullable":true},"invoiceRef":{"type":"string","maxLength":60,"nullable":true},"amount":{"type":"number","minimum":0.01,"description":"Anschaffungswert netto"},"acquiredAt":{"type":"string","format":"date","example":"2026-03-01"},"depreciationMonths":{"type":"integer","minimum":1,"maximum":600},"interval":{"type":"string","enum":["MONTHLY","QUARTERLY","SEMIANNUAL","YEARLY"],"default":"MONTHLY"},"costCenterCode":{"type":"string","example":"IT-1000","description":"Code einer bestehenden Kostenstelle"},"categoryName":{"type":"string","example":"Hardware","description":"Name einer bestehenden Budget-Kategorie"},"notes":{"type":"string","maxLength":2000,"nullable":true}}},"InvestmentUpdate":{"type":"object","description":"Teilfelder einer Investition — nur übertragene Felder werden geändert. Sobald Abschreibungen gebucht sind, sind Betrag, Anschaffungsdatum, Dauer und Intervall gesperrt.","properties":{"name":{"type":"string","minLength":2,"maxLength":150},"vendor":{"type":"string","maxLength":100,"nullable":true},"invoiceRef":{"type":"string","maxLength":60,"nullable":true},"amount":{"type":"number","minimum":0.01},"acquiredAt":{"type":"string","format":"date"},"depreciationMonths":{"type":"integer","minimum":1,"maximum":600},"interval":{"type":"string","enum":["MONTHLY","QUARTERLY","SEMIANNUAL","YEARLY"]},"costCenterCode":{"type":"string"},"categoryName":{"type":"string"},"notes":{"type":"string","maxLength":2000,"nullable":true}}},"BudgetRequest":{"type":"object","description":"Budgetantrag in der Listenansicht.","properties":{"id":{"type":"string"},"fiscalYear":{"type":"string","example":"2026","description":"Bezeichnung des Fiskaljahres"},"costCenter":{"type":"string","example":"IT-1000","description":"Code der Kostenstelle"},"category":{"type":"string","example":"Software-Lizenzen","description":"Name der Budget-Kategorie"},"amount":{"type":"number","description":"Beantragter Nettobetrag"},"reason":{"type":"string","description":"Begründung des Antrags"},"status":{"type":"string","enum":["PENDING","APPROVED","REJECTED"]},"currentStage":{"type":"integer","description":"Aktuell offene Freigabestufe (0 = einstufiger Workflow)"},"requestedBy":{"type":"string","nullable":true,"description":"Name des Antragstellers"},"requestedByEmail":{"type":"string","nullable":true},"decidedBy":{"type":"string","nullable":true,"description":"Name des abschließenden Entscheiders"},"decidedAt":{"type":"string","format":"date-time","nullable":true},"decisionNote":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"}}},"BudgetRequestDetail":{"type":"object","description":"Budgetantrag mit der Entscheidungshistorie der durchlaufenen Freigabestufen.","properties":{"id":{"type":"string"},"fiscalYear":{"type":"string","example":"2026","description":"Bezeichnung des Fiskaljahres"},"costCenter":{"type":"string","example":"IT-1000","description":"Code der Kostenstelle"},"category":{"type":"string","example":"Software-Lizenzen","description":"Name der Budget-Kategorie"},"amount":{"type":"number","description":"Beantragter Nettobetrag"},"reason":{"type":"string","description":"Begründung des Antrags"},"status":{"type":"string","enum":["PENDING","APPROVED","REJECTED"]},"currentStage":{"type":"integer","description":"Aktuell offene Freigabestufe (0 = einstufiger Workflow)"},"requestedBy":{"type":"string","nullable":true,"description":"Name des Antragstellers"},"requestedByEmail":{"type":"string","nullable":true},"decidedBy":{"type":"string","nullable":true,"description":"Name des abschließenden Entscheiders"},"decidedAt":{"type":"string","format":"date-time","nullable":true},"decisionNote":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"},"approvals":{"type":"array","description":"Erteilte Stufen-Entscheidungen in aufsteigender Reihenfolge","items":{"$ref":"#/components/schemas/BudgetRequestApproval"}}}},"BudgetRequestApproval":{"type":"object","description":"Entscheidung einer einzelnen Freigabestufe.","properties":{"stage":{"type":"integer","description":"Nummer der Freigabestufe"},"decision":{"type":"string","description":"Ergebnis der Stufe (z. B. APPROVED oder REJECTED)"},"decidedBy":{"type":"string","nullable":true},"decidedByEmail":{"type":"string","nullable":true},"note":{"type":"string","nullable":true},"decidedAt":{"type":"string","format":"date-time"}}},"Application":{"type":"object","properties":{"id":{"type":"string"},"appCode":{"type":"string","example":"SAP-ERP"},"name":{"type":"string","example":"SAP ERP"},"description":{"type":"string","nullable":true},"status":{"type":"string","enum":["PLANNED","ACTIVE","DEPRECATED","RETIRED"]},"criticality":{"type":"string","enum":["LOW","MEDIUM","HIGH"]},"costCenterCode":{"type":"string","nullable":true,"description":"Code der zugeordneten Kostenstelle"},"supplier":{"type":"string","nullable":true,"description":"Name des zugeordneten Lieferanten"},"notes":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date"},"updatedAt":{"type":"string","format":"date"}}},"ApplicationCreate":{"type":"object","required":["appCode","name"],"properties":{"appCode":{"type":"string","maxLength":40,"description":"Nur Buchstaben, Ziffern, - und _"},"name":{"type":"string","maxLength":200},"description":{"type":"string","nullable":true,"maxLength":2000},"status":{"type":"string","enum":["PLANNED","ACTIVE","DEPRECATED","RETIRED"],"default":"ACTIVE"},"criticality":{"type":"string","enum":["LOW","MEDIUM","HIGH"],"default":"MEDIUM"},"costCenterCode":{"type":"string","nullable":true},"supplierName":{"type":"string","nullable":true},"notes":{"type":"string","nullable":true,"maxLength":4000}}},"ApplicationUpdate":{"type":"object","properties":{"appCode":{"type":"string","maxLength":40},"name":{"type":"string","maxLength":200},"description":{"type":"string","nullable":true,"maxLength":2000},"status":{"type":"string","enum":["PLANNED","ACTIVE","DEPRECATED","RETIRED"]},"criticality":{"type":"string","enum":["LOW","MEDIUM","HIGH"]},"costCenterCode":{"type":"string","nullable":true},"supplierName":{"type":"string","nullable":true},"notes":{"type":"string","nullable":true,"maxLength":4000}}},"SavingsRecommendation":{"type":"object","properties":{"id":{"type":"string"},"sourceKey":{"type":"string","description":"Stabiler Schlüssel je Quelle (Dedupe)"},"type":{"type":"string","example":"license-unused"},"title":{"type":"string"},"params":{"type":"object","description":"Anzeige-Parameter des Befunds (z. B. units)","additionalProperties":true},"monthlySavingEur":{"type":"number","description":"Geschätzte Einsparung pro Monat"},"status":{"type":"string","enum":["OPEN","DONE","DISMISSED"]},"realizedMonthlyEur":{"type":"number","nullable":true,"description":"Bei DONE: tatsächlich realisierter Monatswert"},"decidedAt":{"type":"string","format":"date","nullable":true},"href":{"type":"string","nullable":true,"description":"Ziel-Link in der App (z. B. /licenses, /contracts)"}}},"SavingsUpdate":{"type":"object","description":"status oder realizedMonthly (mindestens eines) — Übergänge wie in der App: OPEN → DONE|DISMISSED, zurück auf OPEN erlaubt.","properties":{"status":{"type":"string","enum":["OPEN","DONE","DISMISSED"]},"realizedMonthly":{"type":"number","minimum":0}}},"License":{"type":"object","properties":{"id":{"type":"string"},"provider":{"type":"string","enum":["M365","GOOGLE","ADOBE","MANUAL"]},"skuId":{"type":"string"},"skuPartNumber":{"type":"string"},"name":{"type":"string","example":"Microsoft 365 E3"},"vendor":{"type":"string","nullable":true},"totalUnits":{"type":"integer"},"assignedUnits":{"type":"integer"},"freeUnits":{"type":"integer"},"monthlyPriceEur":{"type":"number","description":"Preis je Einheit/Monat; 0, wenn nicht gepflegt"},"costCenterCode":{"type":"string","nullable":true},"applicationCode":{"type":"string","nullable":true},"contractName":{"type":"string","nullable":true}}},"User":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string","format":"email"},"name":{"type":"string"},"role":{"type":"string","enum":["ADMIN","CONTROLLER","BUDGET_OWNER","READONLY"]},"active":{"type":"boolean"},"locale":{"type":"string","nullable":true}}},"DiscoveredApp":{"type":"object","description":"Über SaaS-Discovery erkannte Anwendung, im Abgleich mit der gepflegten Applikationsliste.","properties":{"externalId":{"type":"string","description":"Provider-eigene Kennung (z. B. Entra appId)"},"provider":{"type":"string","enum":["ENTRA","GITHUB","ATLASSIAN","ZOOM"],"description":"Quelle der Erkennung"},"displayName":{"type":"string"},"publisher":{"type":"string","nullable":true},"accountEnabled":{"type":"boolean"},"ignored":{"type":"boolean","description":"Vom Admin als irrelevant markiert"},"matchedApplication":{"type":"string","nullable":true,"description":"Name der zugeordneten Applikation, falls gematcht"},"firstSeenAt":{"type":"string","format":"date-time"},"lastSeenAt":{"type":"string","format":"date-time"}}},"CostPerEmployeeRow":{"type":"object","properties":{"costCenterCode":{"type":"string"},"costCenterName":{"type":"string"},"headcount":{"type":"integer","description":"Personenbasis der Zeile: bei Schlüssel HEADCOUNT die gepflegte Mitarbeiterzahl, sonst die Zahl der deduplizierten lizenzierten Nutzer dieser Kostenstelle."},"directEur":{"type":"number","description":"Personengebundene Lizenzkosten (1:1 zurechenbar)"},"allocatedEur":{"type":"number","description":"Anteil an der Umlagemasse (Verträge, Cloud, VMs, AfA, Lizenz-Rest)"},"totalEur":{"type":"number","description":"directEur + allocatedEur"},"perHeadEur":{"type":"number","nullable":true,"description":"totalEur / headcount; null ohne bekannte Köpfe"}}},"EmployeeCostLicense":{"type":"object","properties":{"name":{"type":"string","description":"Anzeigename der SKU"},"provider":{"type":"string","description":"z. B. M365, GOOGLE, ADOBE, MANUAL"},"priceEur":{"type":"number","description":"Monatspreis dieser SKU-Position"}}},"EmployeeCostRow":{"type":"object","description":"Eine Person des Verzeichnisses (personenbezogen).","properties":{"displayName":{"type":"string"},"email":{"type":"string","nullable":true},"sources":{"type":"array","items":{"type":"string"},"description":"Quellsysteme dieser Person, z. B. [\"SCIM\", \"M365\"]"},"costCenterCode":{"type":"string","nullable":true,"description":"Code der Kostenstelle, falls zugeordnet"},"group":{"type":"string","nullable":true,"description":"Wert des Mandanten-Gruppierungsattributs (siehe groupAttribute); null = ohne Zuordnung"},"active":{"type":"boolean"},"directEur":{"type":"number","description":"Direkte, personengebundene Lizenzkosten (Σ licenses.priceEur)"},"allocatedShareEur":{"type":"number","nullable":true,"description":"Umlage-Anteil der eigenen Kostenstelle; null ohne Kostenstelle bzw. ohne Kopfbasis"},"totalEur":{"type":"number","description":"directEur + allocatedShareEur"},"licenses":{"type":"array","items":{"$ref":"#/components/schemas/EmployeeCostLicense"}}}},"PsaTicket":{"type":"object","description":"Ticket aus dem angebundenen PSA-System.","properties":{"ref":{"type":"string","nullable":true,"description":"Ticketnummer, wie der Provider sie anzeigt"},"subject":{"type":"string"},"status":{"type":"string","description":"Roh-Status aus dem PSA (providerspezifisch)"},"priority":{"type":"string","nullable":true},"client":{"type":"string","nullable":true,"description":"ID des zugeordneten Kunden-Mandanten (childTenantId), sofern per Company-Mapping zugeordnet"},"openedAt":{"type":"string","format":"date-time","nullable":true},"closedAt":{"type":"string","format":"date-time","nullable":true},"source":{"type":"string","enum":["CONNECTWISE","AUTOTASK","DATTO_RMM"],"description":"PSA-Provider der Verbindung"}}},"RmmDevice":{"type":"object","description":"Gerät aus dem angebundenen RMM-System.","properties":{"hostname":{"type":"string"},"deviceType":{"type":"string","nullable":true},"os":{"type":"string","nullable":true},"online":{"type":"boolean"},"lastSeenAt":{"type":"string","format":"date-time","nullable":true},"siteRef":{"type":"string","nullable":true,"description":"Site-/Company-Kennung des Providers"},"client":{"type":"string","nullable":true,"description":"ID des zugeordneten Kunden-Mandanten (childTenantId), sofern per Site-Mapping zugeordnet"},"source":{"type":"string","enum":["CONNECTWISE","AUTOTASK","DATTO_RMM"],"description":"RMM-Provider der Verbindung"}}},"CostAnomaly":{"type":"object","description":"Erkannter ungewöhnlicher Ausgabensprung eines abgeschlossenen Monats gegenüber der Baseline der Vormonate.","properties":{"dimension":{"type":"string","enum":["COST_CENTER","CONTRACT","CLOUD_CONNECTION"],"description":"Ebene, auf der der Sprung erkannt wurde"},"refId":{"type":"string","description":"Fach-ID des betroffenen Objekts (Kostenstelle, Vertrag, Cloud-Verbindung)"},"label":{"type":"string","description":"Anzeigename zum Zeitpunkt der Erkennung"},"month":{"type":"string","example":"2026-06","description":"Geprüfter Monat (YYYY-MM)"},"amountEur":{"type":"number","description":"Tatsächliche Ausgabe im geprüften Monat"},"baselineEur":{"type":"number","description":"Durchschnitt der herangezogenen Vormonate"},"deltaPct":{"type":"number","description":"Abweichung in Prozent gegenüber der Baseline"},"status":{"type":"string","enum":["OPEN","DISMISSED"]},"createdAt":{"type":"string","format":"date-time"}}},"Webhook":{"type":"object","description":"Webhook-Abo. Enthält NIE das Signatur-Secret (nur einmalig bei der Anlage).","properties":{"id":{"type":"string"},"url":{"type":"string","format":"uri","example":"https://example.com/hooks/contrixt"},"events":{"type":"array","items":{"type":"string","enum":["budget.warn","budget.exceeded","request.decided","contract.deadline","invoice.deviation","planning.approved","cost.anomaly","actual.created","import.completed","import.undone","contract.created","contract.updated","request.created","request.stageAdvanced","invoice.booked","savings.realized"]},"nullable":true,"description":"Abonnierte Events; null bedeutet alle Events."},"active":{"type":"boolean"},"lastSentAt":{"type":"string","format":"date-time","nullable":true},"consecutiveFailures":{"type":"integer","description":"Aufeinanderfolgende endgültig gescheiterte Zustellungen"},"disabledReason":{"type":"string","nullable":true,"description":"Grund der automatischen Abschaltung, falls active=false"},"createdAt":{"type":"string","format":"date-time"}}},"WebhookCreate":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","description":"Muss mit https:// beginnen"},"events":{"type":"array","items":{"type":"string","enum":["budget.warn","budget.exceeded","request.decided","contract.deadline","invoice.deviation","planning.approved","cost.anomaly","actual.created","import.completed","import.undone","contract.created","contract.updated","request.created","request.stageAdvanced","invoice.booked","savings.realized"]},"nullable":true,"description":"Whitelist aus dem Event-Katalog; leer/weggelassen = alle Events"}}},"WebhookCreated":{"type":"object","description":"Antwort auf die Anlage — enthält das Signatur-Secret EINMALIG im Klartext.","properties":{"id":{"type":"string"},"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"type":"string","enum":["budget.warn","budget.exceeded","request.decided","contract.deadline","invoice.deviation","planning.approved","cost.anomaly","actual.created","import.completed","import.undone","contract.created","contract.updated","request.created","request.stageAdvanced","invoice.booked","savings.realized"]},"nullable":true},"secret":{"type":"string","description":"Signatur-Secret für die HMAC-Prüfung eingehender Zustellungen (nur hier sichtbar)"}}},"WebhookUpdate":{"type":"object","description":"Teilfelder eines Abos — nur übertragene Felder werden geändert.","properties":{"url":{"type":"string","format":"uri","description":"Muss mit https:// beginnen"},"events":{"type":"array","items":{"type":"string","enum":["budget.warn","budget.exceeded","request.decided","contract.deadline","invoice.deviation","planning.approved","cost.anomaly","actual.created","import.completed","import.undone","contract.created","contract.updated","request.created","request.stageAdvanced","invoice.booked","savings.realized"]},"nullable":true,"description":"null oder leeres Array setzt auf \"alle Events\" zurück"},"active":{"type":"boolean"}}},"WebhookTestResult":{"type":"object","description":"Ergebnis des Sofort-Zustellversuchs des Test-Pings.","properties":{"deliveryId":{"type":"string"},"status":{"type":"string","enum":["PENDING","DELIVERED","FAILED","DEAD"]},"attempts":{"type":"integer"},"lastStatusCode":{"type":"integer","nullable":true},"responseMs":{"type":"integer","nullable":true},"lastError":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"},"deliveredAt":{"type":"string","format":"date-time","nullable":true}}},"WebhookDelivery":{"type":"object","properties":{"id":{"type":"string"},"event":{"type":"string"},"status":{"type":"string","enum":["PENDING","DELIVERED","FAILED","DEAD"]},"attempts":{"type":"integer"},"lastStatusCode":{"type":"integer","nullable":true},"responseMs":{"type":"integer","nullable":true},"lastError":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"},"deliveredAt":{"type":"string","format":"date-time","nullable":true}}}}},"paths":{"/cost-centers":{"get":{"tags":["Kostenstellen"],"summary":"Kostenstellen auflisten","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":5000,"default":1000},"description":"Maximale Anzahl Datensätze"}],"responses":{"200":{"description":"Liste der Kostenstellen","content":{"application/json":{"schema":{"type":"object","properties":{"costCenters":{"type":"array","items":{"$ref":"#/components/schemas/CostCenter"}}}}}}},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"}}},"post":{"tags":["Kostenstellen"],"summary":"Kostenstelle anlegen","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CostCenterCreate"}}}},"responses":{"201":{"description":"Angelegt","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CostCenter"}}}},"400":{"description":"Ungültiger Request-Body"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"409":{"description":"Code bereits vergeben"}}}},"/cost-centers/{id}":{"get":{"tags":["Kostenstellen"],"summary":"Kostenstelle lesen","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Kostenstelle","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CostCenter"}}}},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Nicht gefunden"}}},"patch":{"tags":["Kostenstellen"],"summary":"Kostenstelle ändern","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CostCenterUpdate"}}}},"responses":{"200":{"description":"Geändert","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CostCenter"}}}},"400":{"description":"Ungültiger Request-Body"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Nicht gefunden"}}},"delete":{"tags":["Kostenstellen"],"summary":"Kostenstelle löschen","description":"Nur möglich, wenn keine Budgets, Verträge oder andere Daten daran hängen.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Gelöscht"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Nicht gefunden"},"409":{"description":"Wird noch verwendet"}}}},"/categories":{"get":{"tags":["Kategorien"],"summary":"Kategorien auflisten","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":5000,"default":1000},"description":"Maximale Anzahl Datensätze"}],"responses":{"200":{"description":"Liste der Kategorien","content":{"application/json":{"schema":{"type":"object","properties":{"categories":{"type":"array","items":{"$ref":"#/components/schemas/Category"}}}}}}},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"}}},"post":{"tags":["Kategorien"],"summary":"Kategorie anlegen","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CategoryCreate"}}}},"responses":{"201":{"description":"Angelegt","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Category"}}}},"400":{"description":"Ungültiger Request-Body"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"409":{"description":"Name bereits vergeben"}}}},"/categories/{id}":{"get":{"tags":["Kategorien"],"summary":"Kategorie lesen","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Kategorie","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Category"}}}},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Nicht gefunden"}}},"patch":{"tags":["Kategorien"],"summary":"Kategorie ändern","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CategoryUpdate"}}}},"responses":{"200":{"description":"Geändert","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Category"}}}},"400":{"description":"Ungültiger Request-Body"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Nicht gefunden"},"409":{"description":"Name bereits vergeben"}}},"delete":{"tags":["Kategorien"],"summary":"Kategorie löschen","description":"Nur möglich, wenn keine Budgets, Verträge, Anträge oder Investitionen daran hängen.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Gelöscht"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Nicht gefunden"},"409":{"description":"Wird noch verwendet"}}}},"/fiscal-years":{"get":{"tags":["Fiskaljahre"],"summary":"Fiskaljahre auflisten","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":5000,"default":1000},"description":"Maximale Anzahl Datensätze"}],"responses":{"200":{"description":"Liste der Fiskaljahre, neueste zuerst","content":{"application/json":{"schema":{"type":"object","properties":{"fiscalYears":{"type":"array","items":{"$ref":"#/components/schemas/FiscalYear"}}}}}}},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"}}},"post":{"tags":["Fiskaljahre"],"summary":"Fiskaljahr anlegen","description":"Die Bezeichnung muss eindeutig sein, der Zeitraum darf sich nicht mit einem bestehenden Jahr überschneiden.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FiscalYearCreate"}}}},"responses":{"201":{"description":"Angelegt","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FiscalYear"}}}},"400":{"description":"Ungültiger Request-Body oder startDate nicht vor endDate"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"409":{"description":"Bezeichnung bereits vergeben oder Zeitraum überschneidet sich"}}}},"/fiscal-years/{id}":{"get":{"tags":["Fiskaljahre"],"summary":"Fiskaljahr lesen","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Fiskaljahr","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FiscalYear"}}}},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Nicht gefunden"}}},"patch":{"tags":["Fiskaljahre"],"summary":"Fiskaljahr ändern oder abschließen","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FiscalYearUpdate"}}}},"responses":{"200":{"description":"Geändert","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FiscalYear"}}}},"400":{"description":"Ungültiger Request-Body"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Nicht gefunden"},"409":{"description":"Bezeichnung bereits vergeben"}}}},"/budgets":{"get":{"tags":["Budgets"],"summary":"Budget-Matrix lesen","description":"Ohne Parameter wird das aktive Fiskaljahr geliefert.","parameters":[{"name":"fiscalYear","in":"query","required":false,"schema":{"type":"string"},"description":"Bezeichnung des Fiskaljahres, z. B. 2026"}],"responses":{"200":{"description":"Budget-Matrix des Fiskaljahres","content":{"application/json":{"schema":{"type":"object","properties":{"fiscalYear":{"type":"string","example":"2026"},"budgets":{"type":"array","items":{"$ref":"#/components/schemas/BudgetCell"}}}}}}},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Fiskaljahr nicht gefunden bzw. keines vorhanden"}}},"put":{"tags":["Budgets"],"summary":"Planwert setzen","description":"Setzt den Planwert einer Budget-Zelle und legt die Zelle bei Bedarf an. Die Zelle wird über Fiskaljahr-Bezeichnung, Kostenstellen-Code und Kategorie-Name adressiert.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BudgetPlanSet"}}}},"responses":{"200":{"description":"Planwert gesetzt","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BudgetPlanResult"}}}},"400":{"description":"Ungültiger Request-Body"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Fiskaljahr, Kostenstelle oder Kategorie nicht gefunden"},"409":{"description":"Fiskaljahr ist abgeschlossen"}}}},"/actuals":{"get":{"tags":["Ist-Buchungen"],"summary":"Ist-Buchungen auflisten","description":"Neueste Buchungen zuerst.","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":5000,"default":1000},"description":"Maximale Anzahl Datensätze"}],"responses":{"200":{"description":"Liste der Ist-Buchungen","content":{"application/json":{"schema":{"type":"object","properties":{"actuals":{"type":"array","items":{"$ref":"#/components/schemas/ActualListItem"}}}}}}},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"}}},"post":{"tags":["Ist-Buchungen"],"summary":"Ist-Buchung anlegen","description":"Kostenstelle und Kategorie werden über ihre fachlichen Schlüssel aufgelöst; die Budget-Zelle im offenen Fiskaljahr zum Buchungsdatum wird bei Bedarf angelegt.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActualCreate"}}}},"responses":{"201":{"description":"Angelegt","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"}}}}}},"400":{"description":"Ungültiger Request-Body"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Kostenstelle, Kategorie oder offenes Fiskaljahr nicht gefunden"}}}},"/actuals/bulk":{"post":{"tags":["Ist-Buchungen"],"summary":"Ist-Buchungen im Massenimport anlegen","description":"Importiert bis zu 1000 Ist-Buchungen in einem Request. Fehler in einzelnen Zeilen (unbekannte Kostenstelle/Kategorie, kein offenes Fiskaljahr, ungültiges Feld) brechen den Request nicht ab, sondern landen in errors — die übrigen Zeilen werden trotzdem verarbeitet; nur ein strukturell ungültiger Body (rows fehlt/kein Array/leer/über 1000 Zeilen, dryRun oder dedupe falscher Typ) ergibt 400. Mit dryRun:true wird nur geprüft und gezählt, es wird nichts geschrieben. Dubletten werden wie beim Datei-Import über einen Zeilen-Hash (importKey) je Mandant erkannt und je nach dedupe übersprungen oder als Fehler gemeldet. Angelegte Buchungen erhalten source CSV_IMPORT (nicht MANUAL) — wie jeder andere Import sind sie über PATCH/DELETE nicht änderbar, ein Korrekturimport muss erneut über diesen Endpunkt erfolgen.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActualBulkRequest"}}}},"responses":{"200":{"description":"Ergebnis des Imports (bzw. Probelaufs)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActualBulkResult"}}}},"400":{"description":"Ungültiger Request-Body (rows fehlt, kein Array, leer oder über 1000 Zeilen)"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"}}}},"/actuals/{id}":{"get":{"tags":["Ist-Buchungen"],"summary":"Ist-Buchung lesen","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Ist-Buchung","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Actual"}}}},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Nicht gefunden"}}},"patch":{"tags":["Ist-Buchungen"],"summary":"Ist-Buchung ändern","description":"Nur manuell erfasste Buchungen (source MANUAL) sind änderbar.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActualUpdate"}}}},"responses":{"200":{"description":"Geändert","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Actual"}}}},"400":{"description":"Ungültiger Request-Body oder Datum außerhalb des Fiskaljahres"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Nicht gefunden"},"409":{"description":"Buchung stammt aus einer automatischen Quelle und ist nicht änderbar"}}},"delete":{"tags":["Ist-Buchungen"],"summary":"Ist-Buchung löschen","description":"Nur manuell erfasste Buchungen (source MANUAL) sind löschbar.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Gelöscht"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Nicht gefunden"},"409":{"description":"Buchung stammt aus einer automatischen Quelle und ist nicht löschbar"}}}},"/commitments":{"get":{"tags":["Obligo"],"summary":"Obligo auflisten","description":"Neueste Verpflichtungen zuerst.","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":5000,"default":1000},"description":"Maximale Anzahl Datensätze"}],"responses":{"200":{"description":"Liste der Verpflichtungen","content":{"application/json":{"schema":{"type":"object","properties":{"commitments":{"type":"array","items":{"$ref":"#/components/schemas/Commitment"}}}}}}},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"}}},"post":{"tags":["Obligo"],"summary":"Verpflichtung anlegen","description":"Kostenstelle und Kategorie werden über ihre fachlichen Schlüssel aufgelöst; die Budget-Zelle im offenen Fiskaljahr zum Datum wird bei Bedarf angelegt.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommitmentCreate"}}}},"responses":{"201":{"description":"Angelegt","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Commitment"}}}},"400":{"description":"Ungültiger Request-Body"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Kostenstelle, Kategorie oder offenes Fiskaljahr nicht gefunden"}}}},"/commitments/{id}":{"get":{"tags":["Obligo"],"summary":"Verpflichtung lesen","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Verpflichtung","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Commitment"}}}},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Nicht gefunden"}}},"patch":{"tags":["Obligo"],"summary":"Verpflichtung ändern","description":"Nur manuell erfasste Verpflichtungen sind änderbar — Vertrags-Obligo wird am Vertrag gepflegt.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommitmentUpdate"}}}},"responses":{"200":{"description":"Geändert","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Commitment"}}}},"400":{"description":"Ungültiger Request-Body oder Datum außerhalb des Fiskaljahres"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Nicht gefunden"},"409":{"description":"Verpflichtung stammt aus einem Vertrag und ist nicht änderbar"}}},"delete":{"tags":["Obligo"],"summary":"Verpflichtung löschen","description":"Nur manuell erfasste Verpflichtungen sind löschbar.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Gelöscht"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Nicht gefunden"},"409":{"description":"Verpflichtung stammt aus einem Vertrag und ist nicht löschbar"}}}},"/contracts":{"get":{"tags":["Verträge"],"summary":"Verträge auflisten","description":"Liefert alle Verträge des Mandanten (nach Name sortiert) inklusive Kündigungs-Radar: nächstes Vertragsende, letzter Kündigungstermin, Ampel und Restlaufzeit.","responses":{"200":{"description":"Liste der Verträge","content":{"application/json":{"schema":{"type":"object","properties":{"contracts":{"type":"array","items":{"$ref":"#/components/schemas/Contract"}}}}}}},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"}}},"post":{"tags":["Verträge"],"summary":"Vertrag anlegen","description":"Kostenstelle und Kategorie werden über ihre fachlichen Schlüssel (Code bzw. Name) aufgelöst und müssen bereits existieren.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContractCreate"}}}},"responses":{"201":{"description":"Angelegt","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contract"}}}},"400":{"description":"Ungültiger Request-Body"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Kostenstelle oder Kategorie nicht gefunden"}}}},"/contracts/{id}":{"get":{"tags":["Verträge"],"summary":"Vertrag lesen","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Vertrag","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contract"}}}},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Nicht gefunden"}}},"patch":{"tags":["Verträge"],"summary":"Vertrag ändern","description":"Spiegelverträge aus einer MSP-Verrechnung werden vom Dienstleister gepflegt und sind gesperrt (409).","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContractUpdate"}}}},"responses":{"200":{"description":"Geändert","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contract"}}}},"400":{"description":"Ungültiger Request-Body"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Vertrag, Kostenstelle oder Kategorie nicht gefunden"},"409":{"description":"Spiegelvertrag wird vom Dienstleister gepflegt"}}},"delete":{"tags":["Verträge"],"summary":"Vertrag löschen","description":"Nur möglich, wenn keine abhängigen Daten vorhanden sind (Dokumente, Obligos, Rechnungen, Lizenz-SKUs, automatische Buchungen, Spiegelverträge).","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Gelöscht"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Nicht gefunden"},"409":{"description":"Abhängige Daten vorhanden oder Spiegelvertrag"}}}},"/suppliers":{"get":{"tags":["Lieferanten"],"summary":"Lieferanten auflisten","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":5000,"default":1000},"description":"Maximale Anzahl Datensätze"},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["ACTIVE","INACTIVE","BLOCKED"]},"description":"Nur Lieferanten mit diesem Status"}],"responses":{"200":{"description":"Liste der Lieferanten","content":{"application/json":{"schema":{"type":"object","properties":{"suppliers":{"type":"array","items":{"$ref":"#/components/schemas/Supplier"}}}}}}},"400":{"description":"Ungültiger Statusfilter"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"}}},"post":{"tags":["Lieferanten"],"summary":"Lieferant anlegen","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierCreate"}}}},"responses":{"201":{"description":"Angelegt","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Supplier"}}}},"400":{"description":"Ungültiger Request-Body"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"409":{"description":"Lieferantenname bereits vergeben"}}}},"/suppliers/{id}":{"get":{"tags":["Lieferanten"],"summary":"Lieferant lesen","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Lieferant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Supplier"}}}},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Nicht gefunden"}}},"patch":{"tags":["Lieferanten"],"summary":"Lieferant ändern","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierUpdate"}}}},"responses":{"200":{"description":"Geändert","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Supplier"}}}},"400":{"description":"Ungültiger Request-Body"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Nicht gefunden"},"409":{"description":"Lieferantenname bereits vergeben"}}},"delete":{"tags":["Lieferanten"],"summary":"Lieferant löschen","description":"Nur möglich, wenn keine Verträge, Anwendungen, Investitionen oder Rechnungen darauf verweisen.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Gelöscht"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Nicht gefunden"},"409":{"description":"Wird noch verwendet"}}}},"/invoices":{"get":{"tags":["Rechnungen"],"summary":"Rechnungen auflisten","description":"Neueste Rechnungen zuerst. Der Rechnungseingang ist über die API nur lesbar — neue Rechnungen entstehen ausschließlich über Upload oder E-Rechnungs-Postfach.","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":5000,"default":1000},"description":"Maximale Anzahl Datensätze"},{"name":"contractId","in":"query","required":false,"schema":{"type":"string"},"description":"Nur Rechnungen, die diesem Vertrag zugeordnet sind"},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["RECEIVED","MATCHED","BOOKED","DISMISSED"]},"description":"Nur Rechnungen mit diesem Bearbeitungsstand"}],"responses":{"200":{"description":"Liste der Rechnungen","content":{"application/json":{"schema":{"type":"object","properties":{"invoices":{"type":"array","items":{"$ref":"#/components/schemas/Invoice"}}}}}}},"400":{"description":"Ungültiger Statusfilter"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"}}}},"/invoices/{id}":{"get":{"tags":["Rechnungen"],"summary":"Rechnung lesen","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Rechnung","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Invoice"}}}},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Nicht gefunden"}}}},"/investments":{"get":{"tags":["Investitionen"],"summary":"Investitionen auflisten","description":"Neueste Anschaffungen zuerst.","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":5000,"default":1000},"description":"Maximale Anzahl Datensätze"}],"responses":{"200":{"description":"Liste der Investitionen","content":{"application/json":{"schema":{"type":"object","properties":{"investments":{"type":"array","items":{"$ref":"#/components/schemas/Investment"}}}}}}},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"}}},"post":{"tags":["Investitionen"],"summary":"Investition anlegen","description":"Kostenstelle und Kategorie werden über ihre fachlichen Schlüssel (Code bzw. Name) aufgelöst. Die automatische Abschreibungs-Buchung wird bewusst nicht über die API aktiviert.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvestmentCreate"}}}},"responses":{"201":{"description":"Angelegt","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Investment"}}}},"400":{"description":"Ungültiger Request-Body"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Kostenstelle oder Kategorie nicht gefunden"}}}},"/investments/{id}":{"get":{"tags":["Investitionen"],"summary":"Investition lesen","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Investition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Investment"}}}},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Nicht gefunden"}}},"patch":{"tags":["Investitionen"],"summary":"Investition ändern","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvestmentUpdate"}}}},"responses":{"200":{"description":"Geändert","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Investment"}}}},"400":{"description":"Ungültiger Request-Body"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Investition, Kostenstelle oder Kategorie nicht gefunden"},"409":{"description":"Abschreibungen bereits gebucht — Betrag, Datum, Dauer und Intervall sind gesperrt"}}},"delete":{"tags":["Investitionen"],"summary":"Investition löschen","description":"Nur möglich, solange keine Abschreibungen gebucht wurden.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Gelöscht"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Nicht gefunden"},"409":{"description":"Abschreibungen bereits gebucht"}}}},"/requests":{"get":{"tags":["Budgetanträge"],"summary":"Budgetanträge auflisten","description":"Neueste Anträge zuerst. Budgetanträge sind über die API ausschließlich lesbar: Einreichen und Genehmigen laufen über den mehrstufigen Freigabe-Workflow der Anwendung (Antragsteller, Freigabestufen, Auto-Genehmigungsgrenze und Benachrichtigungen).","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":5000,"default":1000},"description":"Maximale Anzahl Datensätze"},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["PENDING","APPROVED","REJECTED"]},"description":"Nur Anträge mit diesem Status"}],"responses":{"200":{"description":"Liste der Budgetanträge","content":{"application/json":{"schema":{"type":"object","properties":{"requests":{"type":"array","items":{"$ref":"#/components/schemas/BudgetRequest"}}}}}}},"400":{"description":"Ungültiger Statusfilter"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"}}}},"/requests/{id}":{"get":{"tags":["Budgetanträge"],"summary":"Budgetantrag lesen","description":"Liefert den Antrag inklusive Status, aktueller Freigabestufe und Entscheidungshistorie.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Budgetantrag","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BudgetRequestDetail"}}}},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Nicht gefunden"}}}},"/applications":{"get":{"tags":["Applikationen"],"summary":"Applikationen auflisten","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":5000,"default":1000},"description":"Maximale Anzahl Datensätze"}],"responses":{"200":{"description":"Liste der Applikationen","content":{"application/json":{"schema":{"type":"object","properties":{"applications":{"type":"array","items":{"$ref":"#/components/schemas/Application"}}}}}}},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"}}},"post":{"tags":["Applikationen"],"summary":"Applikation anlegen","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplicationCreate"}}}},"responses":{"201":{"description":"Angelegt","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Application"}}}},"400":{"description":"Ungültiger Request-Body"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Kostenstelle oder Lieferant nicht gefunden"},"409":{"description":"App-Code bereits vergeben"}}}},"/applications/{id}":{"get":{"tags":["Applikationen"],"summary":"Applikation lesen","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Applikation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Application"}}}},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Nicht gefunden"}}},"patch":{"tags":["Applikationen"],"summary":"Applikation ändern","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplicationUpdate"}}}},"responses":{"200":{"description":"Geändert","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Application"}}}},"400":{"description":"Ungültiger Request-Body"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Nicht gefunden, Kostenstelle oder Lieferant unbekannt"},"409":{"description":"App-Code bereits vergeben"}}},"delete":{"tags":["Applikationen"],"summary":"Applikation löschen","description":"Nur möglich, wenn keine Verträge, Lizenzen, Cloud-Kosten, Scope-Zuordnungen oder VMs daran hängen.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Gelöscht"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Nicht gefunden"},"409":{"description":"Wird noch verwendet"}}}},"/savings":{"get":{"tags":["Einsparungen"],"summary":"Einsparungs-Empfehlungen auflisten","description":"Empfehlungen werden vor dem Listing aus den Quelldaten (Lizenzen, Vertragsradar, Rechnungsabweichungen) abgeglichen.","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":5000,"default":1000},"description":"Maximale Anzahl Datensätze"},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["OPEN","DONE","DISMISSED"]},"description":"Nur Empfehlungen mit diesem Status"}],"responses":{"200":{"description":"Liste der Empfehlungen","content":{"application/json":{"schema":{"type":"object","properties":{"savings":{"type":"array","items":{"$ref":"#/components/schemas/SavingsRecommendation"}}}}}}},"400":{"description":"Ungültiger status-Filter"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"}}}},"/savings/{id}":{"get":{"tags":["Einsparungen"],"summary":"Einsparungs-Empfehlung lesen","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Empfehlung","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SavingsRecommendation"}}}},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Nicht gefunden"}}},"patch":{"tags":["Einsparungen"],"summary":"Status oder realisierten Wert setzen","description":"Erlaubte Übergänge: OPEN → DONE (realizedMonthly optional, sonst Schätzwert), OPEN → DISMISSED, DONE|DISMISSED → OPEN (setzt realizedMonthly zurück). realizedMonthly ohne status ist nur bei bestehendem Status DONE erlaubt (korrigiert den realisierten Wert).","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SavingsUpdate"}}}},"responses":{"200":{"description":"Geändert","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SavingsRecommendation"}}}},"400":{"description":"Ungültiger Request-Body"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Nicht gefunden"},"409":{"description":"Statusübergang nicht erlaubt"}}}},"/licenses":{"get":{"tags":["Lizenzen"],"summary":"Lizenz-SKUs auflisten","description":"Über alle Quellen (Integrationen + manuell erfasste Lizenzen). Nur lesend — Pflege läuft über Sync bzw. die App.","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":5000,"default":1000},"description":"Maximale Anzahl Datensätze"},{"name":"provider","in":"query","required":false,"schema":{"type":"string","enum":["M365","GOOGLE","ADOBE","MANUAL"]},"description":"Nur Lizenzen dieser Quelle"}],"responses":{"200":{"description":"Liste der Lizenz-SKUs","content":{"application/json":{"schema":{"type":"object","properties":{"licenses":{"type":"array","items":{"$ref":"#/components/schemas/License"}}}}}}},"400":{"description":"Ungültiger provider-Filter"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"}}}},"/users":{"get":{"tags":["Benutzer"],"summary":"Benutzer auflisten","description":"Nur lesend — Benutzerverwaltung läuft über die App bzw. SCIM.","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":5000,"default":1000},"description":"Maximale Anzahl Datensätze"}],"responses":{"200":{"description":"Liste der Benutzer","content":{"application/json":{"schema":{"type":"object","properties":{"users":{"type":"array","items":{"$ref":"#/components/schemas/User"}}}}}}},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"}}}},"/discovered-apps":{"get":{"tags":["SaaS-Discovery"],"summary":"Discovery-Ergebnisse auflisten","description":"Über SaaS-Discovery erkannte Anwendungen. Nur lesend — die Erkennung läuft über den Entra-Abgleich bzw. die eigenständigen Discovery-Konnektoren (GitHub, Atlassian, Zoom).","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":5000,"default":1000},"description":"Maximale Anzahl Datensätze"},{"name":"provider","in":"query","required":false,"schema":{"type":"string","enum":["ENTRA","GITHUB","ATLASSIAN","ZOOM"]},"description":"Nur Ergebnisse dieser Quelle"},{"name":"ignored","in":"query","required":false,"schema":{"type":"boolean"},"description":"Nur ignorierte bzw. nur nicht ignorierte Einträge"},{"name":"unmatched","in":"query","required":false,"schema":{"type":"boolean"},"description":"true: nur Einträge ohne zugeordnete Applikation"}],"responses":{"200":{"description":"Liste der Discovery-Ergebnisse","content":{"application/json":{"schema":{"type":"object","properties":{"discoveredApps":{"type":"array","items":{"$ref":"#/components/schemas/DiscoveredApp"}}}}}}},"400":{"description":"Ungültiger Filter"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"}}}},"/cost-per-employee":{"get":{"tags":["IT-Kosten pro Mitarbeiter"],"summary":"IT-Kosten pro Mitarbeiter je Kostenstelle","description":"Momentaufnahme der laufenden IT-Monatskosten „wie heute konfiguriert\" — keine Ist-Buchungen eines bestimmten Monats. Zwei Töpfe: direkte, personengebundene Lizenzkosten je Kostenstelle sowie eine Umlage der übrigen Kosten (Verträge, Cloud, VMs, AfA, Lizenz-Rest) nach dem in den Mandanteneinstellungen gewählten Aufteilungsschlüssel (allocationKey). Menschen ohne Kostenstelle laufen unter unassigned und sind keine Umlagebasis.","responses":{"200":{"description":"Bericht je Kostenstelle","content":{"application/json":{"schema":{"type":"object","properties":{"allocationKey":{"type":"string","enum":["HEADCOUNT","LICENSED_USERS","MANUAL_WEIGHTS","EQUAL"],"description":"Aktiver Aufteilungsschlüssel"},"totalMonthlyEur":{"type":"number","description":"Gesamte IT-Monatskosten = Umlagemasse + alle direkten Kosten"},"allocatableEur":{"type":"number","description":"Umlagemasse ohne Personenbezug"},"headcountTotal":{"type":"integer","description":"Summe der Personenbasis über alle Zeilen"},"unassigned":{"type":"object","description":"Direkte Lizenzkosten von Menschen ohne Kostenstelle — keine Umlagebasis","properties":{"directEur":{"type":"number"},"users":{"type":"integer"}}},"rows":{"type":"array","items":{"$ref":"#/components/schemas/CostPerEmployeeRow"}}}}}}},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"}}}},"/employee-costs":{"get":{"tags":["Mitarbeiter-Kosten-Verzeichnis"],"summary":"Mitarbeiter-Kosten-Verzeichnis (personenbezogen)","description":"Momentaufnahme „wie heute konfiguriert\" je Person (SCIM-verwaltete App-User plus lizenzierte Benutzer aller Provider-Syncs, gemergt): direkte Lizenzkosten plus Umlage-Anteil der eigenen Kostenstelle. ENTHÄLT PERSONENBEZOGENE DATEN (Name, E-Mail) — nur mit dem eigenen Scope `employee-costs` abrufbar, unabhängig von anderen Ressourcen-Rechten des Keys. Der interne Personenschlüssel wird nie ausgeliefert.","parameters":[{"name":"group","in":"query","required":false,"schema":{"type":"string"},"description":"Filter auf den Gruppenwert (aktuelles Attribut: department, costCenter, division, organization, employeeNumber, officeLocation, companyName, city, country, jobTitle — einstellbar je Mandant). 'none' liefert nur Personen ohne Zuordnung."},{"name":"q","in":"query","required":false,"schema":{"type":"string"},"description":"Freitextsuche auf Name/E-Mail (contains, ohne Groß-/Kleinschreibung)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":5000,"default":1000},"description":"Maximale Anzahl Zeilen"}],"responses":{"200":{"description":"Verzeichnis (gefiltert nach group/q, falls angegeben)","content":{"application/json":{"schema":{"type":"object","properties":{"groupAttribute":{"type":"string","enum":["department","costCenter","division","organization","employeeNumber","officeLocation","companyName","city","country","jobTitle"],"description":"Aktives Gruppierungsattribut des Mandanten"},"totals":{"type":"object","description":"Summen der Trefferliste (vor limit); ohne Filter = ganzes Verzeichnis","properties":{"persons":{"type":"integer"},"directEur":{"type":"number"},"allocatedEur":{"type":"number"},"totalEur":{"type":"number"}}},"rows":{"type":"array","items":{"$ref":"#/components/schemas/EmployeeCostRow"}}}}}}},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"}}}},"/psa-tickets":{"get":{"tags":["PSA/RMM"],"summary":"PSA-Tickets auflisten","description":"Tickets aus dem angebundenen PSA-System. Nur lesend — Tickets entstehen ausschließlich über den PSA-Sync. Nicht-MSP-Mandanten erhalten mangels Daten eine leere Liste.","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":5000,"default":1000},"description":"Maximale Anzahl Datensätze"},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["open","closed"]},"description":"'open': Tickets ohne closedAt, 'closed': Tickets mit gesetztem closedAt"},{"name":"client","in":"query","required":false,"schema":{"type":"string"},"description":"Nur Tickets dieses Kunden-Mandanten (childTenantId)"}],"responses":{"200":{"description":"Liste der PSA-Tickets","content":{"application/json":{"schema":{"type":"object","properties":{"psaTickets":{"type":"array","items":{"$ref":"#/components/schemas/PsaTicket"}}}}}}},"400":{"description":"Ungültiger Filter"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"}}}},"/rmm-devices":{"get":{"tags":["PSA/RMM"],"summary":"RMM-Geräte auflisten","description":"Geräte aus dem angebundenen RMM-System. Nur lesend — Geräte entstehen ausschließlich über den RMM-Sync. Nicht-MSP-Mandanten erhalten mangels Daten eine leere Liste.","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":5000,"default":1000},"description":"Maximale Anzahl Datensätze"},{"name":"client","in":"query","required":false,"schema":{"type":"string"},"description":"Nur Geräte dieses Kunden-Mandanten (childTenantId)"},{"name":"online","in":"query","required":false,"schema":{"type":"boolean"},"description":"Nur online bzw. nur offline gemeldete Geräte"}],"responses":{"200":{"description":"Liste der RMM-Geräte","content":{"application/json":{"schema":{"type":"object","properties":{"rmmDevices":{"type":"array","items":{"$ref":"#/components/schemas/RmmDevice"}}}}}}},"400":{"description":"Ungültiger Filter"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"}}}},"/anomalies":{"get":{"tags":["Kosten-Anomalien"],"summary":"Kosten-Anomalie-Befunde auflisten","description":"Befunde werden vor dem Listing wie in der App gegen die Ist-Daten neu bewertet (refreshCostAnomalies). Ist der Kosten-Anomalie-Alarm im Mandanten nicht aktiviert, bleibt der Bestand unverändert (i. d. R. leer).","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":5000,"default":1000},"description":"Maximale Anzahl Datensätze"},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["OPEN","DISMISSED"],"default":"OPEN"},"description":"Nur Befunde mit diesem Status (Default OPEN)"},{"name":"dimension","in":"query","required":false,"schema":{"type":"string","enum":["COST_CENTER","CONTRACT","CLOUD_CONNECTION"]},"description":"Nur Befunde dieser Dimension"},{"name":"month","in":"query","required":false,"schema":{"type":"string","example":"2026-06"},"description":"Nur Befunde dieses Monats (YYYY-MM)"}],"responses":{"200":{"description":"Liste der Kosten-Anomalie-Befunde","content":{"application/json":{"schema":{"type":"object","properties":{"anomalies":{"type":"array","items":{"$ref":"#/components/schemas/CostAnomaly"}}}}}}},"400":{"description":"Ungültiger Filter"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"}}}},"/webhooks":{"get":{"tags":["Webhooks"],"summary":"Webhook-Abos auflisten","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":5000,"default":1000},"description":"Maximale Anzahl Datensätze"}],"responses":{"200":{"description":"Liste der Abos (ohne Secret)","content":{"application/json":{"schema":{"type":"object","properties":{"webhooks":{"type":"array","items":{"$ref":"#/components/schemas/Webhook"}}}}}}},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"}}},"post":{"tags":["Webhooks"],"summary":"Webhook-Abo anlegen","description":"Die Antwort enthält das Signatur-Secret einmalig im Klartext — danach ist es nicht mehr abrufbar.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookCreate"}}}},"responses":{"201":{"description":"Angelegt (inkl. Secret)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookCreated"}}}},"400":{"description":"Ungültiger Request-Body (z. B. keine https-URL, unbekannte Events)"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"409":{"description":"URL bereits als Abo registriert"}}}},"/webhooks/{id}":{"get":{"tags":["Webhooks"],"summary":"Webhook-Abo lesen","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Abo (ohne Secret)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Webhook"}}}},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Nicht gefunden"}}},"patch":{"tags":["Webhooks"],"summary":"Webhook-Abo ändern","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookUpdate"}}}},"responses":{"200":{"description":"Geändert","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Webhook"}}}},"400":{"description":"Ungültiger Request-Body"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Nicht gefunden"},"409":{"description":"URL bereits als Abo registriert"}}},"delete":{"tags":["Webhooks"],"summary":"Webhook-Abo löschen","description":"Löscht kaskadierend auch das Zustellprotokoll des Abos.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Gelöscht"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Nicht gefunden"}}}},"/webhooks/{id}/test":{"post":{"tags":["Webhooks"],"summary":"Test-Ping an ein Abo senden","description":"Sendet sofort einen synthetischen Test-Event ({test: true, sentAt}) an genau dieses Abo, unabhängig vom Event-Filter, und liefert das Zustellergebnis zurück.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"202":{"description":"Zustellversuch durchgeführt","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookTestResult"}}}},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Nicht gefunden"}}}},"/webhooks/{id}/deliveries":{"get":{"tags":["Webhooks"],"summary":"Zustellprotokoll eines Abos","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["PENDING","DELIVERED","FAILED","DEAD"]},"description":"Nur Zustellungen mit diesem Status"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"description":"Maximale Anzahl (max. 200)"}],"responses":{"200":{"description":"Zustellungen, jüngste zuerst","content":{"application/json":{"schema":{"type":"object","properties":{"deliveries":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDelivery"}}}}}}},"400":{"description":"Ungültiger Statusfilter"},"401":{"description":"Kein oder ungültiger API-Key"},"403":{"description":"Key ohne passenden Scope oder Funktion nicht im Paket enthalten"},"404":{"description":"Nicht gefunden"}}}}}}