Alert Sources API
Create, configure and test Incident Management alert sources: ingest tokens, optional HMAC or Bearer authentication, payload mapping, maintenance windows and per-source statistics.
An alert source is an inbound integration. Each webhook source has its own ingest URL (https://uptimeify.io/api/im/ingest/<token>) and a payload mapping that turns the vendor's JSON into an alert. The setup guides explain how to point Zabbix, Datadog, Grafana and others at that URL. This page covers the endpoints that manage the sources themselves.
Authentication
Every endpoint needs IM access: an IM-eligible role (admin, editor, responder) or an organization-wide API token. Incident Management must be enabled for the organization. A customer-scoped token is rejected with 403 Forbidden (customerScopedTokenForbidden).
- Read endpoints (list, detail, presets, payloads, stats, test-mapping) are open to every IM-eligible caller.
- Write endpoints (create, update, delete, token and secret rotation, disable-auth, test-alert) additionally require the role
adminor a team-admin membership in the source's team. An API token carries the role of the user who created it, so use a token created by an organization admin.
Sources are scoped to your organization. A source of another organization answers 404 Not Found (imSourceNotFound), never 403.
The source object
List, detail, update and disable-auth return this shape. Credentials never appear in it: the ingest token, the bearer token and the HMAC secret are only returned once, by the endpoint that mints them.
| Field | Type | Description |
|---|---|---|
id | number | Source ID. |
organizationId | number | Owning organization. |
teamId | number | Team whose escalation the source feeds. |
name | string | Display name, up to 120 characters. |
type | string | zabbix, datadog, grafana, alertmanager, sentry, custom, api, email, or uptimeify_monitoring (the built-in bridge from classic monitoring, created by the platform). |
authMode | object | { url_token?, hmac?: { enabled }, bearer?: { enabled } }. See Optional request authentication. |
payloadMapping | object | Field mapping applied to every inbound payload. |
autoResolve | boolean | Whether a resolve event closes the incident automatically. Copied onto each new incident. |
groupingWindowMinutes | number or null | Grouping window. null groups by dedup key only. |
maintenance | object or null | Recurring maintenance windows, { windows: [...] }. |
snoozeUntil | ISO 8601 or null | Ad hoc mute until this instant. |
secondaryExpiresAt | ISO 8601 or null | Until when the previous ingest token still works after a rotation. |
createdAt, updatedAt | ISO 8601 | Timestamps. |
{
"id": 7,
"organizationId": 1,
"teamId": 3,
"name": "Zabbix production",
"type": "zabbix",
"secondaryExpiresAt": null,
"payloadMapping": {
"title": "trigger_name",
"severity": "event_severity",
"status": "trigger_status",
"host": "host_name",
"dedupKey": "event_id",
"severityMap": { "Disaster": "sev1", "High": "sev2", "Average": "sev3", "Warning": "sev4", "Information": "sev4", "Not classified": "sev4" },
"resolveValues": ["RESOLVED", "OK"]
},
"autoResolve": true,
"groupingWindowMinutes": null,
"maintenance": null,
"snoozeUntil": null,
"createdAt": "2026-10-01T08:00:00.000Z",
"updatedAt": "2026-10-01T08:00:00.000Z",
"authMode": {}
}List sources
GET /api/im/sources
Returns all sources of your organization as an array of source objects, sorted by name.
curl "$BASE_URL/api/im/sources" \
-H "Authorization: Bearer $TOKEN"Get a source
GET /api/im/sources/:id
Returns one source object.
List presets
GET /api/im/sources/presets
Returns the built-in presets you can pass as type when creating a source. docsSlug is the setup guide under /api/incident-management/alert-sources/, vendorTemplate is a ready-made vendor config (only Zabbix ships one, a media type YAML), samplePayload is the test payload test-mapping and test-alert fall back to.
[
{
"name": "zabbix",
"docsSlug": "zabbix",
"vendorTemplate": "zabbix_export:\n version: '7.4'\n ...",
"samplePayload": { "event_id": "6633487", "event_severity": "Disaster", "trigger_name": "Zabbix agent is not available on Zabbix server", "trigger_status": "PROBLEM", "host_name": "Zabbix server" }
},
{ "name": "datadog", "docsSlug": "datadog", "vendorTemplate": null, "samplePayload": { "alert_id": "1234567", "alert_title": "[Triggered] High CPU usage on host web-01" } }
]The full list is zabbix, datadog, grafana, alertmanager, sentry, custom (sample payloads shortened above).
Create a source
POST /api/im/sources
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
teamId | number | Yes | Team in your organization. |
name | string | Yes | 1 to 120 characters, trimmed. |
type | string | Yes | A preset name, api (no ingest URL, fed through Events Ingest) or email (inbound mail address). |
payloadMapping | object | No | Omit it to start from the preset's mapping. An explicit value, even {}, replaces the preset. For email only the nested emailSelectors key is kept, see the Email guide. |
autoResolve | boolean | No | Default true. |
groupingWindowMinutes | number or null | No | Positive integer, or null (default). |
authMode | object | No | Only url_token and enabled: false flags are accepted here. HMAC and Bearer are switched on by their own endpoints below. |
payloadMapping accepts two shapes. The flat form uses string selectors title, severity, status, host, dedupKey, plus severityMap (raw value to sev1 .. sev4), resolveValues (array of strings) and custom (object of selectors). The attribute form is { "version": 2, "attributes": [...] }, each attribute { key, standard?, steps?, flags? } with steps { kind: "extract", type: "path" | "jsonpath" | "constant", expr } or { kind: "valueMap", entries: [{ from, to }], fallback? }. Both forms accept defaultSeverity (sev1 .. sev4, default sev3), used when the payload yields no valid severity.
Example (cURL)
curl -X POST "$BASE_URL/api/im/sources" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "teamId": 3, "name": "Zabbix production", "type": "zabbix" }'Response
200 OK: the source object plus ingestToken. The token is shown only in this response. Only its SHA-256 hash is stored, it cannot be read back later. Build the webhook URL as https://uptimeify.io/api/im/ingest/<ingestToken>. For type: "api" the token is null. For type: "email" the response also carries inboundEmailAddress (<token>@alerts.uptimeify.io).
{
"id": 7,
"organizationId": 1,
"teamId": 3,
"name": "Zabbix production",
"type": "zabbix",
"secondaryExpiresAt": null,
"payloadMapping": { "title": "trigger_name", "dedupKey": "event_id" },
"autoResolve": true,
"groupingWindowMinutes": null,
"maintenance": null,
"snoozeUntil": null,
"createdAt": "2026-10-01T08:00:00.000Z",
"updatedAt": "2026-10-01T08:00:00.000Z",
"authMode": {},
"ingestToken": "Q2hR4mVx9LkP0sTz7bNw1cYe5uJa3fGd"
}Update a source
PATCH /api/im/sources/:id
Send only the fields you want to change. The write bar is checked on the current team, and on the target team when you move the source.
| Field | Type | Description |
|---|---|---|
name | string | 1 to 120 characters. |
teamId | number | Moves the source to another team of the same organization. |
payloadMapping | object | Replaces the whole mapping, it is not merged. null sets an empty mapping. |
authMode | object | Merged into the stored value. hmac.enabled: true / bearer.enabled: true only work once a secret exists (see below). bearer.token_hash is rejected. |
autoResolve | boolean | |
groupingWindowMinutes | number or null | null clears the window. |
maintenance | object or null | { "windows": [{ "dow": [1,2,3,4,5], "from": "22:00", "to": "23:30", "timezone": "Europe/Berlin" }] }. from/to as 24 h HH:MM, dow ISO weekdays 1 (Monday) to 7, timezone an IANA zone, at most 20 windows. null clears it. Alerts inside a window are suppressed. |
snoozeUntil | ISO 8601 or null | Mutes the source until this instant. A past value is accepted and has no effect. null clears it. |
The ingest token cannot be changed here, use rotate-token.
curl -X PATCH "$BASE_URL/api/im/sources/7" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "snoozeUntil": "2026-10-09T18:00:00Z", "groupingWindowMinutes": 15 }'200 OK: the updated source object.
Delete a source
DELETE /api/im/sources/:id
Refused with 409 while the source still has open alerts or is the primary source of an open incident (anything not resolved or merged). Resolve those first. The monitoring bridge source (uptimeify_monitoring) cannot be deleted.
200 OK: { "success": true }.
The 409 (imSourceReferencesExist) body lists what blocks the delete (each list capped at 50):
{
"statusCode": 409,
"statusMessage": "Source is still referenced by open alerts or incidents",
"data": {
"code": "imSourceReferencesExist",
"openAlertCount": 2,
"openIncidents": [{ "id": "42", "title": "Database connection pool exhausted" }]
}
}Rotate the ingest token
POST /api/im/sources/:id/rotate-token
No request body. Mints a new ingest token. The previous token keeps working for 7 days (until secondaryExpiresAt), so you can update the vendor configuration without losing alerts. The new token is shown only in this response. For an email source, the new inbound address is <ingestToken>@alerts.uptimeify.io.
{
"ingestToken": "Vn8kT1qZ4xLb7RwE0mYc2sHa9uJd6fPg",
"secondaryExpiresAt": "2026-10-16T09:30:00.000Z"
}Optional request authentication
The token in the URL is always required. On top of it you can require a signature or a header:
- HMAC: the sender adds
X-Uptimeify-Timestamp(Unix seconds, at most 5 minutes off) andX-Uptimeify-Signature, the hex HMAC-SHA256 of<timestamp>.<raw body>with the source's secret. Asha256=prefix is accepted. - Bearer: the sender adds
Authorization: Bearer <token>.
A request that fails either check is answered exactly like an unknown token (404). authMode.url_token is stored but has no effect.
Set the HMAC secret
POST /api/im/sources/:id/set-hmac-secret
| Field | Type | Required | Description |
|---|---|---|---|
secret | string | No | Your own secret, at least 16 characters. Omit it to have a 256-bit secret generated. |
Enables HMAC (authMode.hmac.enabled: true). Calling it again replaces the secret, and the old one stops working immediately. The secret is stored encrypted and returned only in this response:
{ "secret": "c2VjcmV0LWV4YW1wbGUtbm90LXJlYWwtMTIzNDU2Nzg5MA", "hmacEnabled": true }Rotate the bearer token
POST /api/im/sources/:id/rotate-bearer-token
No request body. Mints a 256-bit bearer token and enables Bearer auth. A previous bearer token stops working immediately. Only a hash is stored, the token is returned only in this response:
{ "token": "b3V0LW9mLWJhbmQtZXhhbXBsZS10b2tlbi1ub3QtcmVhbA", "bearerEnabled": true }Disable HMAC or Bearer
POST /api/im/sources/:id/disable-auth
| Field | Type | Required | Description |
|---|---|---|---|
mode | string | Yes | hmac or bearer. |
Sets authMode.<mode>.enabled to false and returns the source object. The secret stays stored, so PATCH with { "authMode": { "hmac": { "enabled": true } } } turns it back on without a new secret. Disabling a mode that is already off succeeds.
Test a mapping
POST /api/im/sources/:id/test-mapping
Runs the source's stored mapping against a payload without creating anything. Payload order: payload from the body, else the most recent payload the source received, else the preset's sample payload. A mapping failure is not an HTTP error: the response is 200 with ok: false.
| Field | Type | Required | Description |
|---|---|---|---|
payload | object | No | The JSON payload to map. |
curl -X POST "$BASE_URL/api/im/sources/7/test-mapping" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "payload": { "event_id": "6633487", "event_severity": "Disaster", "trigger_name": "Zabbix agent is not available on Zabbix server", "trigger_status": "PROBLEM", "host_name": "Zabbix server" } }'{
"ok": true,
"alert": {
"title": "Zabbix agent is not available on Zabbix server",
"severity": "sev1",
"status": "open",
"host": "Zabbix server",
"dedupKey": "6633487",
"custom": {}
},
"attributes": [
{ "key": "title", "standard": true, "value": "Zabbix agent is not available on Zabbix server", "resolved": true, "flags": {} },
{ "key": "severity", "standard": true, "value": "sev1", "resolved": true, "flags": {} },
{ "key": "status", "standard": true, "value": "PROBLEM", "resolved": true, "flags": {} },
{ "key": "host", "standard": true, "value": "Zabbix server", "resolved": true, "flags": {} },
{ "key": "correlationId", "standard": true, "value": "6633487", "resolved": true, "flags": { "grouped": true } }
]
}On failure: { "ok": false, "error": "title selector resolved to nothing on this payload" }.
alert.status is resolved only when the mapped status value equals resolved (case-insensitive) after resolveValues, anything else is open. Without a dedupKey selector the dedup key is the SHA-1 of title plus host.
Fire a test alert
POST /api/im/sources/:id/test-alert
Queues a real alert through the normal ingest pipeline. It usually opens a real incident and pages whoever is on call for the source's team. Payload: payload from the body, else the preset's sample payload (no fallback to recently received payloads). The body is limited to 256 KB and 20 nesting levels, like the ingest URL.
| Field | Type | Required | Description |
|---|---|---|---|
payload | any JSON | No | Payload to ingest. Required for api and email sources, which have no sample. |
200 OK: { "enqueued": true }.
Recent payloads
GET /api/im/sources/:id/payloads
Up to the last 10 raw payloads the source received, newest first. Useful to build a mapping. Kept in a short-lived cache, so an empty list is normal for a quiet source.
{ "payloads": [ { "event_id": "6633487", "trigger_status": "PROBLEM" } ] }Source statistics
GET /api/im/sources/:id/stats
Daily counters for the last 30 days with data, oldest first. day is a UTC calendar date. alerts counts inbound alerts, deduped those folded into an existing open alert, incidents the incidents opened.
{
"days": [
{ "day": "2026-10-07", "alerts": 41, "deduped": 33, "incidents": 3 },
{ "day": "2026-10-08", "alerts": 12, "deduped": 9, "incidents": 1 }
]
}Common errors
401 Unauthorized(unauthorized) when not authenticated403 Forbidden(customerScopedTokenForbidden) for a customer-scoped token403 Forbidden(imAccessDenied) when the session has no IM-eligible role403 Forbidden(imNotEnabled) when Incident Management is not enabled for the organization403 Forbidden(imTeamWriteDenied) on a write endpoint withoutadminrole or team-admin membership400 Bad Request(invalidRequestBody) when:idis not a positive integer,teamId/name/typeis missing or invalid, a mapping/maintenance/snoozeUntilvalue is malformed,authMode.bearer.token_hashis sent,modeis nothmac/bearer, or a supplied HMAC secret is shorter than 16 characters404 Not Found(imSourceNotFound) when the source does not exist or belongs to another organization422 Unprocessable Entity(invalidTeamId) whenteamIdis not a team of the organization422 Unprocessable Entity(imAuthModeNotProvisionable) when enabling HMAC or Bearer via create/PATCHbefore a secret exists409 Conflict(imSourceReferencesExist) when deleting a source that still has open alerts or incidents409 Conflict(imSourceProtected) when deleting the monitoring bridge source400 Bad Request(imSourceHasNoIngestToken) when rotating the token of anapisource400 Bad Request(imNoTestPayloadAvailable) when test-mapping or test-alert has no payload to use413 Payload Too Large(payloadTooLarge),422 Unprocessable Entity(invalidJson,payloadTooDeep) on test-alert503 Service Unavailable(unavailable) when test-alert cannot queue the alert, safe to retry
Forward or route alert emails from any monitoring tool, ticketing system, or mailbox to a dedicated inbound address, and turn them into incidents automatically.
Routing Rules
List, create, update and delete Incident Management routing rules: which team a new incident is assigned to, and an optional severity override.