Administration, Account, Analytics and Lookups with the AlertOps SDKs
Use the admin resource for alert types, roles, topics and other account-wide settings, account and lookups for plan, time zone and limit checks, and analytics for reports, remembering that several of these objects can't be deleted and some writes are blocked for API keys.
Overview
Relevant for App Admins and developers automating account-wide settings
Account-wide configuration sits behind a few resources: administration objects (alert types, roles, topics, attributes), the account and its plan, the shared lookup catalogs the UI uses for its pickers, analytics reports, and the paid add-ons (call routing, AIOps). Most are plain CRUD. The surprises are objects that can't be deleted, writes that API keys may not make, and report parameters that change format between endpoints.
Key capabilities:
- Idempotent setup — admin.alert_types.ensure and the find helpers let scripts rerun without creating duplicates.
- Plan checks — lookups.can_add("User") tells you whether the plan allows one more user, group, policy or workflow.
- Readable dashboards — analytics.dashboard_sections turns the flat dashboard rows into a map keyed by section.
- Safe JSON configs — aiops.config and aiops.set_config handle AIOps' double-encoded config_data.
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:
| SDK | The same call | Options and request fields |
| Python | ao.groups.members.add_users(group_id, [101, 102], roles=["Primary"]) | Keyword arguments, such as search="Net" |
| TypeScript | await ao.groups.members.addUsers(groupId, [101, 102], ["Primary"]) | One trailing options object keyed by the names in this guide, such as { search: "Net" } |
| Go | ao.Groups.Members.AddUsers(ctx, groupID, []any{101, 102}, []any{"Primary"}) | &alertops.Request{Fields: alertops.M{"search": "Net"}} for endpoints, alertops.M{…} for helper options |
| Java | ao.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 |
| .NET | await 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 finding the right resource
| Resource | What lives there |
| admin | alert_types (+ fields), postmortem_fields, roles, topics, user_attributes, locations, bridges, audit_trail |
| account | Account summary, notification usage, plans, subscription, invoices, sso, quick_start |
| lookups | Read-only /app/* catalogs: users, groups, time zones, priority types, alert statuses, entitlements, limits … |
| analytics | Dashboard, reports, open_alerts drill-downs, on_call now and calendar |
| call_routing | Live call routing numbers (paid add-on) |
| aiops | AIOps correlation configs and simulations (add-on) |
| user_filters | The calling user's saved list views (new UI) |
| auth | The key's permission context and the web app's login and token-link flows |
Alert types, custom fields and postmortem fields
Relevant for App Admins defining alert templates
An alert type is a template of custom fields. Integrations, workflows and escalation policies each reference one. Find or create the type, add a field, then change the field with the read-merge-write helper:
Python
at, created = ao.admin.alert_types.ensure("Database Alert")
field = ao.admin.alert_types.fields.create(at["alert_type_id"], alert_field_name="Cluster",
data_type="ShortString", group_name="")
ao.admin.alert_types.fields.modify(at["alert_type_id"], field["alert_field_id"], required=True)
TypeScript
const [at, created] = await ao.admin.alertTypes.ensure("Database Alert");
const field = await ao.admin.alertTypes.fields.create(at.alert_type_id, {
alert_field_name: "Cluster", data_type: "ShortString", group_name: "",
});
await ao.admin.alertTypes.fields.modify(at.alert_type_id, field.alert_field_id, { required: true });
Go
at, created, err := ao.Admin.AlertTypes.Ensure(ctx, "Database Alert")
if err != nil {
return err
}
atID := alertops.Get(at, "alert_type_id")
field, err := ao.Admin.AlertTypes.Fields.Create(ctx, atID, &alertops.Request{Fields: alertops.M{
"alert_field_name": "Cluster", "data_type": "ShortString", "group_name": "",
}})
if err != nil {
return err
}
_, err = ao.Admin.AlertTypes.Fields.Modify(ctx, atID, alertops.Get(field, "alert_field_id"), alertops.M{"required": true})
Java
Shared.Ensured at = ao.admin().alertTypes().ensure("Database Alert"); // at.record(), at.created()
JsonNode atId = at.record().get("alert_type_id");
JsonNode field = ao.admin().alertTypes().fields().create(atId, Request.of(
"alert_field_name", "Cluster", "data_type", "ShortString", "group_name", ""));
ao.admin().alertTypes().fields().modify(atId, field.get("alert_field_id"), Map.of("required", true));
.NET
var (at, created) = await ao.Admin.AlertTypes.EnsureAsync("Database Alert");
var atId = at!["alert_type_id"]!;
var field = await ao.Admin.AlertTypes.Fields.CreateAsync(atId, new Kwargs
{
["alert_field_name"] = "Cluster", ["data_type"] = "ShortString", ["group_name"] = "",
});
await ao.Admin.AlertTypes.Fields.ModifyAsync(atId, field!["alert_field_id"]!, new Kwargs { ["required"] = true });
Alert types can't be deleted
DELETE /alert_types/{id} answers 200 but the alert type stays (an unknown id is 404). Don't create alert types per test run: reuse one with alert_types.ensure. alert_types.update(atid, {alert_type_name}) renames.
Field PUTs replace the whole row, so send the full field (including sequence) or use fields.modify. Postmortem fields work the same way: admin.postmortem_fields.create({field_name: "Customer impact"}) returns field_id and sequence, and update is a full replace.
Roles, topics and other admin objects
Relevant for App Admins managing access and routing settings
Roles and entitlements
Custom roles are a name plus a set of entitlements. Entitlement ids come from lookups.entitlements(), which returns [{module, entitlements[{id, name}]}] grouped by module.
| Step | Operation | Result |
| 1 | admin.roles.create({role: "Integration Editors", entitlements: [{entitlement_id: <id>}, …]}) | The role, with role_id |
| 2 | admin.roles.assign_users(role_id, [101, 102]) | The role's users |
| 3 | admin.roles.unassign_users(role_id, [101, 102]) | [] |
| 4 | admin.roles.delete(role_id) | Deletes the role |
role_id is a GUID (lower case in lists, upper case in detail), and get, update and delete accept the id or the role name. update replaces the entitlement set.
Unassign a role before deleting it
Deleting is blocked for system roles and for roles that still have users. If a user is deleted while still holding a custom role, that role can no longer be deleted. Unassign every user with roles.unassign_users before you delete either the users or the role.
Topics, user attributes, bridges and the audit trail
| Operation | Body or arguments |
| admin.topics.create | {"topic_name": "Network", "priority": "High", "template": "<p>Network alerts</p>"} |
| admin.topics.groups.add(topic_id, …) | {"groups": [67890]} |
| admin.user_attributes.create | {"attribute_name": "Badge", "user_attribute_data_type": "ShortString", "sequence": 1} |
| admin.bridges.create | {"name": "War room", "telephone_number": "+1 202-555-0142", "telephone_access_code": "1234", "telephone_message_body": "Dial +1 202-555-0142,,1234#", "message_priority_type": "High"} |
| admin.audit_trail.between(from, to) | Two dates, such as 2026-11-01 and 2026-11-04 |
- Topics — priority is the priority name. Topic groups are added with {groups: [group_id]}.
- User attributes — the definitions are one tenant-wide ordered list (sequence). Data types are ShortString, LongString, Boolean, Numeric, Date and Time. Set values per user with users.attributes.
- Bridges — telephone_number is normalised to digits (+1 202-555-0142 reads back as 12025550142).
- Audit trail — dateFrom and dateTo are MM/DD/YYYY. between takes dates or strings and follows every page. Filter by auditItem from lookups.audit_items().
Location and holiday writes are refused for API keys
Every POST, PUT and DELETE under /location returns 403 "The API key doesn't have permissions to perform the request", even with the account owner's key. Reads (admin.locations.list(), admin.locations.holidays.list()) work. Manage locations and holidays in the UI.
Account, plan and SSO
Relevant for App Admins reading plan and account settings
| Operation | Returns |
| account.summary() | timezone_standard_name, zone_name, product_package_name, sso … |
| account.time_zone() | "(UTC-06:00) Central Time (US & Canada)": the name user and schedule bodies take |
| account.notification_usage() | Usage per channel, such as monthly.email_messages → {maximum: "Unlimited", current: "42"} |
notification_usage values are strings, and an unlimited channel reads "Unlimited" (not -99). account.plans(), account.subscription.get() and account.invoices.list() are reads. On a trial account the subscription has plan_id: null and the invoice list is empty.
Subscription writes are billing actions
subscription.create, cancel, update_plan, update_address, update_card and update_contact change plans, cards or cancel the account. The same goes for account.update_licenses (new UI), which buys or adjusts paid seats with {users: n} or {stakeholders: n}. They carry the BILLING flag and are never called by the live test suites. Run them only on purpose.
SSO is one record, and PUT replaces it. account.sso.modify(changes) reads the record, applies the changes and writes it back. Writing back the identical record changes nothing (verified). The algorithm field is spelled saml_alogorithm. account.quick_start.update is 402 "Package settings do not have Quickstart enabled" on packages without quick start.
Lookups and plan limits
Relevant for Developers filling pickers and checking limits
lookups returns the unpaged lists the UI uses: users(), groups(), time_zones(), priority_types(), alert_status(), alert_types(), integrations(), escalation_policies() and more. Status ids are fixed: 1 Open, 2 Closed, 4 Cancelled, 5 Assigned, 6 On Hold, 9 Merged, 10 Snoozed.
| Operation | Returns |
| lookups.can_add("User") | true when the plan allows one more. Also Group, EscalationPolicy, Workflow … |
| lookups.time_zone_name("America/Chicago") | "(UTC-06:00) Central Time (US & Canada)" |
| lookups.limits("EscalationPolicy") | The limit record; maximum -99 means unlimited |
time_zone_name raises LookupError for an unknown zone. lookups.roles() gives role labels only; use admin.roles.list() for role_id.
Analytics reports
Relevant for Analysts and developers exporting incident metrics
Every report is a read-only GET with query parameters. Dates are YYYY-MM-DD (MM/DD/YYYY is also accepted). Missing or unparseable dates don't fail: the server falls back to its default window, so check your dates.
| Operation | Returns |
| analytics.dashboard_sections(from, to, {priority: "Critical"}) | Sections such as mtta ("005:42:05") and status ({"Open": "3", …}); values are strings |
| analytics.report("time_to_ack", from, to, {priority: "High", group: [67890]}) | Any report by op name |
| analytics.on_call.now({group_id: 67890}) | Who is on call now |
The dashboard takes priority ids, the other reports take names
analytics.dashboard's priorityTypeIdList needs this account's priority type ids (from lookups.priority_types()); 1 or Critical fails with 400 "Invalid Priority Type ID". The other reports' priority takes the priority name (High) and silently ignores an id. dashboard_sections with priority: "Critical" resolves the id for you.
Other quirks seen live:
- dashboard returns a flat list of {section, category, category_value, datavalue} with string values. dashboard_sections groups it.
- time_to_resolve names its extrema min_time_to_ack and max_time_to_ack, like time_to_ack.
- time_to_ack with a topic filter returns 500 (a server bug).
- alert_assignment spells its count total_assgined, and alert_analysis_groups starts with a Total Count row.
- Group filters are comma-separated ids. report and dashboard_sections join lists for you.
- on_call.calendar without dates returns today. call_routing.activity uses MM/DD/YYYY, unlike the reports.
Add-ons: call routing and AIOps
Relevant for Developers using paid features
Call routing
Without the call routing add-on, every /call_routing* route answers 402 "Package settings do not have CallRouting enabled." (PaymentRequiredError) before the id is looked up, so an unknown id is 402 too. The pickers still work: call_routing.catalog.phone_numbers(), create_alert_options() and languages(). With the add-on, call_routing.update changes how real phone calls are routed, and call_routing.set_voice_prompt(cid, "welcome", {text}) sets a prompt as text-to-speech or a recording.
AIOps configs and simulations
AIOps configs are addressed by id: 3 production correlation profiles, 4 the auto-merge switch, 7 the test (simulation) profile. A config's payload is a JSON string inside JSON ({"config_data": "<JSON string>"}), on both read and write.
| Operation | Result |
| aiops.profiles(3) | The decoded production integration_groups |
| aiops.config(7) | The simulation config, decoded, or null if the add-on has no config |
| aiops.set_config(7, cfg) | Re-encodes config_data and writes it |
| aiops.simulations.start(7) | 201, simulation_status Pending, with a run_id |
| aiops.simulations.is_finished(7, run_id) / results(7, run_id) | Poll the run, then read its results |
Writing config 3 or 4 changes live alerts
A PUT on config 3 changes live correlation, and enabling config 4 turns on automatic merging of live alerts. Run experiments on config 7 and simulations only. Write back exactly what you read: profiles may hold numbers as strings (time_threshold_mins: "15").
A simulation whose window or integrations hold too few alerts is rejected with 400 and an alertcount in the error body. Alerts ingested minutes earlier aren't counted yet, so simulate over history. Compare simulation_status case-insensitively (Completed live). aiops.create_config is 403 for API keys; configs are written with update_config or set_config.
Saved views and authentication
Relevant for Developers scripting the new UI's views and checking keys
user_filters stores the API-key user's saved list views (new UI). moduleTypeId 1 is Alerts, and attributes is a JSON string that save serialises for you. save creates the view or replaces the one with the same name. There is no single-view GET (405), so use find.
| Operation | Result |
| user_filters.save("Open and assigned", {status: ["Open", "Assigned"]}) | The view, with filterId |
| user_filters.clone(filter_id, {newFilterName: "Open and assigned (copy)"}) | A copy |
| user_filters.set_default(filter_id, {isDefault: true}) | Makes it the default view |
| user_filters.share(filter_id, {grants: [{userId: 102}, {groupId: 7}]}) | {"accessIds": [282]} |
| user_filters.delete(filter_id) | Deletes the view |
auth.me() returns the key owner's context (userId, username, organization, capabilities, featureLimits), and auth.organization() returns the account subdomain. Login, refresh, logout and activation are web-app flows: logout and SSO logout need a UI session token, and without one the SDKs raise UnsupportedAuthError before sending anything. The notification-link actions (auth.act_on_link(link)) work without a key; they are covered in Working with Alerts in the AlertOps SDKs.
Operations
| Operation | HTTP | What it does |
| admin.alert_types.ensure(name) | GET + POST /alert_types | Find or create an alert type. Returns the type and a created flag |
| admin.alert_types.fields.modify(atid, fid, changes) | GET + PUT .../fields/{id} | Change one custom field safely |
| admin.roles.create / assign_users / unassign_users / delete | /roles | Custom roles and their users |
| admin.topics.*, admin.user_attributes.*, admin.bridges.* | /topics, /user_attributes, /bridges | CRUD plus find(name) |
| admin.audit_trail.between(from, to) | GET /audit_trail | Every audit row in a date range |
| account.summary() / time_zone() / notification_usage() | GET /account, /account/notification_usage | Account summary, display time zone, channel usage |
| account.sso.modify(changes) | GET + PUT /account/sso | Change SSO settings safely |
| lookups.can_add(item) / time_zone_name(zone) | GET /app/limits/{item}, /app/time_zone | Plan check, IANA zone to display name |
| analytics.dashboard_sections(from, to, {priority}) | GET /analytics/dashboard | Dashboard grouped by section |
| analytics.report(name, from, to, filters) | GET /analytics/* | Any report by op name (time_to_ack, alert_details …) |
| analytics.on_call.now({group_id}) / calendar({startDate, endDate}) | GET /schedules/oncallnow, /schedules/calendar | Who is on call |
| aiops.config(id) / set_config(id, cfg) / simulations.start(7) | /addon/{id}/config, /addon/{id}/simulate | AIOps configs and simulations |
| user_filters.save(name, attrs) / find(name) | v3 /user_filters | Saved views |
| auth.me() / organization() | GET {v3}/auth/me | The key's permission context |
Each SDK's method reference lists every operation, including the billing writes and the call routing access endpoints.
Best Practices
Relevant for Anyone automating account-wide settings
Setup scripts
- Use ensure and find helpers so scripts can rerun. Alert types especially, since they can't be deleted.
- Check lookups.can_add(...) before creating users, groups, policies or workflows in bulk.
- Take time zone names from account.time_zone() or lookups.time_zone_name(...), never hand-typed.
Safety
- Never call subscription writes or AIOps config 3 and 4 writes from automation unless that is the job.
- Unassign roles before deleting users or roles.
- Read before you write: SSO, alert fields and AIOps configs are whole-record writes.
Reports
- Pass dates explicitly. A bad date silently becomes the default window.
- Use priority names for reports, and dashboard_sections with a priority name for the dashboard.
Configuration Examples
Relevant for Starting templates for common setups
Example 1 — Minimal: idempotent account baseline
| Step | Operation |
| 1 | admin.alert_types.ensure("Database Alert") |
| 2 | admin.topics.find("Network"); only if it returns null, admin.topics.create({topic_name: "Network", priority: "High"}) |
| 3 | account.time_zone() and lookups.can_add("Group") before creating groups |
Example 2 — Typical: a weekly metrics export
Call analytics.report("time_to_ack", start, today) and analytics.report("time_to_resolve", start, today) for the last seven days. Each result has a created_dates list plus mean_time_to_ack_list or mean_time_to_resolve_list; zip the three lists into rows of date, mean time to acknowledge and mean time to resolve, and write them to a CSV file.
Example 3 — Dashboard and on-call together
analytics.dashboard_sections(today - 7 days, today) and analytics.on_call.now() are independent reads, so the async clients can run them concurrently. Read status from the dashboard and each row's group_name from the on-call list.
Troubleshooting
Relevant for Anyone debugging administration and analytics calls
An alert type is still there after delete
- DELETE /alert_types/{id} is a no-op that answers 200. Rename the type or leave it, and reuse it with ensure.
A role can't be deleted
- It is a system role, or users still hold it. Run roles.unassign_users(role_id, user_ids) and retry. A role that was assigned to a user who has since been deleted can't be removed.
403 "The API key doesn't have permissions to perform the request"
- Location and holiday writes are refused for every API key. Use the UI.
- aiops.create_config is refused too. Use aiops.set_config.
400 "Invalid Priority Type ID" from the dashboard
- priorityTypeIdList got a name or a small number. Pass an id from lookups.priority_types(), or use dashboard_sections with priority: "High".
A report ignores the priority or date filter
- Reports other than the dashboard take the priority name, not the id.
- The dates were missing or unparseable, so the default window was used. Use YYYY-MM-DD.
500 from time_to_ack
- The topic filter triggers a server error. Filter by integration or group instead.
402 PaymentRequiredError
- Call routing: the add-on isn't purchased ("Package settings do not have CallRouting enabled.").
- Quick start writes: the package has no quick start.
- Status pages: the page limit is reached (see Status Hub Pages, Incidents and Maintenance with the AlertOps SDKs).
400 with alertcount from simulations.start
- The simulation window holds too few alerts. Widen from_date/to_date in config 7 or add integrations with history.
UnsupportedAuthError
- The operation needs a UI session token (logout, SSO logout, AI chat). Pass a session token to the client, or use the UI.
Related Articles
- Users, Groups and On-Call Schedules with the AlertOps SDKs
- Status Hub Pages, Incidents and Maintenance with the AlertOps SDKs
- Working with Alerts in the AlertOps SDKs
- Getting Started with the AlertOps Python SDK
- Getting Started with the AlertOps TypeScript SDK
Need help?
Email support@alertops.com or contact your AlertOps representative.