Versus Desk

SLAs and Escalation

How Versus Desk measures response and resolution time against business-hours SLAs — definitions, calendars, the attach/pause/stop lifecycle, warning and breach detection on a periodic tick, and escalation routed through Observe contact points.

Versus Desk tracks how quickly your team responds to and resolves tickets against service-level agreements you define. An SLA in Versus is a business-hours clock: it attaches to a ticket the moment the ticket qualifies, counts down only during the hours your calendar says you're open, pauses while the ticket is waiting on someone else, and — if it runs out — fires a breach that escalates through the same alerting layer the rest of the platform uses.

There are no per-ticket timers or background cron jobs behind this. SLA state is materialized on each ticket as a due time, and a single lightweight scheduler tick scans for clocks that have crossed their warning or breach threshold. That keeps the mechanism cheap and auditable at any fleet size.


The two metrics

Every SLA measures one of two things:

MetricWhat it measuresStartsStops
ResponseTime from ticket open to first agent actionOn ticket createFirst public comment, or the ticket moving into progress (start / assign)
ResolutionTime from ticket open to resolutionOn ticket createThe ticket being resolved or fulfilled (resolve / fulfill)

A single ticket typically carries both a response and a resolution clock at once — for example a P1 incident with a 15-minute response target and a 4-hour resolution target. The engine keeps at most one running instance per metric per ticket.


SLA definitions

An SLA definition is the reusable policy that says which tickets get what target. You manage definitions under Versus Desk's SLA settings, or over the API at /api/itsm/sla-definitions/.

FieldMeaning
nameHuman label, e.g. P1 Response.
metricresponse or resolution.
target_minutesThe target, in business minutes (see calendars below).
applies_toMatch criteria — { ticket_type, priority: [...], group_id }. An empty object matches every ticket.
precedenceInteger; when several definitions match the same metric, the highest precedence wins.
calendarThe business calendar the clock runs against. Null means 24×7.
warning_threshold_pctPercent of the way to the deadline at which a warning fires (default 75).
pause_statesTicket states that should stop the clock while the ticket sits there.
rearm_on_reopenWhether the resolution clock re-arms if the ticket is reopened (default true).
archived_atSet to retire a definition without deleting its history.

A definition matches a ticket when its applies_to criteria all hold: the ticket_type matches (if set), the ticket's priority is in the listed priorities (if set), and the assignment group matches (if set). Unset criteria are wildcards.

Example: a P1 response SLA

POST /api/itsm/sla-definitions/
Authorization: Bearer …
Content-Type: application/json

{
  "name": "P1 Response",
  "metric": "response",
  "applies_to": { "priority": [1] },
  "precedence": 99,
  "target_minutes": 15,
  "calendar": 3,
  "warning_threshold_pct": 75,
  "pause_states": ["Pending"]
}

A typical estate defines a matched pair (response + resolution) per priority band. For instance: P1 → 15 min / 4 h, P2 → 30 min / 8 h, P3 → 24 h / 72 h — each expressed in business minutes against your calendar.


Business calendars

A business calendar turns wall-clock time into business time, so a 4-hour resolution target on a ticket opened at 5 p.m. lands at 11 a.m. the next business day rather than 9 p.m. that night. Manage calendars at /api/itsm/calendars/.

FieldMeaning
timezoneIANA zone, e.g. America/Toronto.
hoursOpen segments per weekday, e.g. { "mon": [["08:00","18:00"]], … , "sat": [], "sun": [] }.
holidaysList of ISO dates that are treated as fully closed, e.g. ["2026-12-25"].

The due-time algorithm walks forward from the clock's start instant, consuming the target's minutes only inside open segments, skipping closed days and holidays entirely, and rolling into the next open segment when a day's hours run out. Multiple segments per day (for example a lunch break) are supported. A definition with no calendar falls back to a plain 24×7 countdown — the target minutes are simply added to the start time.

Because the due time is computed once and stored on the ticket, it is stable and inspectable; it is only recomputed when the clock pauses and resumes.


The clock lifecycle

Each running SLA is an instance on the ticket, carrying its own started_at, materialized due_at, and status flags. The lifecycle is driven entirely by ticket events, not by a timer.

