Eviworx
Docs

SLA Management API

Das Unified SLA System überwacht Service-Level-Ziele für Tickets, Incidents und Problems: Reaktions- und Lösungsziele je Priorität und Policy, Fristen nach Geschäftszeiten inklusive Feiertagen, Pause bei ON_HOLD, offenem Incident-Link oder offenem Sub-Ticket, mehrstufige Eskalation und einen laufenden SLA-Monitor. Auch der Status (OK/WARNING/BREACH/CRITICAL) wird nach Geschäftszeiten berechnet.

⏱️
Funktionen
✓ Für Tickets, Incidents und Problems
✓ Reaktions- und Lösungsziel je Priorität
✓ Geschäftszeiten mit Zeitzone und Feiertagen
✓ Status mit einstellbaren Schwellwerten (sla-settings)
✓ Pause bei ON_HOLD, Incident-Link, Sub-Ticket
✓ Mehrstufige Eskalation (3 Trigger-Typen)
✓ Wiederholte Erinnerungen (repeatConfig)
✓ Laufender SLA-Monitor (Hintergrund-Job)
✓ Historie-Report (Compliance, MTTA/MTTR)
✓ Benachrichtigungen (SLA_WARNING, SLA_BREACH …)

Status-Modell

Der prozentuale Verbrauch (percentUsed) wird business-hours-genau aus „verbleibender Geschäftszeit bis zur Deadline" berechnet. Bei Pause friert der Status zum pausedAt-Zeitpunkt ein.

StatusBedingung
OKpercentUsed < 80%
WARNINGpercentUsed ≥ 80%
BREACHpercentUsed ≥ 100% (Deadline überschritten)
CRITICAL> 60 Minuten nach Breach
CANCELLEDTracking storniert (Entity gelöscht/irrelevant)

Schwellwerte konfigurieren

Beide Schwellen sind einstellbar; 80 % und 60 Minuten sind die Standardwerte. Sie liegen unter dem Settings-Key sla-settings (Admin-Center → Service-Konfiguration → SLA & Eskalation → Schwellwerte). Lesen und Schreiben erfordern settings.editSLA:

PUT /api/settings/sla-settings
{
  "warningThresholdPercent": 80,     // 50–99
  "criticalThresholdMinutes": 60     // 5–1440
}
EigenschaftVerhalten
WirkungGilt für die serverseitige Statusberechnung (Dashboard, Ticket-Sidebar, Monitor/Eskalation) — der Status kommt überall aus dem Backend.
WirksamkeitEine Änderung über die Settings-API wirkt sofort.
RobustheitFehlt das Setting oder liegen Werte außerhalb der Grenzen, gelten die Standardwerte 80 / 60. Die Statusberechnung läuft also immer.

Public Endpoints

Basis-Pfad /api/sla. Das SLA-Dashboard ist eine Arbeitssicht für Agenten: Mit viewAll zeigt es alle Trackings, ohne viewAll nur die der Tickets, die der Agent sehen darf.

MethodEndpointPermissionBeschreibung
GET/api/sla/trackingstickets.viewAlltickets.viewOwnAktive Trackings (gefiltert, mit Metriken)
GET/api/sla/statstickets.viewAlltickets.viewOwnAggregierte Counts pro Status
GET/api/sla/reporttickets.viewAll (bzw. incidents/problems.viewAll je entityType)Historische Compliance-Quoten (met/missed, MTTA/MTTR)
GET/api/sla/holidaysjede AuthFeiertagskalender (?year, ?country) → {data}

Berechtigung: trackings und stats erfordern tickets.viewAll oder tickets.viewOwn. Ohne viewAll ist zusätzlich ein aktives Agenten-Profil nötig, Endanwender haben also keinen Zugriff. Die Antwort enthält dann nur TICKET-Trackings, und zwar für genau die Tickets, die der Agent auch in der Ticket-Liste sieht (eigene, über Gruppe, Mailbox oder Beteiligung, inklusive Vertretung). Incident- und Problem-Trackings erfordern incidents.viewAll bzw. problems.viewAll.

