Skip to content
  • There are no suggestions because the search field is empty.

Working with Alerts in the AlertOps SDKs

List alerts with `createdFrom`/`createdTo` and `viewBy` `A`, raise manual alerts through a quick-launch escalation policy, drive the lifecycle with the alert operations, and acknowledge through the notification link token with `auth.act_on_link`, because the web acknowledge action is refused on the tenant.

Overview

Relevant for Developers automating alert triage and response

An alert is opened by an inbound integration or by hand. It carries messages, and each message fans out to recipients as notifications over their contact methods, timed by the alert's escalation policy. alerts covers listing, manual creation, the lifecycle (assign, close, hold, snooze, merge, promote to incident) and the material around an alert: notes, attachments, custom fields, postmortems and drafts.

Key benefits:

  • Listing that works the first time — alerts.list_recent sets viewBy A and an exclusive createdTo of tomorrow, and the paginated variants (…All / …Iter) follow the alerts cursor.
  • Lifecycle helpers — reopen, hold, cancel and resolve hide the numeric status codes.
  • A working acknowledge path — auth.act_on_link runs the Acknowledge, Assign, Escalate or Close button from a notification.

Reading the operation names

Relevant for Developers using any of the five AlertOps SDKs

This guide applies to every AlertOps SDK. It names each operation by its resource path and operation name, such as groups.members.add_users, lists positional arguments in order, and shows request bodies and options as JSON. Where a task needs real code, the example is given in all five languages. Each SDK spells the same call in its own style:

SDKThe same callOptions and request fields
Pythonao.groups.members.add_users(group_id, [101, 102], roles=["Primary"])Keyword arguments, such as search="Net"
TypeScriptawait ao.groups.members.addUsers(groupId, [101, 102], ["Primary"])One trailing options object keyed by the names in this guide, such as { search: "Net" }
Goao.Groups.Members.AddUsers(ctx, groupID, []any{101, 102}, []any{"Primary"})&alertops.Request{Fields: alertops.M{"search": "Net"}} for endpoints, alertops.M{…} for helper options
Javaao.groups().members().addUsers(groupId, List.of(101, 102), List.of("Primary"))Request.of("search", "Net") for endpoints, a Map keyed by the names in this guide for helper options
.NETawait ao.Groups.Members.AddUsersAsync(groupId, new object?[] { 101, 102 }, new object?[] { "Primary" })new Kwargs { ["search"] = "Net" }
  • Names follow each language's style: update_basic is updateBasic in TypeScript and Java, UpdateBasic in Go and UpdateBasicAsync in .NET. Go keeps initialisms upper case (create_api is CreateAPI, post_to_url is PostToURL).
  • Paginated operations also have variants that collect every page or iterate lazily (…All / …Iter; in Python, .all() and .iter() on the operation).
  • Helpers that look something up return null (None in Python, nil in Go) when nothing matches. **ensure helpers** return the record together with a flag that says whether it was created.
  • Errors are the same classes in every SDK, such as NotFoundError and BadRequestError.
  • Code examples come in the order Python, TypeScript, Go, Java, .NET. Each assumes a client named ao (and, in Go, a ctx and a function that returns error), created as in that SDK's Getting Started article.

For one language's details, see Calling the API from Python, TypeScript, Go, Java or .NET.

Before you start

Relevant for Developers preparing a tenant for alert automation

  • Manual alerts need an escalation policy with quick_launch set to true (see Escalation Policies and Response Plays with the AlertOps SDKs).
  • Ingested alerts come from an integration's api_url (see Inbound Integrations, Alert Ingestion and Heartbeats with the AlertOps SDKs).
  • Use test recipients (for example @example.com users) while you build: creating, resending and escalating alerts sends real notifications.

List and export alerts

Relevant for Developers reading alerts for reports and triage

GET /alerts filters on creation date. Pass both dates as YYYY-MM-DD, and pass viewBy A for all alerts. The newest alerts come first. For example, alerts.list with these fields lists open alerts from four days:

