Escalation API
These endpoints manage what Settings → Escalation shows: the teams that share a case queue, and the escalation policies that decide where each kind of case starts and goes. To put one case in a team’s queue, or filter the case list by queue, see the Alerts and cases API.
Before you start
Section titled “Before you start”Who can call them. Signed-in people, not API keys; an API key gets 401 sign in required.
Sign in with curl as described in
Alerts and cases API: before you start, then add
-b ~/.sp-cookies to every request below.
| To | You need |
|---|---|
| Read teams and policies | Any one of cases.view, settings.view, settings.manage |
| Create, change or delete a team | settings.manage |
| Create, change or delete a policy | settings.manage, and by default a second person with settings.approve |
Every change is written to the audit log.
Endpoints at a glance
Section titled “Endpoints at a glance”| Endpoint | Does | Success |
|---|---|---|
GET /api/v1/teams |
Teams and their members | 200 |
POST /api/v1/teams |
Create a team | 201 |
PUT /api/v1/teams/{id} |
Rename a team and set its members | 200 |
DELETE /api/v1/teams/{id} |
Delete a team | 204 |
GET /api/v1/escalation-policies |
Policies, in the order they are tried | 200 |
POST /api/v1/escalation-policies |
Create a policy | 201, or 202 when sent for approval |
PUT /api/v1/escalation-policies/{id} |
Change a policy | 200, or 202 |
DELETE /api/v1/escalation-policies/{id} |
Delete a policy | 204, or 202 |
Errors
Section titled “Errors”Errors use Problem Details.
| Status | Meaning | Examples |
|---|---|---|
401 |
Not signed in, or called with an API key | sign in required |
403 |
Missing permission | missing permission settings.manage |
404 |
No such team or policy in your workspace | not found |
409 |
A name is taken, or the team is in use | conflict: a team with this name already exists; conflict: the escalation policy "Politically exposed persons" sends cases to this team; change the policy first |
422 |
The request is invalid | name must be 1 to 80 characters; a level's time must be between 15 minutes and 90 days; level 2 names a team that doesn't exist; condition: … |
428, 202 |
The change needs a second person’s approval (policies only). See When approval is required |
The team object
Section titled “The team object”| Field | Type | Meaning |
|---|---|---|
id |
UUID | The team |
name |
string | 1 to 80 characters, unique in the workspace |
members |
array | Leads first, then by name. Each: user_id, name (read-only display name) and lead (boolean) |
active_cases |
integer | Cases in the team’s queue that aren’t closed |
lead is a label: it changes nothing about permissions or notifications.
GET /api/v1/teams
Section titled “GET /api/v1/teams”Every team, in name order.
curl -s -b ~/.sp-cookies "$SP/api/v1/teams"[ { "id": "0192f3d0-aaaa-7000-8000-000000000001", "name": "PEP desk", "members": [ { "user_id": "0192f3a0-bbbb-7000-8000-000000000002", "name": "Efua Owusu", "lead": true }, { "user_id": "0192f3a0-bbbb-7000-8000-000000000003", "name": "Kofi Boateng", "lead": false } ], "active_cases": 3 }]Illustrative data.
POST /api/v1/teams and PUT /api/v1/teams/{id}
Section titled “POST /api/v1/teams and PUT /api/v1/teams/{id}”| Field | Required | Values |
|---|---|---|
name |
Yes | 1 to 80 characters |
members |
Yes | Up to 500 entries of {"user_id": …, "lead": true | false}. May be empty. A user listed twice is kept once |
PUT replaces the name and the whole member list: send everyone who should remain. Every member
must be an active user of your workspace; otherwise 422 a member is not an active person in this workspace and nothing changes. Both return the team object.
curl -s -b ~/.sp-cookies -X POST "$SP/api/v1/teams" \ -H 'Content-Type: application/json' \ -d '{"name":"PEP desk","members":[{"user_id":"0192f3a0-bbbb-7000-8000-000000000002","lead":true}]}'DELETE /api/v1/teams/{id}
Section titled “DELETE /api/v1/teams/{id}”Deletes the team. Its cases leave the queue (team_id is cleared) and stay with their assignees.
409 while any escalation policy, in use or off, names the team for a level. Returns 204.
Escalation policies
Section titled “Escalation policies”The policy object
Section titled “The policy object”| Field | Type | Meaning |
|---|---|---|
id |
UUID | The policy. Read-only |
name |
string | 1 to 80 characters, unique in the workspace |
priority |
integer | 1 to 1000. The order: when several policies fit a case, the lowest number wins; ties go in name order |
condition |
string | Which cases the policy takes, up to 1,000 characters. Empty: every case. See The condition |
start_level |
integer | 1 to 3. The level a matching case opens at. Above 1, the case opens escalated and unassigned |
levels |
array | Up to three entries, one per level; see below. Levels you leave out have no team and use the workspace’s deadline |
auto_escalate |
boolean | Escalate the case one level when a deadline passes |
four_eyes_close |
boolean | Closing a case that follows this policy needs a second approver, whatever its priority |
active |
boolean | false: the policy fits no new case. Cases already following it carry on |
active_cases |
integer | Cases following the policy that aren’t closed. Read-only |
updated_at |
time | Read-only |
Each entry of levels:
| Field | Type | Meaning |
|---|---|---|
level |
integer | 1, 2 or 3. Each level at most once |
team_id |
UUID | The team whose queue cases join at this level. Optional |
team_name |
string | Read-only |
sla_minutes |
integer | How long a case has at this level, in minutes: 15 to 129600 (90 days). 0 or absent uses the workspace’s case_sla_hours for the case’s priority |
An entry with neither a team nor a time is dropped when the policy is stored.
The condition
Section titled “The condition”A CEL expression that evaluates to true or false, over:
| Name | Type | Values |
|---|---|---|
kind |
string | aml, fraud, sanctions, other |
priority |
string | low, medium, high, critical |
score |
double | The case’s opening score |
subject_type |
string | customer, account, counterparty |
designations |
list of strings | The subject’s designations in force, for example "pep" in designations |
risk_rating |
string | unrated, low, medium, high, unacceptable, or empty |
segment |
string | The customer’s segment code, or empty |
kyc_level |
int | The customer’s KYC tier; 0 when unknown |
An expression that doesn’t compile, or doesn’t yield a boolean, is refused with 422. The condition
is evaluated once, when a case is opened. If it fails to evaluate for a case, the policy doesn’t fit
that case.
GET /api/v1/escalation-policies
Section titled “GET /api/v1/escalation-policies”Every policy, in the order they are tried.
[ { "id": "0192f3d1-cccc-7000-8000-000000000001", "name": "Politically exposed persons", "priority": 10, "condition": "\"pep\" in designations || \"pep_rca\" in designations", "start_level": 2, "levels": [ { "level": 2, "team_id": "0192f3d0-aaaa-7000-8000-000000000001", "team_name": "PEP desk", "sla_minutes": 1440 } ], "auto_escalate": true, "four_eyes_close": true, "active": true, "active_cases": 3, "updated_at": "2026-10-12T09:30:00Z" }]Illustrative data.
POST /api/v1/escalation-policies and PUT /api/v1/escalation-policies/{id}
Section titled “POST /api/v1/escalation-policies and PUT /api/v1/escalation-policies/{id}”Send name, priority, condition, levels, start_level, auto_escalate, four_eyes_close
and active. PUT replaces the whole policy. Both return the policy object when applied directly.
curl -s -b ~/.sp-cookies -X POST "$SP/api/v1/escalation-policies" \ -H 'Content-Type: application/json' \ -H 'X-Change-Reason: PEP cases go to the PEP desk at level 2 (procedure AML-07)' \ -d '{ "name": "Politically exposed persons", "priority": 10, "condition": "\"pep\" in designations || \"pep_rca\" in designations", "start_level": 2, "levels": [{ "level": 2, "team_id": "0192f3d0-aaaa-7000-8000-000000000001", "sla_minutes": 1440 }], "auto_escalate": true, "four_eyes_close": true, "active": true }'Cases already open keep their level, team and deadline when a policy changes: a changed team or
time applies from their next escalation. four_eyes_close and auto_escalate are read from the
policy as it is now, so those two apply to open cases at once.
DELETE /api/v1/escalation-policies/{id}
Section titled “DELETE /api/v1/escalation-policies/{id}”Deletes a policy that no open case follows. Returns 204 when applied directly.
While open cases follow the policy the request is refused with 409: N open cases follow this
policy; switch it off so it takes no new case, and delete it when they are closed. Set active to
false with PUT instead; active_cases in the list tells you when it can be deleted.
When approval is required
Section titled “When approval is required”Policy changes fall under the action type cases.escalation_policies (Escalation policies),
which requires a second person by default. While it does, the three writing endpoints answer as
every gated endpoint does:
- Without a reason:
428 Reason required. The policy is checked first, so an invalid one gets its422or409here. - With an
X-Change-Reasonheader:202 Accepted, with the pending request inchange_request. Nothing has changed yet. Someone else withsettings.approveapproves it, and the policy is created, changed or deleted then.
A second change to the same policy while one is pending is refused with 409. See
Change requests API for the full exchange, polling and expiry.
Team endpoints are never gated.