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

Workflows, Outbound Integrations and Maintenance Windows with the AlertOps SDKs

Build workflows with workflows.build, enable them and attach them only to escalation policies of the same alert type; treat outbound integration PUTs as whole-record replaces, and schedule maintenance windows in the account time zone.

Overview

Relevant for Developers automating what happens after an alert arrives

Workflows are rules that react to alerts, messages or notification deliveries: when conditions match, they run actions such as changing priority, cancelling deliveries or calling a webhook. An Alert workflow only fires for alerts of an escalation policy it is attached to. Outbound integrations define the external calls (methods) that workflows and escalation policies can make. Maintenance windows suppress alerts from chosen inbound integrations for a scheduled period.

Key capabilities:

  • One-call workflow build — workflows.build creates the workflow, adds conditions and actions in order and can enable it.
  • Catalog helpers — workflows.catalog.alert_type_id and condition_id resolve names to the ids the API wants.
  • Safe outbound edits — outbound.update_settings keeps the method list when you change a name or credentials.
  • Date-time-based windows — maintenance_windows.schedule builds the window body from start and end date-times.

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 setting up the client

ResourceWhat lives there
workflowsWorkflow CRUD, conditions, actions, access, catalog
escalation_policies.workflowsAttaching workflows to an escalation policy (list, available, add, remove)
outboundOutbound integrations, methods, catalog (templates, security types, web methods)
maintenance_windowsMaintenance window CRUD and the schedule helper

Build a workflow

Relevant for Developers creating automation rules

A workflow has a type, an alert type (for Alert and Message workflows), start conditions and actions.

Workflow typeType idAlert typeTypical actions
Alert2An alert type such as Standard AlertUpdate Priority, Update Status, SendMessage, Send Webhook
Message1A message type such as Standard MessageCancelDeliveries
Notification10None (use alert type id 0 in the catalogs)CancelUserDeliveries

workflows.build(name, workflow_type, options) resolves the alert type, creates the workflow disabled, adds each condition and action, and enables it only if you pass enabled: true. It returns the full workflow, including workflow_id; alert_type reads back as the name, such as Standard Alert 1. Build an Alert workflow with the escalation policy's alert type (alert_type_id from escalation_policies.get_basic(12345)) and these options:

{
"alert_type": 7,
"conditions": [{"name": "AlertStatus", "operator": "is", "value": "On Hold"}],
"actions": [{"name": "Update Priority", "value": "Low"}]
}

The workflow builder showing an SDK-built workflow