Trackings, deren Entität der Aufrufer nicht sehen darf, erscheinen nicht in der Antwort; total zählt nur die sichtbaren.

GET /trackings — Query-Parameter

ParameterWerte
entityTypeTICKET, INCIDENT, PROBLEM
statusOK, WARNING, BREACH, CRITICAL, CANCELLED, PAUSED
searchEntity-Nummer oder Titel (max. 200 Zeichen, case-insensitive) — serverseitig vor der Pagination
limit1–100 (Default 25)
offsetPagination-Offset

Statusfilter, Sortierung (verbrauchte Zeit absteigend) und Pagination laufen vollständig serverseitig; der Statusfilter nutzt den vom SLA-Monitor berechneten Status. Trackings, die der Monitor noch nicht bewertet hat, gelten als OK.

Pausierte zählen getrennt: status=PAUSED filtert alle pausierten Uhren — unabhängig davon, welcher Status bei der Pause eingefroren wurde. Umgekehrt schließen die Statusfilter OK, WARNING, BREACH und CRITICAL Pausierte aus. So stimmen Liste und Zähler aus /stats überein. Ohne Statusfilter sind Pausierte in der Liste enthalten.

GET /api/sla/trackings?entityType=TICKET&status=WARNING&search=TK-000123&limit=20
{
  "data": [
    {
      "id": "clx-tracking",
      "entityType": "TICKET",
      "entityId": "clx-ticket",
      "entityNumber": "TK-000123",
      "entityTitle": "Database connection timeout",
      "priority": "HIGH",
      "status": "WARNING",
      "percentUsed": 85.5,
      "responseMet": true,
      "resolutionMet": null,
      "responseDeadline": "2026-01-28T10:00:00.000Z",
      "resolutionDeadline": "2026-01-28T17:00:00.000Z",
      "isPaused": false,
      "pauseReason": null,
      "currentEscalationLevel": 1,
      "minutesToDeadline": 83,
      "minutesAfterBreach": null
    }
  ],
  "pagination": { "total": 42, "limit": 20, "offset": 0 }
}

pauseReason nennt den Grund einer LAUFENDEN Pause und ist sonst null. Bei den beiden Verknüpfungs-Gründen trägt der Wert die Nummer der Gegenseite: LINKED_TO_INCIDENT:INC-000042 für einen offenen Incident-Link, CHILD_TICKET:TK-000456 für ein offenes Sub-Ticket. Eine Pause aus dem Status heraus (pauseOnStatus) trägt keinen solchen Wert. Welche Gründe es gibt und wann sie greifen, beschreibt SLA-System (Architektur).

GET /stats

GET /api/sla/stats
{ "total": 102, "ok": 80, "warning": 15, "breach": 5, "critical": 2, "paused": 7 }

stats zählt den AKTUELLEN Zustand (offene Trackings je Status, pausierte separat) — es ist keine Compliance-Kennzahl. Historische Erfüllungsquoten liefert ausschließlich /report.

GET /report

Historische Auswertung über abgeschlossene Trackings (resolvedAt im Zeitraum). Trackings mit excludeFromReporting oder Status CANCELLED gehen NICHT in die Quoten ein, werden aber informativ mitgezählt. Zeitraum entweder rollierend über days oder explizit über from/to.

