Alerting
Alert connections (Slack, PagerDuty, webhooks and the rest), escalation policies and severities that decide who is paged and how, on-call schedules, and the team groups policies page.
Paths are relative to https://api.zharp.io/api/v1. Alert connections hold webhook URLs and tokens, so every request to them — reads included — needs an admin key. Escalation, severities, on-call and team groups are read with read and changed with admin; changing them also needs the key's owner to be the workspace owner or a manager. See Authentication.
/notification-channels/workspace/:workspaceIdList connectionsGET/notification-channels/:idOne connection, with its deliveriesPOST/notification-channelsCreate a connectionPUT/notification-channels/:idChange a connectionPUT/notification-channels/:id/monitorsChoose its monitorsPOST/notification-channels/:id/testSend a test alertDELETE/notification-channels/:idDelete a connectionGET/escalation/policiesList escalation policiesGET/escalation/policies/:idOne policyPOST/escalation/policiesCreate a policyPUT/escalation/policies/:idReplace a policyDELETE/escalation/policies/:idDelete a policyGET/escalation/severitiesList severitiesPOST/escalation/severitiesCreate a severityGET/on-call/schedulesList schedules, with who's on callGET/on-call/schedules/:id/whoWho's on call nowPOST/on-call/schedulesCreate a schedulePOST/on-call/schedules/:id/overridesPut someone on call for a whileGET/team-groupsList team groupsAlert connections
A connection is somewhere alerts go: a Slack channel, an e-mail list, a webhook. It alerts for every monitor ("appliesTo": "all") or only the monitors linked to it, for the events in its events.
| type | config it needs |
|---|---|
EMAIL | emails: a list of addresses |
SLACK, DISCORD, MATTERMOST, MSTEAMS, GOOGLE_CHAT, ZAPIER | webhookUrl — the incoming webhook. Slack can also be connected in the app with “Add to Slack”. |
WEBHOOK | url; optional method, headers, a payload template, signingSecret — see Webhooks |
PAGERDUTY | integrationKey (Events API v2) |
TELEGRAM | chatId — easiest connected in the app, which links the chat for you |
PUSHOVER | userKey |
PUSHBULLET | accessToken |
SPLUNK | hecUrl and token |
| config key | For every type |
|---|---|
events | Which events it sends: any of down, degraded, resolved, acknowledged, maintenance. Default down, degraded and resolved. |
appliesTo | all for every monitor; otherwise only the monitors linked with PUT …/monitors. |
retries | Delivery attempts, 1–5. Default 3. |
/notification-channels/workspace/:workspaceIdworkspaceIdworkspaceId on any monitor).curl "https://api.zharp.io/api/v1/notification-channels/workspace/3" \ -H "Authorization: Bearer $ZHARP_API_KEY"
{ "message": "Connections retrieved", "data": [ {
"id": 41,
"workspaceId": 3,
"type": "SLACK",
"name": "#ops",
"config": {
"webhookUrl": "https://hooks.slack.com/services/T000/B000/XXXX",
"events": ["down", "resolved"],
"appliesTo": "all",
"connectedBy": { "id": 12, "name": "Ada" },
"connectedAt": "2026-10-01T09:00:00.000Z"
},
"enabled": true,
"language": "en",
"createdAt": "2026-10-01T09:00:00.000Z",
"updatedAt": "2026-10-20T14:12:00.000Z", "_count": { "monitors": 3 }
} ] }403Another workspace's id.
/notification-channels/:idhealth is healthy, failing (the last delivery failed) or unknown (nothing delivered yet). deliveries holds up to 20 from the last day.
idcurl "https://api.zharp.io/api/v1/notification-channels/41" \ -H "Authorization: Bearer $ZHARP_API_KEY"
{
"message": "Connections retrieved",
"data": {
"id": 41,
"type": "SLACK",
"name": "#ops",
"config": { … },
"enabled": true,
"monitorIds": [57, 58],
"stats": { "delivered24h": 5, "failed24h": 1, "lastDeliveryAt": "…", "lastResponseCode": 200, "lastStatus": "delivered", "health": "healthy" },
"deliveries": [
{ "id": 901, "event": "down", "status": "delivered", "error": null, "responseCode": 200, "durationMs": 312, "attempts": 1, "createdAt": "…", "monitor": { "id": 57, "name": "API health" } }
]
}
}404No such connection in this workspace.
/notification-channelstypeconfignameenabledtrue.curl -X POST "https://api.zharp.io/api/v1/notification-channels" \
-H "Authorization: Bearer $ZHARP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "SLACK",
"name": "#ops",
"config": {
"webhookUrl": "https://hooks.slack.com/services/T000/B000/XXXX",
"events": [
"down",
"resolved"
],
"appliesTo": "all"
}
}'{ "message": "Connection created", "data": {
"id": 41,
"workspaceId": 3,
"type": "SLACK",
"name": "#ops",
"config": {
"webhookUrl": "https://hooks.slack.com/services/T000/B000/XXXX",
"events": ["down", "resolved"],
"appliesTo": "all",
"connectedBy": { "id": 12, "name": "Ada" },
"connectedAt": "2026-10-01T09:00:00.000Z"
},
"enabled": true,
"language": "en",
"createdAt": "2026-10-01T09:00:00.000Z",
"updatedAt": "2026-10-20T14:12:00.000Z"
} }400An unknown type, or the config lacks what the type needs.
/notification-channels/:idname, config or enabled. A config you send replaces the old one — send the whole of it; what the server set (the signing secret, who connected it) is kept.idcurl -X PUT "https://api.zharp.io/api/v1/notification-channels/41" \
-H "Authorization: Bearer $ZHARP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"enabled": false
}'{ "message": "Connection updated", "data": { "id": 41, "enabled": false, … } }400The config lacks what the type needs.404No such connection in this workspace.
/notification-channels/:id/monitorsappliesTo isn't all. Monitors not in the list are unlinked.idmonitorIdscurl -X PUT "https://api.zharp.io/api/v1/notification-channels/41/monitors" \
-H "Authorization: Bearer $ZHARP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"monitorIds": [
57,
58
]
}'{ "message": "Connection updated", "data": { "monitorIds": [57, 58] } }404No such connection in this workspace.
/notification-channels/:id/testdeliveries show whether it arrived.idcurl -X POST "https://api.zharp.io/api/v1/notification-channels/41/test" \ -H "Authorization: Bearer $ZHARP_API_KEY"
{ "message": "Test alert sent" }404No such connection in this workspace.
/notification-channels/:ididcurl -X DELETE "https://api.zharp.io/api/v1/notification-channels/41" \ -H "Authorization: Bearer $ZHARP_API_KEY"
{ "message": "Connection deleted" }404No such connection in this workspace.
Escalation policies
A policy is who gets paged, in what order, when a monitor that uses it goes down — set a monitor's escalationPolicyId. Steps run in order; waitMinutes counts from when the incident opened, not from the step before. It stops as soon as someone acknowledges or resolves the incident. See Escalation & On-call.
| Step type | What it does | Uses |
|---|---|---|
ESCALATE | Pages its targets. Several alert steps may share an order and run together. | One of userId, onCallScheduleId (whoever is on call — the whole team if nobody is), groupId, channelId, or config.team: true; severityId |
INSTRUCTIONS | Posts instructions on the incident's timeline; a Markdown checklist can remind people while items stay open. | config.instructions, followUpEnabled, followUpHours |
TIME_RULE | Matches when now is on one of the days, between two times. | config.days, from, to, timezone, action |
METADATA_RULE | Matches a monitor field's value. | config.field, values, action |
ACTION | Ends the run by resolving the incident (or removing it). | config.action: resolve or remove |
A rule's action on a match: none stops the escalation, severity:ID pages at that severity from then on, anything else carries on. Rules, instructions and actions each need an order of their own.
/escalation/policieskeywordcurl "https://api.zharp.io/api/v1/escalation/policies" \ -H "Authorization: Bearer $ZHARP_API_KEY"
{ "message": "Escalation policies retrieved", "data": [ { "id": 5, "name": "Primary", "monitorCount": 4, "steps": [ … ], … } ] }/escalation/policies/:ididcurl "https://api.zharp.io/api/v1/escalation/policies/5" \ -H "Authorization: Bearer $ZHARP_API_KEY"
{ "message": "Escalation policy retrieved", "data": {
"id": 5,
"workspaceId": 3,
"name": "Primary",
"description": "Page on-call, then the backend team",
"severityId": 2,
"repeatCount": 1,
"steps": [
{
"id": 31,
"order": 0,
"type": "ESCALATE",
"waitMinutes": 0,
"onCallScheduleId": 3,
"userId": null,
"channelId": null,
"groupId": null,
"severityId": null,
"config": null,
"onCallSchedule": { "id": 3, "name": "Primary on-call" },
"user": null,
"channel": null,
"group": null
},
{
"id": 32,
"order": 1,
"type": "ESCALATE",
"waitMinutes": 15,
"groupId": 2,
"group": { "id": 2, "name": "Backend" },
…
}
],
"createdAt": "…",
"updatedAt": "…"
} }404No such policy in this workspace.
/escalation/policiesnamedescriptionseverityIdrepeatCount-1 until someone does. Default 0.stepsorder (from 0), type (default ESCALATE), waitMinutes, and what the type uses (above). Every person, schedule, group, connection and severity must be in this workspace.curl -X POST "https://api.zharp.io/api/v1/escalation/policies" \
-H "Authorization: Bearer $ZHARP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Primary",
"severityId": 2,
"repeatCount": 1,
"steps": [
{
"order": 0,
"waitMinutes": 0,
"onCallScheduleId": 3
},
{
"order": 1,
"waitMinutes": 15,
"groupId": 2
},
{
"order": 2,
"waitMinutes": 30,
"config": {
"team": true
}
}
]
}'{ "message": "Escalation policy created", "data": { "id": 5, … } }400A field fails validation, an alert step with nobody to alert, a rule sharing a position, or a target from outside the workspace.
/escalation/policies/:idseverityId and repeatCount left out keep their values.idcurl -X PUT "https://api.zharp.io/api/v1/escalation/policies/5" \
-H "Authorization: Bearer $ZHARP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Primary",
"steps": [
{
"order": 0,
"waitMinutes": 0,
"onCallScheduleId": 3
}
]
}'{ "message": "Escalation policy updated", "data": { "id": 5, … } }400As for creating one.404No such policy in this workspace.
/escalation/policies/:ididcurl -X DELETE "https://api.zharp.io/api/v1/escalation/policies/5" \ -H "Authorization: Bearer $ZHARP_API_KEY"
{ "message": "Escalation policy deleted", "data": null }404No such policy in this workspace.
Severities
A severity is how people are paged: any of EMAIL, PUSH, SMS, CALL and CRITICAL (a push that sounds through silent mode). Policies and steps choose one; without one, each person's own notification settings apply.
/escalation/severitieskeywordcurl "https://api.zharp.io/api/v1/escalation/severities" \ -H "Authorization: Bearer $ZHARP_API_KEY"
{
"message": "Severities retrieved",
"data": [
{ "id": 2, "workspaceId": 3, "name": "Critical", "description": "Wake people up", "channels": ["CALL", "CRITICAL"], "createdAt": "…", "updatedAt": "…",
"escalationPolicies": [{ "id": 5, "name": "Primary", "severityId": 2, "repeatCount": 1, … }] }
]
}/escalation/severitiesGET, PUT and DELETE on /escalation/severities/:id read, change (any of the same fields) and delete one; policies that used a deleted severity fall back to people's own settings.
namedescriptionchannelsEMAIL, PUSH, SMS, CALL, CRITICAL.curl -X POST "https://api.zharp.io/api/v1/escalation/severities" \
-H "Authorization: Bearer $ZHARP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Critical",
"channels": [
"PUSH",
"CRITICAL",
"EMAIL"
]
}'{ "message": "Severity created", "data": { "id": 2, "name": "Critical", "channels": ["PUSH", "CRITICAL", "EMAIL"], … } }400A field fails validation, an unknown channel, or an unknown field.
On-call schedules
A schedule says who is on call when: rotations take turns, active windows limit the hours, and overrides put someone on call for a while. The primary schedule is paged for monitors with no escalation policy.
/on-call/schedulesonCallNow) and the overrides in force.curl "https://api.zharp.io/api/v1/on-call/schedules" \ -H "Authorization: Bearer $ZHARP_API_KEY"
{ "message": "On-call schedules retrieved.", "data": [ { "id": 3, "name": "Primary on-call", "isPrimary": true, "onCallNow": { "id": 12, "name": "Ada", "email": "ada@acme.io", "photoUrl": null }, "rotations": [ … ], "activeWindows": [ … ], "overrides": [] } ] }/on-call/schedules/:id/whodata is null when nobody is — outside its active windows, say.
idcurl "https://api.zharp.io/api/v1/on-call/schedules/3/who" \ -H "Authorization: Bearer $ZHARP_API_KEY"
{ "message": "…", "data": { "id": 12, "name": "Ada", "email": "ada@acme.io", "photoUrl": null } }404No such schedule in this workspace.
/on-call/schedules/:id/export.icsiddayscurl "https://api.zharp.io/api/v1/on-call/schedules/3/export.ics?days=30" \ -H "Authorization: Bearer $ZHARP_API_KEY"
BEGIN:VCALENDAR X-WR-CALNAME:On-call: Primary on-call BEGIN:VEVENT SUMMARY:On call: Ada DTSTART:20261026T090000Z DTEND:20261102T090000Z …
404No such schedule in this workspace.
/on-call/schedulesnamedescriptiontimezoneUTC.rotationsname, type (DAILY, WEEKLY, MONTHLY), startTime (when the first shift starts), rotationLength (1–365 of the type's unit), and members: [{ "userId": 12, "order": 0 }] — active members of this workspace, taking turns in order.activeWindows{ "days": ["MON","TUE"], "startsAt": "09:00", "endsAt": "17:00" }. None means always.applyFromcurl -X POST "https://api.zharp.io/api/v1/on-call/schedules" \
-H "Authorization: Bearer $ZHARP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Primary on-call",
"timezone": "Europe/London",
"rotations": [
{
"name": "Weekly",
"type": "WEEKLY",
"startTime": "2026-10-26T09:00:00Z",
"rotationLength": 1,
"members": [
{
"userId": 12,
"order": 0
},
{
"userId": 14,
"order": 1
}
]
}
]
}'{ "message": "On-call schedule created.", "data": {
"id": 3,
"workspaceId": 3,
"name": "Primary on-call",
"description": null,
"timezone": "Europe/London",
"isPrimary": true,
"activeWindows": [{ "id": 1, "days": ["MON", "TUE", "WED", "THU", "FRI"], "startsAt": "09:00", "endsAt": "17:00" }],
"rotations": [
{
"id": 4,
"name": "Weekly",
"type": "WEEKLY",
"startTime": "2026-10-26T09:00:00.000Z",
"rotationLength": 1,
"effectiveFrom": null,
"effectiveUntil": null,
"members": [
{ "id": 9, "userId": 12, "order": 0, "user": { "id": 12, "name": "Ada", "email": "ada@acme.io", "photoUrl": null, "timezone": "Europe/London" } },
{ "id": 10, "userId": 14, "order": 1, "user": { "id": 14, "name": "Ben", … } }
]
}
],
"createdAt": "…",
"updatedAt": "…"
} }400A field fails validation, an unknown time zone, or someone who isn't an active member.
/on-call/schedules/:idapplyFrom when that's later, so the current shift isn't cut short. Overrides stay.idcurl -X PUT "https://api.zharp.io/api/v1/on-call/schedules/3" \
-H "Authorization: Bearer $ZHARP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Primary on-call",
"applyFrom": "2026-11-02T09:00:00Z",
"rotations": [
{
"name": "Weekly",
"type": "WEEKLY",
"startTime": "2026-11-02T09:00:00Z",
"rotationLength": 1,
"members": [
{
"userId": 14,
"order": 0
},
{
"userId": 12,
"order": 1
}
]
}
]
}'{ "message": "On-call schedule updated successfully.", "data": { "id": 3, … } }400As for creating one.404No such schedule in this workspace.
/on-call/schedules/:id/overridesiduserIdstartTimeendTimecurl -X POST "https://api.zharp.io/api/v1/on-call/schedules/3/overrides" \
-H "Authorization: Bearer $ZHARP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"userId": 14,
"startTime": "2026-12-24T00:00:00Z",
"endTime": "2026-12-27T00:00:00Z"
}'{ "message": "On-call override created.", "data": { "id": 12, "scheduleId": 3, "userId": 14, "startTime": "…", "endTime": "…", "user": { … } } }400Missing dates, an end before the start, or someone who isn't an active member.404No such schedule in this workspace.
/on-call/schedules/:id/primaryidcurl -X POST "https://api.zharp.io/api/v1/on-call/schedules/3/primary" \ -H "Authorization: Bearer $ZHARP_API_KEY"
{ "message": "On-call schedule has been set as primary successfully.", "data": null }404No such schedule in this workspace.
/on-call/schedules/:ididcurl -X DELETE "https://api.zharp.io/api/v1/on-call/schedules/3" \ -H "Authorization: Bearer $ZHARP_API_KEY"
{ "message": "On-call schedule deleted successfully.", "data": null }404No such schedule in this workspace.
Team groups
Named groups of members — Backend, Payments — that an escalation step can page together.
/team-groupsWho is in a group: GET /workspaces/:id/members?groupId=2.
curl "https://api.zharp.io/api/v1/team-groups" \ -H "Authorization: Bearer $ZHARP_API_KEY"
{ "message": "…", "data": [{ "id": 2, "workspaceId": 3, "name": "Backend", "description": null, "memberCount": 4, "createdAt": "…", "updatedAt": "…" }] }/team-groupsnamedescriptionmemberIdscurl -X POST "https://api.zharp.io/api/v1/team-groups" \
-H "Authorization: Bearer $ZHARP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Backend",
"memberIds": [
12,
14
]
}'{ "message": "Group created", "data": { "id": 2, "name": "Backend", "memberCount": 2, … } }400No name, or someone who isn't in the workspace.409A group with that name exists.
/team-groups/members/:userIduserIdgroupIdscurl -X PUT "https://api.zharp.io/api/v1/team-groups/members/14" \
-H "Authorization: Bearer $ZHARP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"groupIds": [
2,
6
]
}'{ "message": "…", "data": [{ "id": 2, "name": "Backend" }, { "id": 6, "name": "Payments" }] }400A group from outside the workspace.404They aren't in this workspace.
/team-groups/:ididnamecurl -X PUT "https://api.zharp.io/api/v1/team-groups/2" \
-H "Authorization: Bearer $ZHARP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Platform"
}'{ "message": "…", "data": { "id": 2, "name": "Platform", … } }404No such group in this workspace.409A group with that name exists.
/team-groups/:ididcurl -X DELETE "https://api.zharp.io/api/v1/team-groups/2" \ -H "Authorization: Bearer $ZHARP_API_KEY"
{ "message": "…", "data": null }404No such group in this workspace.
A WEBHOOK connection sends every monitor and incident event to your URL as it happens, signed — often simpler than polling. See Webhooks.
- Alert Channels — each connection type in the app.
- Escalation & On-call — how policies, severities and schedules work together.