{"viewBy": "A", "createdFrom": "2026-11-01", "createdTo": "2026-11-05", "status": "1", "limit": 100}
  • alerts.list_recent(1) lists the last day's alerts with viewBy A and createdTo set to tomorrow.
  • alerts.list_recent(7, {all_pages: true, priority: "Critical"}) follows every page and filters by priority.
  • alerts.export({viewBy: "A", createdFrom: "2026-11-03", createdTo: "2026-11-05"}) returns CSV text with the same filters.

createdTo is exclusive and viewBy defaults to "mine"
createdTo set to today leaves out today's alerts: pass tomorrow. Without viewBy A the API returns only the caller's own alerts, which is often an empty list for an API key.

FieldDescription
createdFrom, createdToYYYY-MM-DD; required in practice; createdTo is exclusive
viewByA for all alerts; the default is the caller's own
statusComma-separated status ids (see the status table below)
priority, owner, integration, escalationPolicy, subject, sourceFurther filters
limitPage size; the paginated variants follow metadata.next

A new alert can take a few seconds to appear in the list, so poll briefly after creating or ingesting one.

Create a manual alert

Relevant for Developers raising alerts from scripts and tools

alerts.create with this body returns the full alert, including alert_id, the first message_id and the status Open:

{
"escalation_policy": 31337,
"priority": "Critical",
"subject": "Disk full on db-01",
"description": "/var is at 98%",
"recipients": [{"recipient_type": "Group", "recipient_id": 12345},
{"recipient_type": "User", "recipient_id": 101}]
}

escalation_policy is the id of a policy with quick_launch true. recipient_type is User or Group. Pass response_play with a play id instead of escalation_policy to drive the alert with a response play.

Only quick-launch policies can raise manual alerts
With a policy whose quick_launch is false, POST /alerts answers "Invalid Escalation Policy or Response Play value". Turn it on with escalation_policies.update(ep_id, {quick_launch: true}).

Ingested events are appended, not duplicated
Events from the same integration source with an empty source id are appended to the open alert ("Mapped Appended"). A second ingest opens a new alert only after the first is closed.

Read an alert

Relevant for Anyone inspecting what happened to an alert

OperationReturns
alerts.get(alert_id)The alert: status, priority, escalation_policy.id, owner, is_incident and more
alerts.status_changes(alert_id)[{last_modified_date, user_name, status, owner, incident}]
alerts.timeline(alert_id)[{event_time, event_source, event_type, …}]
alerts.recipients(alert_id)The users on the alert
alerts.messages.summary(alert_id){inbound_events, users, notifications, acknowledged, email, sms, …}

Alert ingested through the SDK

Figure 1. An alert ingested through the SDK's API integration, after the SDK closed it. The summary shows owner Alice, the SDK-E2E NOC escalation policy and the technical service; the stats show 1 inbound event, 3 users and 3 notified, with 0 acknowledged.

Manage the lifecycle

Relevant for Developers assigning, closing and parking alerts

OperationEffect
alerts.assign(alert_id, {user: 101})Status Assigned; returns the alert
alerts.hold(alert_id)On Hold
alerts.reopen(alert_id)Open (also ends a hold or snooze)
alerts.snooze(alert_id, {snooze_hours: 2})1 to 168 hours; Snoozed
alerts.close(alert_id, {user: 101, resolution: "Disk cleaned"})Closes the alert
alerts.resolve(alert_id, 101, "Done")Closes, or cancels alerts that can't be closed; does nothing if already done
alerts.bulk_assign({user: 101, alerts: [1001, 1002]})Assigns several alerts
alerts.bulk_close({user: 101, alerts: [1001, 1002], resolution: "Batch fix"})Closes several alerts

Assigning an alert takes ownership and stops the escalation ladder through the default Stop Delivery on Assignment or Closed workflow. In the SDK-E2E run, an alert assigned straight away notified only the Primary; Secondary and Manager were never paged.

alerts.set_status(alert_id, {status: "<id>"}) takes a status id as a string and returns no body. The ids, from lookups.alert_status():