Attach. When a ticket is created — and again whenever its fields change — the engine re-evaluates every non-archived definition. For each metric, the highest-precedence matching definition wins, and a fresh instance is attached only if none is already running for that metric. An unrelated field edit therefore never disturbs a clock that is already counting.

Stop. The response clock stops on the first agent action: a public comment, or the ticket moving into progress. The resolution clock stops when the ticket is resolved or fulfilled. Stopping records an outcome:

OutcomeWhen
metThe clock stopped before its due time (or a response clock stopped at all).
breachedThe clock had already passed its due time when it stopped.
cancelledThe instance was voided without a met/breached result.

Pause and resume. When a ticket enters a pending state (the pend transition), its running clocks pause; the unpend transition resumes them. On resume, the paused duration is added to the instance's accumulated-pause total and the due_at is pushed forward by exactly that much, so waiting on a third party never counts against your target. pause_states on the definition records which states are meant to stop the clock.


Warnings, breaches, and the tick

Time-driven detection runs on a single periodic tick rather than one alarm per ticket. The tick is invoked by a management command on a short cadence (roughly every 30 seconds):

python manage.py check_itsm_gates

On each tick the engine scans running, unpaused instances and does two things:

  • Warning — if the elapsed time has reached warning_threshold_pct of the way to due_at and no warning has fired yet, it marks the warning fired and emits an sla_warn event. This is your "getting close" signal.
  • Breach — if due_at has passed, it stamps the instance breached_at and emits an sla_breach event.

Each instance fires each of these at most once. Paused clocks are skipped, so a ticket parked in a pending state never warns or breaches while it waits.

The same command also resumes any workflow runs that were gated on a change window — SLAs and change-gate resumption share one scheduler pass. See Change Management for the change side.


Escalation

Escalation in Versus Desk is deliberately not a new notification system. A warning or breach is recorded as an audit event on the ticket (sla_warn / sla_breach, actor system, with the metric and due time in the payload) and then handed to Observe's alerting layer as a normal alert.

The engine looks for an Observe contact point named itsm-sla-escalation. If one is configured, it delivers an alert through Observe's existing routing — reusing the same deduplication, webhook, and email paths every other alert uses — with labels that make routing and filtering easy:

LabelValue
ticket_numbere.g. INC0001234
priorityThe ticket's priority band
metricresponse or resolution
sla_statesla_warn or sla_breach

The ticket's short description rides along as the alert annotation. If no itsm-sla-escalation contact point exists, the audit event is still recorded and the delivery step simply no-ops — nothing breaks, you just aren't paged. This means you wire SLA escalation exactly the way you wire any other alert: point the contact point at PagerDuty, Slack, email, or a webhook, and route by the labels above. See Contact Points and Observe Alerting.


Working with SLAs on a ticket

See a ticket's clocks. Every SLA instance on a ticket — running or finished — is available at:

GET /api/itsm/tickets/{number}/sla/
[
  {
    "metric": "response",
    "started_at": "2026-08-19T13:02:00Z",
    "due_at": "2026-08-19T13:17:00Z",
    "warning_fired": true,
    "breached_at": null,
    "stopped_at": "2026-08-19T13:11:40Z",
    "outcome": "met"
  }
]

The fields tell the whole story: whether the warning fired, whether it breached, when it stopped, and how it ended.

Find what's breaching now. The ticket list accepts an SLA filter that returns only tickets with a currently-breached clock that hasn't yet stopped — the working set for an escalation queue or a dashboard tile:

GET /api/itsm/tickets/?sla=breached

Reopens. If a resolved ticket is reopened and its definition has rearm_on_reopen set, a fresh resolution clock attaches on the next evaluation, so the reopened work is measured again rather than counting as already-met.


How it fits the platform

SLAs are one of the two time-driven mechanisms in Versus Desk (the other being change-window gating), and both ride the same tick, the same audit trail, and — through Observe — the same alert delivery your infrastructure monitoring already uses. That keeps the service desk consistent with the rest of the platform: policy is data you can version, detection is a cheap periodic scan over a materialized due time, and escalation is a routing decision you make once in Observe rather than a bespoke pager built into the ticket system. In multi-tenant deployments, calendars, definitions, and instances are all tenant-scoped, so each tenant sets its own hours and targets.


Related