Getting Started with the AlertOps Python SDK
Install the SDK with pip, put your API key in ALERTOPS_API_KEY or a .env file, and create an AlertOps() client; every v2 and v3 endpoint is then a method such as ao.groups.list().
Overview
Relevant for Developers automating AlertOps from Python
The AlertOps Python SDK (alertops-sdk) wraps the whole AlertOps REST surface (the v2 and v3 APIs, alert ingestion and the public status viewer) in one client object. Each endpoint is an op on a resource, such as ao.groups.list or ao.escalation_policies.create, and the same definitions drive a synchronous client (AlertOps) and an asynchronous one (AsyncAlertOps). The registry has 784 ops across 27 root resources, all listed in docs/REFERENCE.md.
Key benefits:
- Complete — every endpoint in the AlertOps API reference maps to an SDK op, and a unit test fails if one is missing.
- Verified live — every op either returned a 2xx against a real tenant or carries a documented reason why it can't.
- One definition, two clients — the sync and async clients share every endpoint and every helper.
- Quirks handled — response envelopes are unwrapped, page-index offsets are paginated correctly, and three error formats map to one exception tree.
Installation
Relevant for Developers setting up a project
The SDK needs Python 3.10 or later. Its only runtime dependency is httpx. Install it from PyPI:
pip install "alertops-sdk[semantic]"
The [semantic] extra adds PyYAML, which the semantic layer (alertops.semantic) uses to load the AlertOps semantic spec. Leave it out if you only call endpoints.
To work on the SDK itself (tests, linting), clone its repository and install the dev extra into a virtual environment:
python -m venv .venv && . .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e ".[dev]"
Everything you need is importable from the top-level package:
from alertops import AlertOps, AsyncAlertOps, ClientConfig, APIError, NotFoundError
Configure the client
Relevant for Developers connecting to a tenant
AlertOps() with no arguments reads ALERTOPS_* environment variables. You can pass any setting as a keyword argument instead, and keyword arguments always win.
| Setting | Argument | Environment variable | Default |
| API key | api_key= (also the first positional argument) | ALERTOPS_API_KEY | none (required for API-key endpoints) |
| v2 base URL | v2_base_url= | ALERTOPS_V2_BASE_URL (falls back to ALERTOPS_BASE_URL) | https://app.alertops.com/api/v2 |
| v3 base URL | v3_base_url= | ALERTOPS_V3_BASE_URL | https://app.alertops.com/api/v3 |
| Ingest base | notify_base_url= | ALERTOPS_NOTIFY_BASE_URL | https://notify.alertops.com |
| UI session token | session_token= | ALERTOPS_SESSION_TOKEN | none (only for session-only endpoints) |
| AI chat backend | ai_chat_base_url= | ALERTOPS_AI_CHAT_BASE_URL | none (no public default) |
| AI alert backend | ai_alert_base_url= | ALERTOPS_AI_ALERT_BASE_URL | built-in default |
| Timeout | timeout= | ALERTOPS_TIMEOUT | 30.0 seconds |
| Retries | max_retries= | ALERTOPS_MAX_RETRIES | 3 |
from alertops import AlertOps
ao = AlertOps() # ALERTOPS_API_KEY from the environment
ao = AlertOps("your-api-key", timeout=60) # explicit key and a longer timeout
ao = AlertOps(dotenv=".env") # read a .env file as well
Precedence
Settings resolve in this order, highest first:
- Keyword arguments to AlertOps(...) (a None value is ignored).
- The config=ClientConfig(...) object, if you pass one. When you pass config=, environment variables and dotenv are not read at all.
- Values from the dotenv= file.
- Exported ALERTOPS_* environment variables.
- The ClientConfig defaults.
A .env file beats exported variables
AlertOps(dotenv=".env") applies the file's KEY=VALUE lines on top of the process environment, so a stale ALERTOPS_API_KEY exported in your shell can't shadow the key in the file. The reader accepts # comments, export KEY=value lines and quoted values. A missing file is silently ignored.
ClientConfig field reference
ClientConfig is a frozen dataclass. Build one when you want to share settings between clients or keep configuration out of the environment.
| Field | Description |
| api_key | AlertOps API key. Sent as api-key to v2 and as x-api-key to v3. |
| session_token | UI session token, sent as Authorization. Only needed for login, refresh, activation and AI backends, which reject API keys. |
| v2_base_url | Root of the v2 REST API. Default https://app.alertops.com/api/v2. |
| v3_base_url | Root of the v3 REST API, also used for the unauthenticated status viewer (public base). Default https://app.alertops.com/api/v3. |
| notify_base_url | Alert ingestion host for integration api_urls. Default https://notify.alertops.com. |
| ai_chat_base_url | AI chat backend. No default: ops on this base raise ConfigurationError until you set it. |
| ai_alert_base_url | AI alert backend. Has a built-in default. |
| timeout | Per-request timeout in seconds. Default 30.0. |
| max_retries | Retries after the first attempt for retryable failures. Default 3. |
| backoff_factor | Base of the exponential backoff, in seconds. Default 0.5 (0.5 s, 1 s, 2 s …). |
| backoff_max | Cap on one backoff delay, in seconds. Default 8.0. |
| user_agent | User-Agent prefix. Default alertops-sdk-python (the SDK appends /<version>). |
| extra_headers | Headers added to every request, for example a proxy header. Default {}. |
from alertops import AlertOps, ClientConfig
cfg = ClientConfig(api_key="your-api-key", timeout=60.0, max_retries=5,
extra_headers={"X-Request-Source": "nightly-sync"})
ao = AlertOps(config=cfg)
You can also pass your own httpx.Client (or httpx.AsyncClient for the async client) as http_client=. The SDK then doesn't close it for you.
Authentication
Relevant for Developers wondering which header to send
You don't set auth headers yourself. Each op declares its base (v2, v3, public, notify, ai_chat, ai_alert) and its auth mode, and the client picks the header:
| Op auth | Base | Header sent |
| key | v2 (and every non-v3 base) | api-key: <api_key> |
| key | v3 | x-api-key: <api_key> |
| session | any | Authorization: <session_token> |
| none | public, notify and anonymous auth routes | no auth header |
The key is checked when you make a call, not when you create the client. A missing key raises ConfigurationError("No AlertOps API key: pass api_key= or set ALERTOPS_API_KEY"). A session-only op called without a session token raises UnsupportedAuthError, which says the op "needs a UI session token (session_token=); API keys are not accepted".
Your first calls
Relevant for Developers checking that the connection works
Start with read-only lookups. They are unpaged, cheap and change nothing.
from alertops import AlertOps
with AlertOps() as ao: # closes the HTTP connection pool on exit
print(ao.account.time_zone()) # account time zone display name
print([p.name for p in ao.lookups.priority_types()])
print(ao.lookups.time_zone_name("America/Chicago")) # "(UTC-06:00) Central Time (US & Canada)"
for g in ao.groups.list.iter(): # every group, all pages
print(g.group_id, g.group_name)
noc = ao.groups.find("NOC") # case-insensitive name match, or None
if noc:
print(ao.groups.get(noc.group_id).group_name)
Methods used in this article
| Method | HTTP | What it does |
| ao.lookups.users() | GET /app/users | Every user, unpaged: user_id, user_name, first_name, last_name. |
| ao.lookups.groups() | GET /app/groups | Every group, unpaged: group_id, group_name. |
| ao.lookups.priority_types() | GET /app/priority_types | Critical, High, Medium, Low, No Priority. |
| ao.lookups.time_zones() | GET /app/time_zone | [{id, name, zone}]. The name is what user and schedule bodies take. |
| ao.lookups.time_zone_name(zone) | flow | Maps an IANA zone to the display name AlertOps expects. |
| ao.lookups.can_add(item_name) | flow | True if the plan allows another User, Group, EscalationPolicy or Workflow. |
| ao.account.time_zone() | flow | The account time zone display name. |
| ao.groups.list(...) | GET /groups | One page of groups (limit, offset page index, search). |
| ao.groups.get(group_id) | GET /groups/{group_id} | One group. |
| ao.groups.find(name) | flow | The group whose name matches (case-insensitive), or None. |
| ao.request(method, path, base=…) | any | Escape hatch for a path the SDK doesn't model. |
For every other op, see docs/REFERENCE.md.
Result types
Relevant for Developers reading responses
The SDK returns light, schema-free containers instead of typed models, because AlertOps response shapes vary by endpoint and sometimes by call.
| Type | What it is | Notes |
| AOObject | A dict with attribute access: group.group_name equals group["group_name"]. | Nested objects are AOObject too. A missing attribute raises AttributeError, so use .get("key") for optional fields. |
| AOList | A list of results. | .raw keeps the original envelope (paging metadata, counts) when the SDK unwrapped one. Lists collected with .all() have .raw = None. |
| None | Endpoints that answer with an empty body (many PUTs and DELETEs). | |
| bytes | CSV, iCal and file downloads (ops flagged non_json). | |
| str | A 2xx response whose body isn't JSON. |
groups = ao.groups.list(limit=25) # AOList of AOObject rows, envelope "groups" unwrapped
print(len(groups), groups.raw.keys()) # the full original response is still there
csv_bytes = ao.groups.export() # bytes
id_of(obj) returns the first id-like field of a record (id, user_id, group_id, escalation_policy_id …), which helps when an endpoint answers with a different id alias than you expect:
from alertops import id_of
ep = ao.escalation_policies.create(escalation_policy_name="Payments", priority="Critical",
enabled=True, quick_launch=False)
ep_id = id_of(ep, "escalation_policy_id")
The request() escape hatch
Relevant for Developers calling a path the SDK doesn't model yet
ao.request(method, path, *, base="v2", auth="key", **kw) calls any path with the same auth, retries, error mapping and envelope parsing as a modelled op. It follows the normal call convention: keyword fields become the JSON body for POST/PUT/PATCH and query parameters for GET/DELETE, and body=, params=, files= and headers= work as usual.
pages = ao.request("GET", "/status_pages", base="v3") # x-api-key sent automatically
ao.request("POST", "/groups", body={"group_name": "sdk-example", "description": [],
"dynamic": False, "topics": []})
ao.request("DELETE", "/groups/12345/access/admins", body=[{"user_id": 101}]) # DELETE may carry a body
Prefer the modelled op when one exists
request() exists for gaps. The modelled ops carry envelope unwrapping, pagination (.iter() / .all()) and documented gotchas that request() doesn't know about.
Where to go next
Relevant for Developers planning what to read
| If you want to | Read |
| Understand path params, body vs query, pagination, errors and retries | Calling the API: Parameters, Pagination and Errors |
| Use asyncio, or write helpers that work on both clients | Async Client and Composable Flows |
| Create users, groups and rotations | Users, Groups and On-Call Schedules with the AlertOps SDKs |
| Build escalation ladders | Escalation Policies and Response Plays with the AlertOps SDKs |
| Turn on-call intent into correct config | Planning On-Call Setups with the AlertOps Semantic Layer |
| Send alerts in and work with them | Inbound Integrations, Alert Ingestion and Heartbeats with the AlertOps SDKs; Working with Alerts in the AlertOps SDKs |
| See how the SDK itself is tested | Testing and Verifying the SDK |
Best Practices
Relevant for Anyone running the SDK in scripts or services
Credentials
- Keep the API key in .env or a secret store, never in source control or command-line history.
- Use AlertOps(dotenv=".env") in scripts so a key exported in your shell can't override the intended tenant.
- Treat the url returned by ao.users.schedules.webcal(user_id) and ao.schedules.webcal(group_id) as a secret: it embeds an API key.
Client lifetime
- Create one client per process and reuse it: it holds an HTTP connection pool.
- Use with AlertOps() as ao: (or call ao.close()) in short-lived scripts so connections are released.
Start read-only
- Test a new key with lookups (ao.lookups.priority_types(), ao.account.time_zone()) before any write.
- Check plan limits with ao.lookups.can_add("User") before bulk creation.
Configuration Examples
Relevant for Starting templates for common setups
Example 1 — Minimal script
from alertops import AlertOps
ao = AlertOps() # export ALERTOPS_API_KEY=... first
for u in ao.users.list.iter(search="ann"):
print(u.user_id, u.user_name)
Example 2 — Project with a .env file
# .env
ALERTOPS_API_KEY=your-api-key
ALERTOPS_TIMEOUT=60
ALERTOPS_MAX_RETRIES=5
from alertops import AlertOps
with AlertOps(dotenv=".env") as ao:
group, created = ao.groups.ensure("sdk-example-noc") # get or create, returns (group, created)
print(group.group_id, "created" if created else "existing")
Example 3 — Explicit config shared by a sync and an async client
import asyncio
from alertops import AlertOps, AsyncAlertOps, ClientConfig
cfg = ClientConfig(api_key="your-api-key", timeout=45.0)
ao = AlertOps(config=cfg)
async def main():
async with AsyncAlertOps(config=cfg) as aao:
print(await aao.account.time_zone())
asyncio.run(main())
Troubleshooting
Relevant for Anyone debugging a first connection
ConfigurationError: No AlertOps API key
- The key wasn't found when the call ran. Export ALERTOPS_API_KEY, pass api_key=, or pass dotenv= pointing to a file that defines it.
- If you passed config=ClientConfig(...), the environment isn't read: set api_key on the config.
AuthenticationError (401) "Invalid API Key"
- The key is wrong or revoked, or a stale exported key is shadowing the right one. Use AlertOps(dotenv=".env") and check ao.config.api_key.
- Check that v2_base_url and v3_base_url point at the tenant's host.
ConfigurationError: base 'ai_chat' has no URL configured
- The AI chat backend has no public default. Set ALERTOPS_AI_CHAT_BASE_URL or pass ai_chat_base_url=. These ops also need a UI session token.
UnsupportedAuthError: needs a UI session token
- The op is session-only (login, refresh, activation, AI backends). API keys are rejected by the server, so the SDK refuses before sending. Pass session_token= from a signed-in UI session, or use an API-key alternative.
AttributeError on a result
- AOObject raises AttributeError for a field the response doesn't contain. Use row.get("field") for optional fields, and print the row to see the real shape.
Related Articles
- Calling the API from Python: Parameters, Pagination and Errors
- Async Client and Composable Flows in the Python SDK
- Users, Groups and On-Call Schedules with the AlertOps SDKs
- Administration, Account, Analytics and Lookups with the AlertOps SDKs
- Testing and Verifying the Python SDK
Need help?
Email support@alertops.com or contact your AlertOps representative.