Skip to main content

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:

FieldWhat
serviceLevelsEach 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.
assignmentsWhere SLAs apply: id, orgId, scope (org, unit, account or account_service), unitId, serviceLevelId; an account's also account, serviceIds, startsOn, endsOn, overrideCount and contractRef.
calendarsThe business hours, with how many SLAs use each.
policiesThe 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​

EndpointWhat
POST /api/v1/sla/agreementsA 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/:idChange 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/:idRefused while it applies anywhere. One that cases were measured under is archived (archived: true); otherwise it is deleted.
POST /api/v1/sla/agreements/:id/restoreBring an archived SLA back.
GET /api/v1/sla/agreements/:id/versionsIts 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​

EndpointWhat
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/:idReplace its SLA: serviceLevelId. This is how the organization default is changed.
DELETE /api/v1/sla/assignments/:idRemove 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​

EndpointWhat
GET /api/v1/sla/accounts/:accountIdThe 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/assignmentsGive 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/:idChange 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/:idRemove 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:

FieldWhat
serviceLevelid, name, description, version (the case's), currentVersion and archived; null when no SLA applies.
scopeThe level it came from: account_service, account, service, unit, org, or none.
levelsEach 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).
wouldNowBePresent when a case like this would now get another SLA (where SLAs apply changed after it got its own).
calendarThe 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.
overridesHow 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​

EndpointWhat it does
POST /api/v1/sla/holiday-calendarsA 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/:idIts 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/:idRefused 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​

EndpointWhat it does
POST /api/v1/sla/regionsA 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/:idIts name, timeZone and businessHoursId. New cases count in the new hours.
DELETE /api/v1/sla/regions/:idIts 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​

EndpointWhat it does
GET /api/v1/cases/:ref/sla/eventsThe 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 → olasEach 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.