Service levels
Service level agreements (SLAs), where they apply, and the business hours they count in, through the REST API. What
they mean is in Service levels. Every endpoint below needs Service level settings
(sla.settings) in the organization it touches, except Why this one? on a case, which needs only access to the
case, and an account's SLAs, which need sla.read to read and sla.assign to change.
The overview
GET /api/v1/sla returns, when you may set service levels:
| Field | What |
|---|---|
serviceLevels | Each SLA: id, orgId, name, description, businessHoursId (none: around the clock), targets, version, archivedAt, appliesTo (the organization default, units and, with Service Configuration, services: scope, id, label) and openCases. ?archived=true includes archived ones. |
assignments | Where SLAs apply: id, orgId, scope (org, unit, account or account_service), unitId, serviceLevelId; an account's also account, serviceIds, startsOn, endsOn, overrideCount and contractRef. |
calendars | The business hours, with how many SLAs use each. |
policies | The SLAs with their units as before 0.2.0, for clients of the policies endpoints below. |
targets is an object per priority (urgent, high, normal, low), each with firstResponse and resolution
(numbers, or null for no target) and firstResponseUnit and resolutionUnit (minutes, the default, or days,
business days). Minutes are at most a year; days at most 365.
SLAs
| Endpoint | What |
|---|---|
POST /api/v1/sla/agreements | A new SLA: orgId, name, description, businessHoursId, useRegionCalendar (default false: when true, each case counts in its region's business hours and businessHoursId is left empty), targets. It starts at version 1. The first SLA of an organization becomes its default (madeDefault: true). |
PATCH /api/v1/sla/agreements/:id | Change it: name, description, businessHoursId, useRegionCalendar (left out: as it is), targets, and applyToOpen (default false). A change makes a new version for new cases; with applyToOpen: true the open cases under it take the new version too, and applied says how many. A save that changes nothing makes no version. |
DELETE /api/v1/sla/agreements/:id | Refused while it applies anywhere. One that cases were measured under is archived (archived: true); otherwise it is deleted. |
POST /api/v1/sla/agreements/:id/restore | Bring an archived SLA back. |
GET /api/v1/sla/agreements/:id/versions | Its versions, newest first: version, name, targets, businessHours, useRegionCalendar, createdAt, createdBy, appliedToOpen and cases (how many cases are measured under it). |
Creating and changing an SLA return warnings: things that are saved but look wrong, such as a higher priority with
a longer target than a lower one.
Operational level agreements
An OLA is made with the same endpoints: POST /api/v1/sla/agreements with kind: "ola", teamId (a team of the
organization) and unitIds (optional: only cases in these units and below), and targets with pickUp, handBack,
pickUpUnit and handBackUnit per priority (other targets are dropped). It counts in businessHoursId (never the
region's), is never an organization default, and can't be assigned to a unit, account or service (invalid_sla).
PATCH takes teamId and unitIds too; a new version is for the spans that start from then on, so applyToOpen
does nothing. warnings include a hand back longer than the resolution of an SLA where the team works.
GET /api/v1/sla lists OLAs in serviceLevels with kind: "ola", team, unitIds and openCases (the spans
open now), and the teams an OLA can be for; versions count spans in cases.
Where they apply
| Endpoint | What |
|---|---|
POST /api/v1/sla/assignments | { scope: "org", orgId, serviceLevelId } makes the organization default; { scope: "unit", unitId, serviceLevelId } gives a unit (and the units below it) its own. Refused when the organization or unit already has one (assignment_exists), or when a unit is given one before the organization has a default (no_default). |
PATCH /api/v1/sla/assignments/:id | Replace its SLA: serviceLevelId. This is how the organization default is changed. |
DELETE /api/v1/sla/assignments/:id | Remove a unit's. The organization default can only be replaced while units or services have their own (default_required). |
Each takes applyToOpen (a query parameter on DELETE): without it the change reaches new cases only; with it the
open cases it covers are measured again under the SLA that now applies, and applied says how many.
The routes above change the organization default and units' SLAs; an account's are changed with the account
endpoints below (account_assignment otherwise).
A service's SLA is set on the service with Service Configuration (slaPolicyId on the service, in the
Service Configuration API).
An account's SLAs
| Endpoint | What |
|---|---|
GET /api/v1/sla/accounts/:accountId | The account's service level: inForce (what its new cases get: from account or default, and the serviceLevel), default, assignments, openCases (that you can see), region (its own; null: its units'), options (the SLAs, calendars, regions and agreements that may be picked; with Service Configuration, services the account uses, else null) and may (assign, settings). |
POST /api/v1/sla/accounts/:accountId/assignments | Give the account its own SLA: serviceLevelId; serviceIds (empty, the default: all services; with Service Configuration, some of those the account uses); startsOn (YYYY-MM-DD, today by default) and endsOn (null: until ended); contractRef; agreementId (one of the account's agreements); overrides; applyToOpen. |
PATCH /api/v1/sla/accounts/:accountId/assignments/:id | Change any of those; end it with endsOn. Fields left out stay as they are. |
PUT /api/v1/sla/accounts/:accountId/region | { regionId }: the region its cases count in; null: the region of each case's unit. Needs sla.assign. |
DELETE /api/v1/sla/accounts/:accountId/assignments/:id | Remove one (for one made by mistake). ?applyToOpen=true re-measures the account's open cases. |
GET /api/v1/sla/accounts/choices?orgId= | What New account offers: the organization default, the serviceLevels that may be picked, and mayAssign. |
overrides changes cells for the account: { "targets": { "urgent": { "firstResponse": 30 } }, "businessHoursId": "…" }, where a target of null means no target and businessHoursId null means around the clock. Only cells that
differ from the SLA are kept. Setting or changing them needs sla.settings.
Each assignment in the list has id, scope, state (current, upcoming or ended), serviceLevel, services,
startsOn, endsOn, endsSoon (within 30 days), overrides, overrideCount, targets and businessHours (the
SLA's current ones with the account's cells on top), contractRef and agreement.
Saves are refused when another of the account's SLAs covers the same services at the same time
(assignment_overlaps), for an SLA, calendar or agreement of another account (invalid_sla,
invalid_calendar, invalid_agreement), for services the account doesn't use (invalid_service), and for services
without Service Configuration (no_services). They return warnings, such as an SLA that ends within 30 days with
nothing after it, and applied.
POST /api/v1/accounts takes serviceLevelId too: another SLA than the organization default gives the new account
its own from today (it needs sla.assign); the default, or none, leaves it following the defaults.
Why this one?
GET /api/v1/cases/:ref/sla (token scope cases:read) says which SLA a case is measured under and why:
| Field | What |
|---|---|
serviceLevel | id, name, description, version (the case's), currentVersion and archived; null when no SLA applies. |
scope | The level it came from: account_service, account, service, unit, org, or none. |
levels | Each level from the most specific: scope, label (the account and service, the account's "all services", the service, the unit, "Organization default"), from (for a unit: the unit above it the SLA is set on), serviceLevel, won, and for an account's, overrides (how many cells it changes). |
wouldNowBe | Present when a case like this would now get another SLA (where SLAs apply changed after it got its own). |
calendar | The business hours it counts in (name, timeZone, weekly) and from (sla, service, account or region); with region, also region (id, name, and from: account, unit or org). Null: around the clock. |
overrides | How many cells the account's SLA changed for this case. |
The case itself carries serviceLevel, serviceLevelVersion and slaSource (the level and the case's unit, service
and account when it was chosen, and an account SLA's changed cells as they were then). Its history has an sla event when its SLA changes (from, to, reason).
Business hours
POST /api/v1/sla/calendars (orgId, name, timeZone, weekly: seven lists of [start, end] minutes, Sunday
first, holidayCalendarIds: the holiday calendars of the same organization it uses, and holidays: dates of its own
it is closed), PATCH /api/v1/sla/calendars/:id and DELETE /api/v1/sla/calendars/:id. A change reaches the open
cases counting in them at once. GET /api/v1/sla lists them under calendars, each with its holidayCalendars.
Holiday calendars
| Endpoint | What it does |
|---|---|
POST /api/v1/sla/holiday-calendars | A new one: orgId, name, description, and days, each { date: "2026-12-25", name: "Christmas Day", yearly?: true }. A yearly day is closed on that month and day every year. |
PATCH /api/v1/sla/holiday-calendars/:id | Its name, description and all its days. Open cases in business hours that use it are measured again; refreshed says how many. |
POST /api/v1/sla/holiday-calendars/:id/import | { ics }: an iCalendar file's text. The years it covers replace those years' dates, a yearly event replaces the yearly day on the same date, and the rest stays. Returns the calendar with imported (days read) and years. A file without dated events is refused (no_holidays). |
DELETE /api/v1/sla/holiday-calendars/:id | Refused while business hours use it (in_use). |
GET /api/v1/sla lists them under holidayCalendars, each with usedBy (the business hours that use it). All of
them need sla.settings in the organization.
Regions
| Endpoint | What it does |
|---|---|
POST /api/v1/sla/regions | A new one: orgId, name (unique in the organization), timeZone, businessHoursId (null: around the clock). An organization's first region becomes its own. |
PATCH /api/v1/sla/regions/:id | Its name, timeZone and businessHoursId. New cases count in the new hours. |
DELETE /api/v1/sla/regions/:id | Its units and accounts go back to the region above them. The organization's own region is refused while there are others (org_region). |
PUT /api/v1/sla/units/:unitId/region | { regionId }: the unit's region, for it and the units below it; null: as the unit above. |
GET /api/v1/sla lists them under regions, each with its businessHours and how many units and accounts are in it
(_count), and unitRegions: the units with a region of their own. All of them need sla.settings. A new account
takes regionId (POST /api/v1/accounts, with sla.assign); GET /api/v1/sla/accounts/choices lists the
regions and the organization's (defaultRegionId).
Warnings, breaches and breach reasons
| Endpoint | What it does |
|---|---|
GET /api/v1/cases/:ref/sla/events | The case's events, oldest first: kind (warning, breached, paused, resumed), target and label, at, dueAt, status and actor (pauses), minutes (resumed), reevaluated (at, dueAt: a later change moved the target past the breach), and for a breach its reason (reason, note, by, at). With may.reason, the organization's reasons. Token scope cases:read. |
GET /api/v1/cases/:ref/sla/events → olas | Each time a team with an OLA held the case: team, ola, version, startedAt, pickedUpAt and pickedUpBy, endedAt and endReason (resolved, reassigned), pickUpDueAt, handBackDueAt, pausedAt, pausedMinutes. Their warnings and breaches are in events with target pick_up or hand_back. |
POST /api/v1/cases/:ref/sla/events/:id/reason | { reasonId, note }: a breach's reason (null clears it). Needs case.write on the case; token scope cases:write. Logged as case.sla_reason. |
GET /api/v1/sla/breach-reasons?orgId= | The organization's reasons in order (the six defaults the first time). |
PUT /api/v1/sla/breach-reasons | { orgId, reasons: [{ id?, label }] }: all of them, in order. Ones left out are archived; breaches keep them. |
SLAs take warnAt (percent of a target's time, default 75; 0: no warning) and availability (the monthly
availability promised for the services they cover, in percent, 50 to 100, or null; OLAs have none; an account's
overrides may set its own), and targets take nextResponse and
nextResponseUnit per priority. Cases carry nextResponseDueAt. Webhooks sla.warning and sla.breached are sent
as they happen.
Service level report
GET /api/v1/reports/service-levels (report.read, token scope reports:read): days (7, 30, 90, 180 or 365),
by (service_level, account, unit, priority), unitId, tz. Returns totals, rows and months, each with
cases, firstResponse and resolution (met, breached, rate), nextResponseBreaches, nearMisses,
reevaluated and pausedMinutes, and reasons (the SLA's targets only). teams reports the OLAs: per team spans,
ola (names), pickUp and handBack (met, breached, rate) and reassigned. GET /api/v1/reports/service-levels/export?format=csv|xlsx|pdf gives
the table as a file. The population of measured cases is the evidence source sla.
Before 0.2.0: policies
POST, PATCH and DELETE /api/v1/sla/policies keep working for this release. A policy is an SLA with one unit
(unitId; the organization unit makes it the default), and as before, a change re-measures the open cases it covers.
The case's slaPolicy is the same as serviceLevel. Use the endpoints above for new work.