ParameterWerte
days1–365 (Default 30, rollierend ab jetzt)
from / toISO-8601; from muss vor to liegen (sonst 400 VALIDATION_ERROR)
entityTypeTICKET, INCIDENT, PROBLEM (ohne Angabe: alle)
GET /api/sla/report?days=30
GET /api/sla/report?from=2026-06-01T00:00:00Z&to=2026-07-01T00:00:00Z&entityType=INCIDENT
{
  "from": "2026-06-16T00:00:00.000Z",
  "to": "2026-07-16T00:00:00.000Z",
  "totals": {
    "resolved": 412,
    "met": 383,
    "missed": 29,
    "compliancePct": 92.9,
    "responseMet": 396,
    "responseMissed": 16,
    "responseCompliancePct": 96.1,
    "excluded": 7,
    "cancelled": 3,
    "avgResponseMin": 24,
    "avgResolutionMin": 268
  },
  "byEntityType": [
    { "entityType": "TICKET", "met": 350, "missed": 24, "compliancePct": 93.6 }
  ],
  "byPriority": [
    { "entityType": "TICKET", "priority": "HIGH", "met": 88, "missed": 9, "responseMet": 94, "responseMissed": 3 }
  ],
  "scope": "global"
}
FeldBedeutung
compliancePctmet / (met + missed) · auf eine Nachkommastelle gerundet; ohne gewertete Trackings = 100
avgResponseMinMTTA: Ø responseAt − createdAt (Kalenderminuten); null ohne Datenbasis
avgResolutionMinMTTR: Ø resolvedAt − createdAt abzüglich Pausenzeit; null ohne Datenbasis
excluded / cancelledinformative Zähler, die nicht in die Quoten eingehen
scopeglobal = alle Daten (viewAll) · own = nur die für den Agenten sichtbaren Tickets. Wer die Zahlen anzeigt, sollte das Feld auswerten — eine 92%-Quote über die eigenen Tickets ist etwas völlig anderes als 92% über alle.

Die nötige Berechtigung hängt von entityType ab: INCIDENT erfordert incidents.viewAll, PROBLEM problems.viewAll, alles andere (auch der Gesamtreport ohne entityType) tickets.viewAll. Ohne viewAll gilt wie beim Dashboard die Agenten-Sicht: Mit tickets.viewOwn und aktivem Agenten-Profil liefert der Report die Historie der Tickets, die der Agent sehen darf (dieselbe Auswahl wie bei /trackings). Das gilt nur für Tickets; eine Incident- oder Problem-Historie erfordert immer das jeweilige viewAll (sonst 403).

Admin Endpoints

Basis-Pfad /api/sla/admin. Alle Endpoints erfordern settings.editSLA.

BereichEndpoints
SLA PoliciesGET /policies, GET /policies/:id, POST /policies, PATCH /policies/:id, DELETE /policies/:id
Escalation PoliciesGET /escalation-policies, GET /:id, POST, PATCH /:id, DELETE /:id
Business HoursGET /business-hours, GET /:id, POST, PATCH /:id, DELETE /:id
HolidaysGET /holidays, GET /:id, POST, POST /bulk, PATCH /:id, DELETE /:id, DELETE /year/:year
HelpersGET /agent-groups, GET /users (für Eskalations-Ziele)

Änderungen laufen per PATCH. Die Default-Policy wird über das Feld isDefault festgelegt: Beim Speichern mit isDefault=true verliert die bisherige Default-Policy desselben entityType diesen Status automatisch.

Änderungen an einer Business-Hours-Definition (Zeitplan, Zeitzone, Feiertagsbezug) wirken sofort auf die Deadline-Berechnung. Die Helfer-Listen für Eskalations-Ziele (/agent-groups, /users) führen nur aktive, nicht archivierte Konten ohne System-Benutzer.

SLA-Policy