StatusId
Open1 (settable: reopens)
Closed2 (use close)
Cancelled4 (settable)
Assigned5 (use assign)
On Hold6 (settable)
Merged9 (use merge)
Snoozed10 (use snooze)

Merge, unmerge and promote

OperationEffect
alerts.merge({alerts: [child_id], parent_alert_id: parent_id})The child's status becomes Merged
alerts.merged_alerts(parent_id)[{alert_id, description, …}]
alerts.unmerge({alerts: [child_id], parent_alert_id: parent_id})The child reopens
alerts.promote_to_incident(parent_id, {…})Promotes the alert to an incident (body below)
{"message_text": "Customer impact", "escalation_policy_id": 31337, "topic_id": null,
"recipients": [{"recipient_id": 101, "recipient_type": "User"}]}

merge, unmerge, promote_to_incident and set_status return no body; re-read the alert to see the change.

Messages and notifications

Relevant for Developers following deliveries and nudging an escalation

OperationEffect
alerts.messages.latest_id(alert_id)The newest message_id
alerts.messages.notifications(alert_id, message_id)Per recipient: {recipient, notifications[{status, contact, date_sent}]}
alerts.messages.reply(alert_id, {message_text: "Investigating", escalation_policy_id: 31337, recipients: [{recipient_id: 101, recipient_type: "User"}]})Replies on the alert
alerts.messages.resend(alert_id, message_id, [{recipient_id: 101, recipient_type: "User"}])Sends the message again
alerts.messages.escalate(alert_id, message_id)"Escalated to <last>, <first>"
alerts.messages.escalate_to_next_group(alert_id, message_id)Moves to the next group
alerts.messages.cancel(alert_id, message_id)"Cancel Complete"
  • cancel works only while deliveries are still pending; afterwards it answers 400 "Nothing to Cancel".
  • escalate and escalate_to_next_group need a next user or group and the first delivery to have gone out. Right after create they answer "There is no user/group to escalate to".
  • To add people to an open alert, use alerts.add_recipients(alert_id, {…}) with the body below. The key is recipient, not recipient_id, and a recipient already on the alert gives 400 "Create not complete".
{"escalation_policy": 31337, "response_play": null,
"recipients": [{"recipient_type": "User", "recipient": "102", "send_message": true}]}

Acknowledge an alert

Relevant for Developers building acknowledge buttons, bots and responders

The web acknowledge action is refused on the tenant
alerts.acknowledge(alert_id) (POST .../messages/{id}/acknowledge) answers 400 "Alert is not enabled for acknowledge" on every alert the test suite could create. That held with every EP acknowledgement channel on, on centralized and user EPs, with the recipient's own key, and on an exact clone of a policy whose alerts allow it. Of 1,041 tenant alerts, the 114 that were acknowledged all went through a notification channel (email link, SMS, phone keypress or push), never through this action.

The new UI uses an alert-level call instead, alerts.acknowledge_alert(alert_id, {device_id: "", user_id: …}) (POST /alerts/{id}/acknowledge). It shows the button only when the alert's acknowledge_allowed is true. On this tenant the call answers 400 "Alert not acknowledged.", so it is held back by the same server-side gate. alerts.technical_services(alert_id) (v3) lists the technical services an alert belongs to.

Acknowledgement works through the token in the notification. Each alert email has Acknowledge, Assign, Escalate and Close buttons that link to https://app.alertops.com/<account>/alert/acknowledge?Token=<32 hex>&TokenType=DLV. Pass that link to auth.act_on_link:

  1. Copy the button link from the delivered notification (an Outlook safelinks-wrapped copy works too).
  2. Call auth.act_on_link(link).
  3. Read the result: the action (acknowledge) and the alert summary, whose message_status is now ACK and alert_status is still Open.

Python

action, summary = ao.auth.act_on_link(link)
print(action, summary["message_status"], summary["alert_status"]) # acknowledge ACK Open

TypeScript

const [action, summary] = await ao.auth.actOnLink(link);
console.log(action, summary.message_status, summary.alert_status); // acknowledge ACK Open

Go