Figure 1. The "SDK-E2E Hold to Low priority" workflow built with workflows.build. The header shows type Alert and alert type Standard Alert 1 (the escalation policy's type, not the default Standard Alert). The builder shows one Match All condition (Alert Status is On Hold) and one action (Update Priority to Low).

Alert type is an id on create and a name on read
POST /workflows takes alert_type as the alert type id. The record and PUT /basic use alert_type as the name and expose the id as alert_type_id. build accepts either a name or an id and resolves it. Without alert_type, it picks Standard Alert (or Standard Message).

Conditions and actions

Condition bodies are {type, match, name, operator, value, list_id}. type is start (default) or stop, and match is all (default) or any. Time conditions use the operators > and <. Action bodies are {name, value, …}, where name is an action name from the catalog.

OperationEffect
workflows.conditions.add(wid, {name: "AlertStatus", operator: "is", value: "Open"})Adds a condition; returns it with condition_id
workflows.actions.add(wid, {name: "Update Priority", value: "High"})Adds an action; returns it with action_id
workflows.actions.update(wid, action_id, {name: "Update Priority", value: "Low"})Changes an action
ActionFields
Update Priorityvalue: a priority name (Low, High …)
Update Statusvalue: a status (Closed …), optional message_text
SendMessagevalue: an escalation policy or response play name, plus users[] and groups[] names. Targets come from actions.targets_available(wid)
Send Webhookvalue: the web method, plus webhook_url and message_text

conditions.add and actions.add return the new record (condition_id, action_id). Their updates and deletes return no body.

Look up condition and action names

The catalogs are per workflow type and alert type. Use the helpers to resolve ids, then read the allowed values.

OperationReturns
workflows.catalog.types()[{id, name, alert_types[{id, name}]}]
workflows.catalog.alert_type_id("Alert", "Standard Alert")The alert type id
workflows.catalog.actions(2)Action names for Alert workflows
workflows.catalog.conditions(2, alert_type_id, {exclude: "aifields"})Condition fields
workflows.catalog.condition_id("Alert", alert_type_id, "AlertStatus")One condition's id
workflows.catalog.condition_values(2, alert_type_id, condition_id)Allowed values, such as the message_thread_status_type names

For Notification workflows, catalog.delivery_attempt_fields() lists the <<keyword>> fields, and the conditions catalog takes alert type id 0: workflows.catalog.conditions(10, 0).

Recurring workflows and stop conditions

A recurring workflow re-runs every recurrence_interval while its start conditions hold, until a stop condition matches. workflows.build("Lower priority hourly until closed", "Alert", options) with these options lowers the priority every hour until the alert closes:

{
"scheduled": true, "recurrence_interval": 60,
"conditions": [{"name": "AlertStatus", "operator": "is", "value": "Open"},
{"type": "stop", "name": "AlertStatus", "operator": "is", "value": "Closed"}],
"actions": [{"name": "Update Priority", "value": "Low"}]
}

Stop conditions need a recurring workflow
A type: "stop" condition on a workflow without scheduled: true and a recurrence_interval fails with 400 "Stop condition is not allowed in an non-recurring workflow".

Enable and attach a workflow

Relevant for On-call admins putting a workflow into effect

A workflow does nothing until it is enabled and attached to an escalation policy. Once both are true it fires on that policy's live alerts.

  1. Enable it: workflows.enable(wid) (or workflows.set_status(wid, {enabled: true})).
  2. Attach it: escalation_policies.workflows.add(12345, [{workflow_id: wid}]).
  3. Check where it is attached: workflows.usage(wid) returns [{id, name, is_escalation_policy}].

Attach only enabled workflows of the policy's alert type
POST /escalation_policies/{id}/workflows accepts a workflow only if it is enabled and has the same alert type as the policy. Otherwise it fails with 400 "Workflow ID: <id> is a duplicate or not found". catalog.alert_type_id("Alert") returns Standard Alert, which isn't the policy's alert type on every tenant, so pass the policy's alert_type_id to build. escalation_policies.workflows.available(ep_id) lists exactly the workflows you can attach.

PUT /status enables a workflow even with no conditions or actions. The UI blocks that, but the API doesn't, so check the workflow before enabling it. Enabling an attached workflow makes it fire on live alerts.

Workflow operations

OperationHTTPWhat it does
workflows.build(name, type, {alert_type, conditions, actions, scheduled, recurrence_interval, enabled})POST + POSTs + GETCreate with conditions and actions, optionally enable
workflows.list and its paginated variants / find(name)GET /workflowsList (query search, workflowtype, enabled, sortBy). offset is a page index
workflows.get(id)GET /workflows/{id}Workflow with conditions[] and actions[]
workflows.update_basic(id, …)PUT /workflows/{id}/basicName, enabled, alert_type (name), scheduled
workflows.enable(id) / disable(id)PUT /workflows/{id}/statusSwitch on or off, then read back
workflows.clone(id, {name})POST /workflows/{id}/cloneFull copy with new condition and action ids
workflows.usage(id)GET /workflows/{id}/usageWhere it is attached
workflows.delete_if_exists(id)PUT status + DELETEDisable, then delete. false if already gone
workflows.conditions.* / workflows.actions.*/workflows/{id}/conditions, /actionslist, add, update, delete
workflows.access.*/workflows/{id}/accessCustom access, admins, owner transfer
escalation_policies.workflows.add(ep_id, [{workflow_id}])POST /escalation_policies/{id}/workflowsAttach. remove detaches (DELETE with a body)

Access works like the other editors: grantees need a workflow entitlement (Basic-role users fail with "User: <id> is invalid"), admins_available is empty until custom access is on, and transfer_owner needs a user who already has access. Each SDK's method reference lists every operation.

Outbound integrations and methods

Relevant for Developers connecting AlertOps to external systems

An outbound integration holds security settings and one or more methods (REST or SOAP calls). Creating one sends nothing. It only calls out when a workflow or escalation policy uses one of its methods.

  1. Create it: outbound.create({outbound_integration_name: "Ticketing API"}). security_type defaults to None.
  2. Add a REST method: outbound.methods.add_rest(outbound_id, "Create ticket", "https://tickets.example.com/api/issues", {…}) with the options below. It returns the method with its method_id.
  3. Check where it is used: outbound.usage(outbound_id).
{"request_data": "{\"title\": \"AlertOps alert\"}", "headers": {"Content-Type": "application/json"}}

add_rest builds a REST/JSON method body for the Standard Alert alert type. Pass alert_type (a name) for another type, and web_method for anything other than POST.

PUT replaces the whole record, methods included
PUT /integrations/outbound/{id} stores exactly the methods you send: an empty list deletes every method. Each echoed method needs alert_type as the alert type name, but reads only carry alert_type_name, so sending a record back unchanged fails with 404 "Alert Template: not found". Use outbound.update_settings(id, changes), which reads the record, copies alert_type_name into alert_type on every method and writes it back.

For example, outbound.update_settings(outbound_id, {outbound_integration_name: "Ticketing API (prod)"}) renames the integration and keeps its methods.

security_type: "Basic" requires a password in security. The available types come from outbound.catalog.web_security_types() (Basic, BPPM Auth Token, None, OAUTH 2 SessionID).

OperationHTTPWhat it does
outbound.create({outbound_integration_name, security_type, security, methods})POST /integrations/outboundCreate. Returns the full record
outbound.list({name}) / find(name)GET /integrations/outboundBare list. outbound_integration_id is a string here
outbound.get(id) / usage(id)GETRecord with methods[] and usage (workflows, escalation policies, response plays)
outbound.update_settings(id, changes)GET + PUTChange name or security, keep methods
outbound.delete(id) / delete_if_exists(id)DELETEBlocked while the integration is used or bidirectional
outbound.methods.add_rest(id, name, uri, …)POST /{id}/methodsAdd a REST/JSON method
outbound.methods.get(id, method_id)GET /{id}/method/{method_id}One method (singular method in the path)
outbound.methods.update / clone / deletePUT / POST / DELETE /{id}/methods/{method_id}Update (full body, returns nothing), clone (with a name), delete
outbound.catalog.template_detail(template_id)GET /app/outbound/templates/{id}A template with its JSON-string fields parsed

Reads add alert_type_id and alert_type_name to each method, and method_type.headers is a JSON string.

Maintenance windows

Relevant for On-call admins silencing alerts during planned work

A maintenance window suppresses alerts from the inbound integrations it lists, once, daily, weekly or monthly. maintenance_windows.schedule(description, integration_ids, start, end, options) builds the body from date-times and returns the created window, with its maintenance_window_id and a status such as Planned. For example, "Saturday patching" for integration 54321 from 2026-11-07 02:00 to 04:00.

Dates and times are in the account time zone
start_date, start_time, end_date and end_time are read as the account's local time, not UTC. Check the zone with account.summary() → zone_name (IANA) before you build date-times.

For recurring windows, pass recurrence and the days. Daily selects all seven days for you. For example, "Thursday deploys" from 2026-11-12 03:00 to 2026-12-31 05:00 with the options {recurrence: "Weekly", days: ["thursday"]}.

Weekly windows must span at least a week
A Weekly window whose end date is less than a week after its start date is rejected ("Start Date and End Date can not be same; Difference ... should be atleast a week").

OperationHTTPWhat it does
maintenance_windows.schedule(description, integration_ids, start, end, {recurrence, days})POST /maintenancewindowsCreate from date-times
maintenance_windows.list({maintenanceStatus})GET /maintenancewindowsBare list. Current, Planned, Past or All
maintenance_windows.find(description, status)GETFind a window by its description (status defaults to All)
maintenance_windows.get(id)GET /maintenancewindows/{id}One window. integration reads back as [{id, name}]
maintenance_windows.update(id, body)PUT /maintenancewindows/{id}Whole record, same body as create. Returns nothing
maintenance_windows.delete(id) / delete_if_exists(id)DELETEDelete

Writes take bare integration ids; reads return [{id, name}]. To build an update body from date-times, use the window-body builder and pass its result to maintenance_windows.update:

SDKWindow-body builder
Pythonwindow_body in alertops.resources.maintenance
TypeScriptwindowBody, exported by @alertops/sdk
Goalertops.WindowBody
JavaAlertsHelpers.windowBody in com.alertops.sdk.helpers
.NETMaintenanceWindowsResource.WindowBody

Best Practices

Relevant for Anyone running workflows and maintenance in production

Workflows

  • Build and test workflows while they are unattached, then enable and attach.
  • Pass the escalation policy's alert_type_id to build instead of relying on the Standard Alert default.
  • Use escalation_policies.workflows.available(ep_id) to confirm a workflow can be attached before you call add.
  • Disable before deleting. delete_if_exists does both.

Outbound

  • Change outbound settings only through update_settings. Never PUT a record you didn't read in full.
  • Set each method's alert_type deliberately. add_rest defaults to Standard Alert.

Maintenance

  • Build date-times in the account time zone and keep windows as short as the work allows.
  • List every integration the work affects. Integrations that aren't listed keep alerting.

Configuration Examples

Relevant for Starting templates for common setups

Example 1 — Lower priority when an alert is put On Hold (verified live)

This is the workflow from the SDK end-to-end scenario. It reads the policy's alert type, builds and enables the workflow in one call, then attaches it:

Python

ep = ao.escalation_policies.get_basic(ep_id)
wf = ao.workflows.build("Hold to Low priority", "Alert", alert_type=ep["alert_type_id"], enabled=True,
conditions=[{"name": "AlertStatus", "operator": "is", "value": "On Hold"}],
actions=[{"name": "Update Priority", "value": "Low"}])
ao.escalation_policies.workflows.add(ep_id, [{"workflow_id": wf["workflow_id"]}])

TypeScript

const ep = await ao.escalationPolicies.getBasic(epId);
const wf = await ao.workflows.build("Hold to Low priority", "Alert", {
alert_type: ep.alert_type_id, enabled: true,
conditions: [{ name: "AlertStatus", operator: "is", value: "On Hold" }],
actions: [{ name: "Update Priority", value: "Low" }],
});
await ao.escalationPolicies.workflows.add(epId, [{ workflow_id: wf.workflow_id }]);

Go

ep, err := ao.EscalationPolicies.GetBasic(ctx, epID, nil)
if err != nil {
return err
}
wf, err := ao.Workflows.Build(ctx, "Hold to Low priority", "Alert", alertops.M{
"alert_type": alertops.Get(ep, "alert_type_id"), "enabled": true,
"conditions": []any{alertops.M{"name": "AlertStatus", "operator": "is", "value": "On Hold"}},
"actions": []any{alertops.M{"name": "Update Priority", "value": "Low"}},
})
if err != nil {
return err
}
_, err = ao.EscalationPolicies.Workflows.Add(ctx, epID,
&alertops.Request{Body: []any{alertops.M{"workflow_id": alertops.Get(wf, "workflow_id")}}})

Java

JsonNode ep = ao.escalationPolicies().getBasic(epId);
JsonNode wf = ao.workflows().build("Hold to Low priority", "Alert", Map.of(
"alert_type", ep.get("alert_type_id"), "enabled", true,
"conditions", List.of(Map.of("name", "AlertStatus", "operator", "is", "value", "On Hold")),
"actions", List.of(Map.of("name", "Update Priority", "value", "Low"))));
ao.escalationPolicies().workflows().add(epId, List.of(Map.of("workflow_id", wf.get("workflow_id"))));

.NET

var ep = await ao.EscalationPolicies.GetBasicAsync(epId);
var wf = await ao.Workflows.BuildAsync("Hold to Low priority", "Alert", new Kwargs
{
["alert_type"] = ep?["alert_type_id"], ["enabled"] = true,
["conditions"] = new JsonArray(new JsonObject { ["name"] = "AlertStatus", ["operator"] = "is", ["value"] = "On Hold" }),
["actions"] = new JsonArray(new JsonObject { ["name"] = "Update Priority", ["value"] = "Low" }),
});
await ao.EscalationPolicies.Workflows.AddAsync(epId,
Kwargs.Body(new JsonArray(new JsonObject { ["workflow_id"] = wf?["workflow_id"]?.DeepClone() })));

To check it, put an open alert of that policy On Hold with alerts.set_status(alert_id, {status: "6"}). In the end-to-end run, a few seconds later the alert's priority changed from High to Low.

Example 2 — Stop paging once a message is acknowledged

workflows.build("Cancel on ack", "Message", options) with:

{"conditions": [{"name": "MessageStatusType", "operator": "is", "value": "Acknowledged"}],
"actions": [{"name": "CancelDeliveries"}]}

Example 3 — Build and enable in one call

workflows.build("Failed delivery cleanup", "Notification", options) with these options returns a workflow whose workflow_type_name is Notification and alert_type_id is 0:

{"enabled": true,
"conditions": [{"name": "DeliveryStatusType", "operator": "is", "value": "Failed"}],
"actions": [{"name": "CancelUserDeliveries"}]}

Troubleshooting

Relevant for Anyone debugging workflows, outbound calls or maintenance

"Workflow ID: <id> is a duplicate or not found" when attaching

  • The workflow is disabled. Enable it with workflows.enable(wid) first.
  • The workflow's alert type differs from the policy's. Compare workflows.get(wid) → alert_type_id with escalation_policies.get_basic(ep_id) → alert_type_id, and rebuild with the policy's id.
  • The workflow is already attached. Check escalation_policies.workflows.list(ep_id).

"Stop condition is not allowed in an non-recurring workflow"

  • Create the workflow with scheduled: true and a recurrence_interval, or drop the stop condition.

The workflow never fires

  • It isn't attached to the policy that owns the alerts. Check workflows.usage(wid).
  • It's disabled, or its conditions don't match. Read workflows.get(wid) and compare values with catalog.condition_values.

Deleting a missing workflow raises BadRequestError

  • DELETE /workflows/{id} answers 400 "Workflow ID: <id> not found" (a GET answers 404). Use delete_if_exists, which treats both as already gone.

404 "Alert Template: not found" on an outbound PUT

  • A method was sent back without alert_type (the name). Use outbound.update_settings.

All outbound methods disappeared

  • A PUT was sent with methods: [] or without the methods. Re-add them with methods.add_rest, and use update_settings from now on.

A weekly maintenance window is rejected

  • The end date is less than a week after the start date. Extend end to at least seven days after start.

A maintenance window starts at the wrong hour

  • The date-times were built in UTC. Rebuild them in the account time zone (account.summary() → zone_name).

Related Articles

Need help?

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