POST /api/sla/admin/policies
{
  "name": "Premium Support SLA",
  "description": "Aggressive targets, 24/7",
  "entityType": "TICKET",
  "targets": {
    "CRITICAL": { "responseMin": 15, "resolutionMin": 120 },
    "HIGH":     { "responseMin": 30, "resolutionMin": 240 },
    "MEDIUM":   { "responseMin": 60, "resolutionMin": 480 },
    "LOW":      { "responseMin": 120, "resolutionMin": 1440 }
  },
  "businessHoursId": null,
  "escalationPolicyId": "clx-escalation",
  "categoryIds": [],
  "pauseOnStatus": ["ON_HOLD"],
  "pauseOnIncidentLink": true,
  "pauseOnChildTicket": true,
  "isDefault": false,
  "isActive": true
}
FeldBeschreibung
entityTypeTICKET, INCIDENT, PROBLEM
targetsMap Priorität → { responseMin?, resolutionMin }. responseMin optional (z.B. Problems ohne Response-SLA).
businessHoursIdnull = 24/7; sonst business-hours-aware Deadlines
escalationPolicyIdoptionale Eskalations-Policy
categoryIdsEinschränkung auf bestimmte Kategorien (leer = alle). Zwei AKTIVE Policies desselben entityType dürfen sich keine Kategorie teilen → 409 SLA_CATEGORY_OVERLAP.
pauseOnStatusStatus, bei denen die SLA pausiert (Default [ON_HOLD])
pauseOnIncidentLinkSLA pausiert, wenn die Entity an einen offenen Incident gelinkt ist (Default true)
pauseOnChildTicketSLA des Elterntickets pausiert, solange eines seiner Sub-Tickets offen ist (Default true, nur entityType TICKET)
isDefaultDefault-Policy für diesen entityType

Eskalations-Policy

levels ist ein JSON-Array. Jede Stufe hat einen Trigger und Aktionen:

POST /api/sla/admin/escalation-policies
{
  "name": "Standard Escalation",
  "levels": [
    {
      "level": 1,
      "triggerType": "PERCENTAGE",      // PERCENTAGE | BREACH | TIME_AFTER_BREACH
      "triggerValue": 80,
      "slaType": "RESOLUTION",          // RESPONSE | RESOLUTION
      "actions": [
        { "type": "NOTIFY", "notifyTargets": ["ASSIGNEE"] }
      ]
    },
    {
      "level": 2,
      "triggerType": "BREACH",
      "triggerValue": 0,
      "slaType": "RESOLUTION",
      "actions": [
        { "type": "NOTIFY", "notifyTargets": ["ASSIGNEE", "GROUP_LEAD", "MANAGER", "CUSTOM"], "customUserIds": ["clx-user-1"] },
        { "type": "REASSIGN", "reassignToGroupId": "clx-escalation-group" }
      ]
    },
    {
      "level": 3,
      "triggerType": "TIME_AFTER_BREACH",
      "triggerValue": 60,
      "slaType": "RESOLUTION",
      "actions": [ { "type": "ESCALATE_PRIORITY", "prioritySteps": 1 } ]
    }
  ],
  "repeatConfig": {
    "enabled": true,
    "intervalMin": 120,
    "maxRepeats": 5,
    "notifyTargets": ["ASSIGNEE", "GROUP_LEAD"]
  },
  "isActive": true
}
Action typeKonfiguration
NOTIFYnotifyTargets (ASSIGNEE, GROUP_LEAD, MANAGER, CUSTOM) · customUserIds[] (Pflicht bei CUSTOM)
REASSIGNreassignToGroupId und/oder reassignToUserId (mindestens eines; sind beide gesetzt, wird erst die Gruppe, dann der User zugewiesen)
ESCALATE_PRIORITYPriorität anheben (prioritySteps 1–99, Default 1) → SLA-Reset auf neue Targets
WEBHOOKexterner Webhook-Aufruf (webhookUrl Pflicht)

Die Eskalations-Policy legt die Empfänger fest. Über welchen Kanal (In-App/E-Mail/Push/Webex/Teams) eine SLA-Benachrichtigung zugestellt wird, bestimmen die Notification-Typ-Konfiguration und die Nutzer-Präferenzen. Empfänger werden zur Laufzeit zusätzlich gegen die Sichtbarkeit der Entity gefiltert.

Validierung der Stufen

  • level: eindeutig und streng aufsteigend sortiert
  • PERCENTAGE: triggerValue 1–200 · TIME_AFTER_BREACH: triggerValue ≥ 1 Minute
  • slaType: RESPONSE oder RESOLUTION
  • NOTIFY mit Ziel CUSTOM erfordert mindestens einen Eintrag in customUserIds; REASSIGN erfordert ein Ziel; WEBHOOK erfordert webhookUrl