action, summary, err := ao.Auth.ActOnLink(ctx, link, nil)
if err != nil {
return err
}
fmt.Println(action, alertops.Get(summary, "message_status"), alertops.Get(summary, "alert_status")) // acknowledge ACK Open

Java

PlatformHelpers.LinkResult result = ao.auth().actOnLink(link);
System.out.println(result.action() + " " + result.alert().path("message_status").asText()
+ " " + result.alert().path("alert_status").asText()); // acknowledge ACK Open

.NET

var (action, summary) = await ao.Auth.ActOnLinkAsync(link);
Console.WriteLine($"{action} {summary?["message_status"]} {summary?["alert_status"]}"); // acknowledge ACK Open

Verified with a real alert email on 2026-11-03: the message went PACK → ACK and the alert stayed Open. Acknowledging says "I saw it" and the ladder keeps going; assign or close the alert to stop it.

To call the steps yourself, split the link into its action, account, token and token type, then use the token operations:

SDKLink parser
Pythonparse_action_link in alertops.resources.auth
TypeScriptparseActionLink, exported by @alertops/sdk
Goalertops.ParseActionLink
JavaPlatformHelpers.parseActionLink in com.alertops.sdk.helpers
.NETAuthResource.ParseActionLink
  • auth.alert_by_token({tokenType, account, token}) returns {message_status, alert_status, can_assign, …}.
  • auth.acknowledge_by_token({account, token, token_type, contact: "", resolution: ""}) acknowledges.
  • auth.close_by_token({account, token, token_type, contact: "", resolution: "Fixed"}) closes.

One token, all four actions, one acknowledgement
The same token serves Acknowledge, Assign, Escalate and Close. A token acknowledges once: a second acknowledge answers 400 "Message not acknowledged.". A bad token answers 400 "Token is invalid". These endpoints are anonymous, so the token is the only credential: treat notification links like passwords.

Acknowledge and close by token were exercised live; assign and escalate use the same mechanism but weren't sent, to avoid extra emails.

Notes, attachments, fields, postmortems and drafts

Relevant for Developers enriching alerts and closing the loop

OperationEffect
alerts.notes.create(alert_id, {note: "Rolled back release 42"})Adds a note; returns it with note_id
alerts.notes.update(alert_id, note_id, {note: "Rolled back release 42 at 09:10"})Changes a note
alerts.attachments.add(alert_id, "df.txt", <bytes>, "text/plain")Uploads a file and returns its new row
alerts.attachments.download(alert_id, attachment_id)Raw bytes
alerts.update_fields(alert_id, [{alert_field_id: 3, alert_field_value: "db-01"}])Sets custom field values; take the ids from the alert's alert_fields
  • update_fields takes a bare list. An entry without alert_field_id blanks the field.
  • The legacy alerts.update needs status (a name) and applies only source fields (source, source_id …). It ignores subject, priority and description.

Postmortems exist only on closed alerts: postmortem.get answers 400 "is not closed" while the alert is open, then {} until one is saved. Create and update need every active postmortem field, so use alerts.postmortem.save(alert_id, {…}), which fills them in:

{"name": "db-01 disk", "owner": 101, "status": "Draft",
"impact_start": "2026-11-03T09:00", "impact_end": "2026-11-03T09:30", "duration": 30,
"values": {"Root Cause": "Log rotation disabled"}}

Drafts: alerts.drafts.create(body) takes the same body as alerts.create and returns an alert in status "Message Draft". The draft_id the draft endpoints use is that alert's message_id. drafts.find(subject) looks one up and drafts.send(draft_id, body) opens it. Deleting a draft leaves the backing alert in "Message Draft": it can't be closed, only cancelled (alerts.cancel, or resolve, which does that for you).

Operation Reference

Relevant for Developers looking up an operation

