SLA Management
SLA policy configuration, business hours schedules, escalation rules, breach handling, and the SLA calculation engine.
Overview
SLA (Service Level Agreement) policies define responseTimeHours and resolutionTimeHours targets for ITSM tickets. When a ticket is created, FlowOS matches it against the tenant's active policies and opens two independent clocks — response and resolution — as separate records in the itsm_task_slas collection (model: TaskSla). There is no embedded sla object on the incident/problem/change/request record itself; live SLA state is always read from the Task SLA API documented below.
SLA Policy Fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| name | string | required | Display name, e.g. "Enterprise P1 SLA". | |
| module | enum | required | "itsm" | "itsm" · "hr" · "csm". The SLA policy list on this page only shows/creates module: "itsm" policies. |
| conditions | object | optional | {} | { type?: string, priority?: string }. Matching criteria — see Matching below. |
| responseTimeHours | number | required | — | Hours from ticket creation to the response-clock deadline. |
| resolutionTimeHours | number | required | — | Hours from ticket creation to the resolution-clock deadline. |
| isActive | boolean | optional | true | Only active policies are considered when matching a new ticket. |
| businessHoursCalendarId | ObjectId | optional | null | References a Business Hours Calendar record (see below). |
businessHoursCalendarId records the association and is returned by the API, but the SLA clock (sla-clock.service.ts) does not currently read it when computing a deadline or elapsed time — responseTimeHours/resolutionTimeHoursare applied as flat wall-clock hours from ticket creation, not business-hours-aware hours. Off-hours pausing only happens via the manual/status-driven pause described under Pause & Resume below, independent of the linked calendar.Matching
On ticket creation, FlowOS looks up the most recently created isActive policy for module: "itsm" whose conditions.priorityequals the ticket's priority, whose conditions.type equals the ticket's type, or that has no conditions at all (a catch-all policy). There is no priority/rank field — when several policies match, the newest one wins (sorted by createdAt descending). If nothing matches, FlowOS falls back to a default of responseTimeHours: 4, resolutionTimeHours: 24 with no linked policy record.
Business Hours Calendars
A Business Hours Calendar is a dedicated record type (sla_business_hours_calendars collection) that defines a per-weekday working schedule, a timezone, and a list of holidays. Time outside the calendar's working windows and on holidays does not count as elapsed business time for SLA calculations. An SLA policy opts into a calendar via businessHoursCalendarId; a policy without one is treated as always-on (24x7).
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| name | string | required | — | Display name, e.g. "US East Coast Business Hours". Unique per tenant. |
| timezone | string | required | UTC | IANA timezone identifier, e.g. "America/New_York". |
| weeklySchedule | object | optional | — | One entry per weekday (monday…sunday), each { isWorkday, startTime, endTime }. startTime/endTime are "HH:mm" strings. Defaults to Mon–Fri 09:00–18:00 workdays, Sat/Sun off. |
| holidays | { date, name }[] | optional | [] | Specific calendar dates treated as non-working, regardless of weeklySchedule. |
| isDefault | boolean | optional | false | Marks the tenant/workspace default calendar. Setting one calendar as default unsets isDefault on all others. |
| description | string | optional | — | Free-form description. |
| createdBy | ObjectId | optional | — | User who created the calendar (ref User). |
Elapsed business time is computed by walking day-by-day between two timestamps, summing only the minutes that fall inside a workday's startTime–endTime window and skipping any day that is a holiday or has isWorkday: false. The same logic is used in reverse to project a deadline forward by N business hours from a start time.
POST /api/sla/calendars
{
"name": "US East Coast Business Hours",
"timezone": "America/New_York",
"weeklySchedule": {
"monday": { "isWorkday": true, "startTime": "09:00", "endTime": "18:00" },
"tuesday": { "isWorkday": true, "startTime": "09:00", "endTime": "18:00" },
"wednesday": { "isWorkday": true, "startTime": "09:00", "endTime": "18:00" },
"thursday": { "isWorkday": true, "startTime": "09:00", "endTime": "18:00" },
"friday": { "isWorkday": true, "startTime": "09:00", "endTime": "18:00" },
"saturday": { "isWorkday": false, "startTime": "09:00", "endTime": "18:00" },
"sunday": { "isWorkday": false, "startTime": "09:00", "endTime": "18:00" }
},
"holidays": [{ "date": "2026-12-25", "name": "Christmas Day" }],
"isDefault": true
}
# Link a policy to it
PATCH /api/itsm/sla-policies/:id
{ "businessHoursCalendarId": "665f1a2b3c4d5e6f7a8b9c0d" }POST /api/sla/calendars/:id/set-default is a convenience action equivalent to updating isDefault: true via PATCH — both unset isDefault on every other calendar in the workspace first.Task SLA Records (Live State)
Each open clock is one document in itsm_task_slas. A ticket typically has two — one breachType: "response" and one breachType: "resolution" — created together by startSlaClock() when the ticket is created.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| ticketId | ObjectId | required | — | The incident/problem/change/request this clock belongs to. |
| ticketType | enum | required | — | "incident" · "problem" · "change" · "request" |
| breachType | enum | required | — | "response" · "resolution" — which target this clock is tracking. |
| slaPolicyId | ObjectId | optional | — | The matched SlaPolicy, if any (null when the 4h/24h fallback was used). |
| targetHours | number | required | — | Hours allotted, copied from the matched policy (or the fallback). |
| deadline | Date | required | — | startedAt + targetHours, extended by any paused duration. |
| state | enum | required | "running" | "running" · "paused" · "breached" · "completed" |
| percentElapsed | number | optional | 0 | 0–100, recalculated by the SLA scanner every 5 minutes. |
| totalPausedMs | number | optional | 0 | Cumulative paused time, excluded from percentElapsed. |
| pauseIntervals | object[] | optional | — | { pausedAt, resumedAt?, reason }[] — full pause history. |
| notifiedThresholds | number[] | optional | [] | Elapsed-percent thresholds already alerted on (currently only 75 is used). |
| escalationLevel | number | optional | 0 | Index into ["L1","L2","L3","vendor"] — bumped on breach, incidents only. |
Pause & Resume
Moving a ticket to pending or on_hold automatically pauses its clocks (reason = the status value); moving it to in_progress or assigned automatically resumes them, pushing the deadline forward by the paused duration. Clocks can also be paused/resumed manually via the Task SLA API, and are marked completed when the ticket reaches resolved or closed.
SLA Alerting
A global scanner ticks every 5 minutes across all workspaces, recalculating percentElapsed for every running clock. The first time a clock crosses 75% it emits sla.breach.predicted once; once now >= deadline the clock is marked breached and sla.breached fires (plus sla.resolution_breached or sla.response_breached). Both feed the standard ITSM notification pipeline.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| sla.breach.predicted | event | optional | — | Fired once when percentElapsed ≥ 75. Payload: tenantId, workspaceId, slaId, ticketId, ticketType, ticketNumber, breachType, percentElapsed, deadline. |
| sla.breached | event | optional | — | Fired when now ≥ deadline. Payload adds assignedTo, title and escalationLevel — but only for ticketType "incident"; problems/changes/requests don’t carry an assignee on this payload today. |
| escalation.triggered | event | optional | — | Incidents only. Fired alongside sla.breached; bumps the incident’s escalationLevel one step through L1 → L2 → L3 → vendor. |
sla.breached event.API Quick Reference
/api/itsm/sla-policiesList SLA policies (module: itsm)
/api/itsm/sla-policiesCreate SLA policy
/api/itsm/sla-policies/:idGet a single policy
/api/itsm/sla-policies/:idUpdate a policy
/api/sla/calendarsList business hours calendars
/api/sla/calendarsCreate a calendar
/api/sla/calendars/:idGet a calendar by id
/api/sla/calendars/:idUpdate a calendar
/api/sla/calendars/:idDelete a calendar
/api/sla/calendars/:id/set-defaultMake this the workspace default calendar
/api/itsm/task-sla/:ticketIdGet all SLA clocks for a ticket
/api/itsm/task-slaList running/breached clocks (dashboard), filter by state/ticketType/breachType
/api/itsm/task-sla/summaryCounts by state, plus atRisk (running & percentElapsed ≥ 75)
/api/itsm/task-sla/:id/pauseManually pause a clock. Body: { reason: "on_hold"|"pending_customer"|"maintenance"|"other" }
/api/itsm/task-sla/:id/resumeManually resume a paused clock
/api/itsm/task-sla/tickManually trigger the SLA scanner for the current workspace