Wiederkehrende Breach-Reminder (repeatConfig)

Jede Eskalationsstufe wird genau einmal ausgelöst. Damit ein dauerhaft verletztes SLA nicht in Vergessenheit gerät, kann die Policy nach der letzten Stufe Erinnerungen wiederholen:

FeldBeschreibung
enabledWiederholung aktiv
intervalMin15–10080 — Geschäftsminuten seit der letzten Eskalation/Wiederholung
maxRepeats1–20 — danach ist Schluss
notifyTargetsmindestens ein Ziel (ASSIGNEE, GROUP_LEAD, MANAGER, CUSTOM); CUSTOM erfordert customUserIds

Jede erfolgte Wiederholung steht als REPEAT-Eintrag in der Eskalations-Historie. repeatConfig: null im PATCH entfernt die Wiederholung; ein weggelassenes Feld lässt sie unangetastet.

Business Hours & Feiertage

POST /api/sla/admin/business-hours
{
  "name": "German Business Hours",
  "schedule": {
    "monday":    { "start": "09:00", "end": "17:00" },
    "tuesday":   { "start": "09:00", "end": "17:00" },
    "wednesday": { "start": "09:00", "end": "17:00" },
    "thursday":  { "start": "09:00", "end": "17:00" },
    "friday":    { "start": "09:00", "end": "17:00" },
    "saturday":  null,
    "sunday":    null
  },
  "timezone": "Europe/Berlin",
  "excludeHolidays": true,
  "holidayCountry": "DE",
  "holidayRegion": "BY",
  "isDefault": true
}
// POST /api/sla/admin/holidays   (or /holidays/bulk with { holidays: [...] })
{ "name": "Tag der Deutschen Einheit", "date": "2026-10-03", "isRecurring": true, "country": "DE", "region": null }

Doppelte Feiertage: Der Einzel-POST lehnt einen bereits vorhandenen Feiertag mit 409 SLA_HOLIDAY_DUPLICATE ab. Der Bulk-Import gleicht vorher gegen den Bestand ab und meldet die Zahl der übersprungenen Einträge als skippedExisting — ein zweiter Import derselben Liste legt also nichts doppelt an.

region entscheidet über die Reichweite: region: null bedeutet landesweit — die Uhr steht dann ÜBERALL still. Regionale Feiertage (etwa Fronleichnam oder Allerheiligen) gehören deshalb mit ihrer Region eingetragen, sonst pausieren sie die SLA auch dort, wo gearbeitet wird.

Automatischer Feiertags-Import

Deutsche Feiertage müssen nicht von Hand gepflegt werden: ein Hintergrund-Job berechnet sie (inkl. der beweglichen über die Osterformel) und legt sie für das aktuelle UND das nächste Jahr an — und zwar für jede Land/Region-Kombination, die in einer Business-Hours-Definition tatsächlich verwendet wird. Genau dieses Fenster liest die Deadline-Berechnung.

EigenschaftWert
ZeitplanJährlich am 1. November, 04:00 — rechtzeitig, bevor die Deadline-Berechnung das Folgejahr braucht. Zusätzlich beim Start des Workers (füllt frische Installationen und verpasste Jahre sofort).
IdempotenzMehrfachläufe erzeugen keine Dubletten — bestehende Einträge werden erkannt und übersprungen.
UmfangDeutschland (bundesweite und regionale Feiertage). Feiertage anderer Länder werden über den Bulk-Import gepflegt.
VerwaltungAls Cronjob „Holiday Auto-Import" sichtbar und dort abschaltbar (CronJobs)

Beispiel: Business-Hours-Deadline

Business Hours: Mo–Fr 09:00–17:00 (8h/Tag)
Ticket erstellt: Freitag 14:00 · Target: 480 min (8 Geschäftsstunden)

  Fr 14:00 → 17:00 = 180 min      (übrig 300)
  Sa/So            = übersprungen  Mo 09:00 → 14:00 = 300 min      (übrig 0)