OperationHTTPWhat it does
alerts.list / list_recentGET /alertsFiltered list; cursor paging via metadata.next
alerts.exportGET /alerts/exportCSV text with the same filters
alerts.createPOST /alertsManual alert through a quick-launch EP or a response play
alerts.get / timeline / status_changesGET /alerts/{id}Record, event timeline, status history
alerts.assign / close / bulk_assign / bulk_closePOSTOwnership and resolution
alerts.set_status / reopen / hold / cancel / resolvePUT /alerts/{id}/statusStatus changes by id, or helpers
alerts.snooze / update_snoozePOST, PUT .../snoozePark for 1 to 168 hours
alerts.merge / unmerge / promote_to_incidentPOST, PUTGrouping and incidents
alerts.messages.notifications / summaryGETDelivery detail and counts
alerts.messages.reply / resend / escalate / cancelPOSTMessage actions
auth.act_on_linkhelperRun a notification button (acknowledge, assign, escalate, close)
auth.acknowledge_by_token / close_by_tokenPOST /auth/.../tokenToken actions, anonymous
alerts.notes / attachments / update_fieldsGET, POST, PUTEnrichment
alerts.postmortem.save / drafts.create / drafts.sendhelper, POSTPostmortems and drafts

Workflow and outbound logs (workflow_log, outbound_log, run_outbound_action) and the legacy preview are in each SDK's method reference.

Best Practices

Relevant for Anyone automating alerts in production

Query deliberately

  • Always pass viewBy A and a createdTo of tomorrow, or use list_recent.
  • Keep date ranges short and filter by integration or escalationPolicy instead of scanning everything.

Stop escalation the right way

  • Assign or close an alert to stop the ladder; acknowledging alone lets it continue.
  • Clean up with resolve, which handles drafts and already-closed alerts without errors.

Handle notification links as secrets

  • Don't log or store action links longer than needed; anyone with the link can act on the alert.
  • Act on each link once and read the result from the returned summary.

Configuration Examples

Relevant for Starting templates for alert automation

Example 1 — Triage: assign today's open critical alerts

StepOperation
1alerts.list_recent(1, {all_pages: true, status: "1", priority: "Critical"})
2For each alert: alerts.assign(alert_id, {user: 101})
3For each alert: alerts.notes.create(alert_id, {note: "Auto-assigned by triage bot"})

Example 2 — Count today's alerts per integration

Iterate alerts.list with its lazy paginated variant and {viewBy: "A", createdFrom: "2026-11-04", createdTo: "2026-11-05"}, and count the alerts by integration.id.

Example 3 — Chat bot: acknowledge from a pasted link

Call auth.act_on_link(link). On success, reply with the action and the summary's message_status and alert_status. On BadRequestError, reply with the error's messages, for example ["Message not acknowledged."] for a reused token.

Troubleshooting

Relevant for Anyone debugging alert calls

The list is empty, or today's alerts are missing

  • viewBy is missing, so only the caller's own alerts come back. Pass viewBy A.
  • createdTo is today; it is exclusive. Pass tomorrow, or use list_recent.
  • The alert is a few seconds old. Poll again shortly.

Create fails with "Invalid Escalation Policy or Response Play value"

  • The policy's quick_launch is false, or the id isn't a policy. Set quick_launch to true or use a quick-launch policy.

Acknowledge fails with 400 "Alert is not enabled for acknowledge"

  • The web action is refused on this tenant. Use auth.act_on_link(link) with the notification's button link.

Acknowledge by token fails with 400 "Message not acknowledged."

  • The token was already used to acknowledge. Read auth.alert_by_token(…): message_status is ACK already.

Token calls fail with 400 "Token is invalid"

  • The link was truncated or rewritten. Pass the full link, or the safelinks URL, to the link parser and check the token is 32 hex characters.

Escalate fails with "There is no user/group to escalate to"

  • The first delivery hasn't gone out yet, or there's no next member or group. Wait for messages.notifications to show a delivery, then retry.

Cancel fails with 400 "Nothing to Cancel"

  • Every delivery of the message has already been sent.

Postmortem calls fail

  • 400 "is not closed": close the alert first. A list of missing field names: use postmortem.save, which sends every active field.

A draft can't be closed

  • Draft-backed alerts can only be cancelled. Use alerts.cancel(alert_id) or resolve.

Related Articles

Need help?

Email support@alertops.com or contact your AlertOps representative.