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

Calling the API from .NET

Pass path ids as arguments and everything else in one Kwargs dictionary; its fields become the JSON body for POST/PUT/PATCH and query parameters otherwise, and failures throw typed errors such as NotFoundError.

Overview

Relevant for Developers writing calls against any endpoint

Every generated method takes three kinds of argument, in this order:

  1. Its path values, in path order. Each is an object: a number, a string or a JsonNode.
  2. One optional Kwargs options argument.
  3. A CancellationToken.

Kwargs follows the Python SDK's keyword convention exactly, so a Python example translates one to one. Responses are schema-free JsonNode values, because AlertOps response shapes vary by endpoint and sometimes by call. Text endpoints return string, and binary endpoints (exports, iCal) return byte[].

Path values and Kwargs

Relevant for Developers building requests

Kwargs is a Dictionary<string, object?> keyed by Python names. Every key that isn't reserved is a field.

KeyDescription
any fieldMerged into the JSON body for POST, PUT, PATCH and body-carrying DELETEs. Sent as a query parameter for every other method.
bodyAn explicit body (any JSON-convertible value, such as a JsonArray). Fields merge into it when it is an object. A present body key set to null sends no body at all.
paramsExtra query parameters, also for body methods.
filesMultipart parts: { ["file"] = new FilePart("log.txt", bytes, "text/plain") }. The body's fields become form fields.
headersExtra request headers.

Values can be CLR primitives, JsonNodes, dictionaries, sequences or anonymous objects. They are converted with AlertOpsJson.From.

await ao.Groups.ListAsync(new Kwargs { ["search"] = "Network", ["limit"] = 100 });      // GET ?search=…&limit=100
await ao.Groups.CreateAsync(new Kwargs { ["group_name"] = "Network Ops" }); // POST {"group_name": …}
await ao.Groups.Members.AddAsync(11, Kwargs.Body(new JsonArray(
new JsonObject { ["member_type"] = "User", ["member"] = "7", ["sequence"] = 1, ["roles"] = new JsonArray("Primary") })));
await ao.EscalationPolicies.UpdateBasicAsync(3, Kwargs.Body(wholeRecord)); // record may contain any key

Pass server records as body, not as fields
A record with a top-level body, params, files or headers key would be read as an option. Ingest payloads often have a body field. Whenever you send a record you read from the API, wrap it: Kwargs.Body(record).

Query values follow Python's rules:

  • true and false become true/false.
  • Sequences are comma-joined.
  • null values are dropped.
  • Numbers use Python's int/float formatting.

Path values are percent-encoded, so a name such as Email Official/2 is safe.

Pagination

Relevant for Developers reading long lists

Paginated endpoints have three methods: ListAsync (one page), ListAllAsync (every page collected) and ListIterAsync (an IAsyncEnumerable that fetches lazily).

var every = await ao.Groups.ListAllAsync(new Kwargs { ["search"] = "sdk" });
await foreach (var alert in ao.Alerts.ListIterAsync(new Kwargs { ["createdFrom"] = "2026-03-01", ["createdTo"] = "2026-03-08" }))
{
if ((string?)alert?["status"] == "Open") Console.WriteLine(alert!["alert_id"]);
}
StyleUsed byBehaviour
page indexgroups, users, policies, audit trail …limit + offset, where offset is a page index (0, 1, 2), not a row offset. Stops on a short page.
cursorv3 listspageSize + after, read from nextCursor / hasMore.
page numbernotification historypageNumber + pageSize.
metadata cursoralertslimit + next, read from metadata.next.
limit onlyinbound logOne page.

A list unwrapped from an envelope keeps the envelope, which holds paging metadata and counts. Read it with AlertOpsJson.RawOf(list).

Errors and retries

Relevant for Developers handling failures

Non-2xx answers throw a subclass of APIError (base AlertOpsException). Each carries:

  • Status, Body, Method and Url.
  • Op, the Python op id (such as Groups.get).
  • OpId, the dotted id (groups.get).
  • Messages, normalised from the AlertOps error shapes.
ErrorWhen
BadRequestError400 or 422
AuthenticationError401
PaymentRequiredError402, the feature is not in the plan
PermissionDeniedError403
NotFoundError404
ConflictError409
RateLimitError429 after retries
ServerError5xx
TransportErrornetwork failure after retries
ValueError / LookupError / TypeErrora helper rejected an argument, or a named thing (role, zone, type) does not exist

Retries are automatic:

  • Which calls:
  • GET, PUT and DELETE retry on 429, 502, 503 and 504.
  • POST retries only on 429 and on connect failures, so a create is never sent twice.
  • Delay: BackoffFactor × 2^attempt (default 0.5 s, capped at 8 s, with ±25 % jitter).
  • Retry-After: honoured when the server sends it, up to 32 s.
  • Cancellation: the CancellationToken cancels the call and its pending retries.

Listeners and the escape hatch

Relevant for Developers adding logging or calling unmodelled paths

ao.AddListener(e => Console.WriteLine($"{e.OpId} {e.Method} {e.Url} {e.Status} {e.Elapsed:0.000}s"));
await ao.RequestAsync("GET", "/groups", new Kwargs { ["search"] = "Net" }, @base: "v2");

Troubleshooting

Relevant for Anyone debugging a call

TypeError: … does not take a request body

  • You passed body to a GET. Use fields or params instead.

A PUT wiped fields on the record

  • Many AlertOps PUTs replace the whole record. Use the read-merge-write helpers (ao.EscalationPolicies.UpdateAsync, ao.Schedules.UpdateFieldsAsync), or GET the record, change it and PUT it back with Kwargs.Body(record).

Page 2 repeats page 1

  • You set offset to a row count. Offset is a page index. Use ListAllAsync or ListIterAsync instead.

Related Articles

Need help?

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