→ Resolution-Deadline: Montag 14:00
  (fällt Montag auf einen Feiertag → Dienstag 14:00)

SLA-Lebenszyklus

  • Erstellung (automatisch): Beim Anlegen einer Entity wählt der Service die passende Policy (Kategorie-spezifisch → sonst Default des entityType), kopiert die Priority-Targets in ein SLATracking und berechnet die Deadlines (24/7 oder business-hours).
  • Pause/Resume: Status in pauseOnStatus (Default ON_HOLD) ODER offener Incident-Link (pauseOnIncidentLink) ODER offenes Sub-Ticket am Elternticket (pauseOnChildTicket) pausiert die Uhr; beim Fortsetzen wird die Deadline um die pausierte Geschäftszeit verschoben (pausedTotalSec, pauseHistory) — fortgesetzt wird erst, wenn kein Grund mehr greift. Jede Pause, Fortsetzung und Stornierung schreibt zusätzlich einen sichtbaren Eintrag in die Timeline der betroffenen Entität — ein ON_HOLD-Wechsel erzeugt deshalb bewusst zwei Einträge: den Statuswechsel und die SLA-Pause.
  • Priority-Change → Reset: Neue Targets aus der Policy, Deadlines ab jetzt neu, Eskalationslevel zurückgesetzt.
  • Response Met: Bei Tickets zählt die erste öffentliche Antwort eines Agenten — egal ob im Web, per E-Mail-Antwort oder durch eine ausgehende E-Mail, mit der das Ticket entsteht. Eine bloße Zuweisung (an Agent, Gruppe, Mailbox oder Queue) erfüllt die Response-SLA NICHT. Bei Incidents und Problems gilt die ITIL-Semantik: Die Zuweisung bzw. Annahme gilt als Reaktion.
  • Resolution Met: Schließen bzw. Lösen setzt resolutionMet: true, wenn die Resolution-Deadline gehalten wurde, sonst false (breachAt ist gesetzt).
  • SLA-Monitor: Ein Background-Job im job-worker berechnet periodisch Status (calculatedStatus) und feuert fällige Eskalationsstufen.

Benachrichtigungen

SLA-Ereignisse erzeugen Notifications über das zentrale Notification-System: SLA_WARNING, SLA_RESPONSE_WARNING, SLA_BREACH, SLA_RESPONSE_BREACH, SLA_CRITICAL, SLA_ESCALATION, SLA_MET. Kanäle & Empfänger steuert die Notification-Konfiguration.

Absences & Vertretung

Abwesenheiten (Urlaub/Krank/…) und Vertreterregelungen sind eine eigene Domain (/api/absences) und beeinflussen Zuweisung/Substitution, nicht die SLA-Uhr direkt. Absence-Typen: VACATION, SICK, TRAINING, BUSINESS_TRIP, PARENTAL, COMPENSATORY, OTHER. Absences API

⏱️
Kernprinzipien
  • ✓ Targets pro Priorität, Policy pro entityType
  • ✓ Business-hours-genaue Deadlines & Status
  • ✓ Pause bei ON_HOLD / Incident-Link / Sub-Ticket
  • ✓ Mehrstufige Eskalation + SLA-Monitor
🔐
Berechtigungen (RBAC)
  • tickets.viewAlltickets.viewOwn – SLA-Dashboard (trackings/stats) und Historie (report); ohne viewAll nur sichtbare Tickets und nur mit Agenten-Profil
  • incidents.viewAll / problems.viewAll – Historie je entityType
  • settings.editSLA – Policies/Eskalation/Business-Hours/Feiertage + Schwellwerte (sla-settings). Kritische Berechtigung: Ein Entzug wirkt sofort, verweigerte Versuche werden protokolliert.

Auth-/Rollenmodell: Permissions & RBAC

Verwandte Dokumentation