From 973c08bc5c5a258027ab376c8a6deaf346cad260 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Thu, 7 May 2026 16:08:09 -0500 Subject: [PATCH] docs(api): add Coverages, Maintenance Windows, Schedules, Escalation Policies The /docs/api and /docs/graphql pages were missing four resource families that the API has shipped: - Coverages (RF coverage prediction with async compute, recompute, KMZ download) - Maintenance Windows (one-off + recurring with RRULE) - On-Call Schedules (top-level CRUD + layers, members, overrides, on_call lookup) - Escalation Policies (CRUD + rules + targets) Both REST and GraphQL pages now have sidebar entries and example requests for each. --- .../controllers/api_docs_html/index.html.heex | 536 ++++++++++++++++++ .../graphql_docs_html/index.html.heex | 455 +++++++++++++++ 2 files changed, 991 insertions(+) diff --git a/lib/towerops_web/controllers/api_docs_html/index.html.heex b/lib/towerops_web/controllers/api_docs_html/index.html.heex index 324bd60e..b33189af 100644 --- a/lib/towerops_web/controllers/api_docs_html/index.html.heex +++ b/lib/towerops_web/controllers/api_docs_html/index.html.heex @@ -166,6 +166,42 @@ Graphs & Live Polling +
  • + + RF Coverages + +
  • +
  • + + Maintenance Windows + +
  • +
  • + + On-Call Schedules + +
  • +
  • + + Escalation Policies + +
  • @@ -3864,6 +3900,506 @@ curl -G https://towerops.net/api/v1/activity \\
    + +
    +

    + RF Coverages +

    +

    + RF coverage predictions model where each radio actually reaches. Compute is async — a created or recomputed coverage starts in + queued + and progresses through computing + to ready + or failed. The status field is present on every response so polling-based clients (Terraform, Ansible, scripts) can wait for a healthy resource. Outputs include a coloured PNG heatmap, a Float32 GeoTIFF raster, and a downloadable KMZ bundle for Google Earth. +

    + +
    +

    List all coverages

    +
    +
    +

    + GET /api/v1/coverages. Returns every coverage in the authenticated organization, regardless of status. +

    +
    +
    +
    +
    <%= raw("""
    +curl -G https://towerops.net/api/v1/coverages \\
    +  -H "Authorization: Bearer #{@sample_token}"
    +""") %>
    +
    +
    +
    +
    + +
    +

    Create a coverage

    +
    +
    +

    + POST /api/v1/coverages. Creates a new coverage and immediately enqueues compute. Required: name, site_id, antenna_slug, frequency_mhz, tx_power_dbm, height_agl_m, azimuth_deg, radius_m. Optional fields tune cell size, downtilt, foliage, receiver height, and threshold. +

    +
    +
    +
    +
    <%= raw(~S"""
    +curl -X POST https://towerops.net/api/v1/coverages \
    +  -H "Authorization: Bearer YOUR_API_TOKEN" \
    +  -H "Content-Type: application/json" \
    +  -d '{
    +    "coverage": {
    +      "name": "North sector 5 GHz",
    +      "site_id": "550e8400-e29b-41d4-a716-446655440000",
    +      "antenna_slug": "rf-elements-twistport-symmetrical-horn-30",
    +      "frequency_mhz": 5800,
    +      "tx_power_dbm": 22.0,
    +      "height_agl_m": 30,
    +      "azimuth_deg": 0,
    +      "downtilt_deg": 2,
    +      "radius_m": 6000,
    +      "cell_size_m": 10
    +    }
    +  }'
    +""") %>
    +
    +
    +
    +
    + +
    +

    Retrieve a coverage

    +
    +
    +

    + GET /api/v1/coverages/:id. Returns one coverage including its bbox, status, progress percentage, and (when ready) the + png_path + + raster_path + outputs. +

    +
    +
    +
    +
    <%= raw("""
    +curl https://towerops.net/api/v1/coverages/COV_ID \\
    +  -H "Authorization: Bearer #{@sample_token}"
    +""") %>
    +
    +
    +
    +
    + +
    +

    Update a coverage

    +
    +
    +

    + PATCH /api/v1/coverages/:id. Mutates editable fields (name, antenna parameters, radius, cell size, etc.). Does + not + auto-recompute — call the recompute endpoint when you want fresh outputs. +

    +
    +
    +
    +
    <%= raw(~S"""
    +curl -X PATCH https://towerops.net/api/v1/coverages/COV_ID \
    +  -H "Authorization: Bearer YOUR_API_TOKEN" \
    +  -H "Content-Type: application/json" \
    +  -d '{"coverage": {"tx_power_dbm": 25.0}}'
    +""") %>
    +
    +
    +
    +
    + +
    +

    + Recompute a coverage +

    +
    +
    +

    + POST /api/v1/coverages/:id/recompute. Re-queues the compute pipeline without changing any parameters. Returns + 409 Conflict + if the coverage is already computing. Useful after a parameter update or after the antenna catalog is refreshed. +

    +
    +
    +
    +
    <%= raw("""
    +curl -X POST https://towerops.net/api/v1/coverages/COV_ID/recompute \\
    +  -H "Authorization: Bearer #{@sample_token}"
    +""") %>
    +
    +
    +
    +
    + +
    +

    Download as KMZ

    +
    +
    +

    + GET /api/v1/coverages/:id/kmz. Returns a Google Earth-compatible KMZ bundle (KML + PNG). Returns + 409 Conflict + if the coverage is not yet ready. +

    +
    +
    +
    +
    <%= raw("""
    +curl -OJ https://towerops.net/api/v1/coverages/COV_ID/kmz \\
    +  -H "Authorization: Bearer #{@sample_token}"
    +""") %>
    +
    +
    +
    +
    + +
    +

    Delete a coverage

    +
    +
    +

    + DELETE /api/v1/coverages/:id. Removes the coverage and any cached outputs. Returns + 204 No Content + on success. +

    +
    +
    +
    +
    <%= raw("""
    +curl -X DELETE https://towerops.net/api/v1/coverages/COV_ID \\
    +  -H "Authorization: Bearer #{@sample_token}"
    +""") %>
    +
    +
    +
    +
    +
    + +
    + + +
    +

    + Maintenance Windows +

    +

    + Maintenance windows tell Towerops to suppress alerts for a site or device during planned work. Windows can be one-off or recurring (iCal RRULE). +

    + +
    +

    + List maintenance windows +

    +
    +
    +

    + GET /api/v1/maintenance_windows. Optional filter + query param accepts active, upcoming, or past. +

    +
    +
    +
    +
    <%= raw("""
    +curl -G https://towerops.net/api/v1/maintenance_windows \\
    +  -H "Authorization: Bearer #{@sample_token}" \\
    +  -d filter=active
    +""") %>
    +
    +
    +
    +
    + +
    +

    + Create a maintenance window +

    +
    +
    +

    + POST /api/v1/maintenance_windows. Required: name, starts_at, ends_at. Scope to a site or a specific device. Set + recurring: true + with a recurrence_rule + (RRULE) to repeat. +

    +
    +
    +
    +
    <%= raw(~S"""
    +curl -X POST https://towerops.net/api/v1/maintenance_windows \
    +  -H "Authorization: Bearer YOUR_API_TOKEN" \
    +  -H "Content-Type: application/json" \
    +  -d '{
    +    "maintenance_window": {
    +      "name": "Quarterly firmware roll",
    +      "starts_at": "2026-06-01T05:00:00Z",
    +      "ends_at":   "2026-06-01T09:00:00Z",
    +      "site_id":   "SITE_UUID",
    +      "suppress_alerts": true
    +    }
    +  }'
    +""") %>
    +
    +
    +
    +
    + +
    +

    + Get / update / delete +

    +
    +
    +

    + Standard CRUD on /api/v1/maintenance_windows/:id. GET + returns the resource; PATCH + updates fields with the same body shape as create; DELETE + returns a JSON success body. +

    +
    +
    +
    +
    <%= raw("""
    +curl -X DELETE https://towerops.net/api/v1/maintenance_windows/WINDOW_ID \\
    +  -H "Authorization: Bearer #{@sample_token}"
    +""") %>
    +
    +
    +
    +
    +
    + +
    + + +
    +

    + On-Call Schedules +

    +

    + Schedules drive who gets paged when an alert fires. A schedule contains one or more + layers + (rotations); each layer has ordered members. Ad-hoc overrides + let you swap the on-call user for a fixed time window without changing the rotation. +

    + +
    +

    Schedule CRUD

    +
    +
    +

    + GET /api/v1/schedules, POST, GET /:id, PATCH /:id, DELETE /:id. Required on create: name, timezone. Showing one schedule returns its layers + overrides nested. +

    +
    +
    +
    +
    <%= raw(~S"""
    +curl -X POST https://towerops.net/api/v1/schedules \
    +  -H "Authorization: Bearer YOUR_API_TOKEN" \
    +  -H "Content-Type: application/json" \
    +  -d '{
    +    "schedule": {
    +      "name": "Primary on-call",
    +      "description": "Weekly rotation",
    +      "timezone": "America/Chicago"
    +    }
    +  }'
    +""") %>
    +
    +
    +
    +
    + +
    +

    Who is on call?

    +
    +
    +

    + GET /api/v1/schedules/:id/on_call. Returns the user currently on call (taking layers and overrides into account), or + null + if nobody is. +

    +
    +
    +
    +
    <%= raw("""
    +curl https://towerops.net/api/v1/schedules/SCHED_ID/on_call \\
    +  -H "Authorization: Bearer #{@sample_token}"
    +""") %>
    +
    +
    +
    +
    + +
    +

    Layers and members

    +
    +
    +

    + POST /api/v1/schedules/:id/layers, PATCH /api/v1/schedules/:id/layers/:layer_id, + DELETE + the same. A layer requires name, position, + rotation_type + (weekly/daily/custom), rotation_interval, handoff_time, and start_date. +

    Add and remove members with + POST /:id/layers/:layer_id/members + (body: user_id, position) and DELETE /:id/layers/:layer_id/members/:member_id. +

    +
    +
    +
    +
    <%= raw(~S"""
    +curl -X POST https://towerops.net/api/v1/schedules/SCHED_ID/layers \
    +  -H "Authorization: Bearer YOUR_API_TOKEN" \
    +  -H "Content-Type: application/json" \
    +  -d '{
    +    "layer": {
    +      "name": "Primary",
    +      "position": 0,
    +      "rotation_type": "weekly",
    +      "rotation_interval": 1,
    +      "handoff_time": "09:00:00",
    +      "start_date": "2026-06-01T00:00:00Z"
    +    }
    +  }'
    +""") %>
    +
    +
    +
    +
    + +
    +

    Overrides

    +
    +
    +

    + POST /api/v1/schedules/:id/overrides + creates a one-off cover ("Bob is on for Alice from Friday 17:00 to Monday 09:00"). Required: user_id, start_time, end_time. Remove with DELETE /:id/overrides/:override_id. +

    +
    +
    +
    +
    <%= raw(~S"""
    +curl -X POST https://towerops.net/api/v1/schedules/SCHED_ID/overrides \
    +  -H "Authorization: Bearer YOUR_API_TOKEN" \
    +  -H "Content-Type: application/json" \
    +  -d '{
    +    "override": {
    +      "user_id": "USER_UUID",
    +      "start_time": "2026-06-07T17:00:00Z",
    +      "end_time":   "2026-06-09T09:00:00Z"
    +    }
    +  }'
    +""") %>
    +
    +
    +
    +
    +
    + +
    + + +
    +

    + Escalation Policies +

    +

    + Escalation policies are an ordered list of rules; each rule fires its + targets + after a delay if the previous rule's targets haven't acknowledged the alert. Targets can reference users, schedules, or arbitrary webhooks. +

    + +
    +

    Policy CRUD

    +
    +
    +

    + Standard REST CRUD under /api/v1/escalation_policies. + GET /:id + returns the policy with its rules and targets nested. Required on create: name. Optional: description, repeat_count, repeat_interval_minutes. +

    +
    +
    +
    +
    <%= raw(~S"""
    +curl -X POST https://towerops.net/api/v1/escalation_policies \
    +  -H "Authorization: Bearer YOUR_API_TOKEN" \
    +  -H "Content-Type: application/json" \
    +  -d '{
    +    "escalation_policy": {
    +      "name": "Critical alerts",
    +      "description": "Page primary, then secondary, then manager",
    +      "repeat_count": 3,
    +      "repeat_interval_minutes": 15
    +    }
    +  }'
    +""") %>
    +
    +
    +
    +
    + +
    +

    Rules

    +
    +
    +

    + POST /api/v1/escalation_policies/:id/rules + appends a rule. PATCH /api/v1/escalation_policies/:id/rules/:rule_id + updates one. DELETE + removes one. Required on create: position, delay_minutes. +

    +
    +
    +
    +
    <%= raw(~S"""
    +curl -X POST https://towerops.net/api/v1/escalation_policies/POLICY_ID/rules \
    +  -H "Authorization: Bearer YOUR_API_TOKEN" \
    +  -H "Content-Type: application/json" \
    +  -d '{
    +    "rule": {"position": 0, "delay_minutes": 5}
    +  }'
    +""") %>
    +
    +
    +
    +
    + +
    +

    Targets

    +
    +
    +

    + Each rule has one or more targets: + POST /api/v1/escalation_policies/:id/rules/:rule_id/targets + creates one, + + DELETE /api/v1/escalation_policies/:id/rules/:rule_id/targets/:target_id + + removes it. A target body specifies target_type + (user, schedule, or webhook) plus the corresponding identifier or URL. +

    +
    +
    +
    +
    <%= raw(~S"""
    +curl -X POST https://towerops.net/api/v1/escalation_policies/POLICY_ID/rules/RULE_ID/targets \
    +  -H "Authorization: Bearer YOUR_API_TOKEN" \
    +  -H "Content-Type: application/json" \
    +  -d '{
    +    "target": {"target_type": "schedule", "schedule_id": "SCHED_UUID"}
    +  }'
    +""") %>
    +
    +
    +
    +
    +
    + +
    +

    diff --git a/lib/towerops_web/controllers/graphql_docs_html/index.html.heex b/lib/towerops_web/controllers/graphql_docs_html/index.html.heex index f70ecb15..77adb310 100644 --- a/lib/towerops_web/controllers/graphql_docs_html/index.html.heex +++ b/lib/towerops_web/controllers/graphql_docs_html/index.html.heex @@ -126,6 +126,38 @@ Time-Series +
  • + + Service Checks + +
  • +
  • + + On-Call Schedules + +
  • +
  • + + Escalation Policies + +
  • +
  • + + Maintenance Windows + +
  • @@ -189,6 +221,38 @@ Integrations +
  • + + Service Checks + +
  • +
  • + + Schedules + +
  • +
  • + + Escalation Policies + +
  • +
  • + + Maintenance Windows + +
  • @@ -841,6 +905,179 @@

    + +
    +

    + Query: Service Checks +

    +

    + List and inspect HTTP, TCP, DNS, and ping service checks. +

    + +

    checks

    +
    +
    <%= raw(~S[{
    +  checks(checkType: "http", deviceId: "device-uuid") {
    +    id
    +    name
    +    checkType
    +    enabled
    +    intervalSeconds
    +    currentState
    +    config
    +  }
    +}]) %>
    +
    + +

    check (single)

    +
    +
    <%= raw(~S[{
    +  check(id: "check-uuid") {
    +    id
    +    name
    +    checkType
    +    config
    +    currentState
    +    currentStateType
    +    lastCheckAt
    +  }
    +}]) %>
    +
    +
    + + +
    +

    + Query: On-Call Schedules +

    +

    + Inspect schedules and find out who is currently on call. +

    + +

    schedules

    +
    +
    <%= raw(~S[{
    +  schedules {
    +    id
    +    name
    +    timezone
    +    description
    +  }
    +}]) %>
    +
    + +

    + schedule (with layers + overrides) +

    +
    +
    <%= raw(~S[{
    +  schedule(id: "schedule-uuid") {
    +    id
    +    name
    +    timezone
    +    layers {
    +      id
    +      name
    +      position
    +      rotationType
    +      members {
    +        userId
    +        position
    +      }
    +    }
    +    overrides {
    +      id
    +      userId
    +      startTime
    +      endTime
    +    }
    +  }
    +}]) %>
    +
    + +

    onCall

    +
    +
    <%= raw(~S[{
    +  onCall(scheduleId: "schedule-uuid") {
    +    userId
    +    email
    +  }
    +}]) %>
    +
    +
    + + +
    +

    + Query: Escalation Policies +

    +

    + Inspect escalation policies and their rules + targets. +

    + +

    escalationPolicies

    +
    +
    <%= raw(~S[{
    +  escalationPolicies {
    +    id
    +    name
    +    description
    +    repeatCount
    +    repeatIntervalMinutes
    +  }
    +}]) %>
    +
    + +

    + escalationPolicy (with rules + targets) +

    +
    +
    <%= raw(~S[{
    +  escalationPolicy(id: "policy-uuid") {
    +    id
    +    name
    +    rules {
    +      id
    +      position
    +      delayMinutes
    +      targets {
    +        id
    +        targetType
    +        userId
    +        scheduleId
    +      }
    +    }
    +  }
    +}]) %>
    +
    +
    + + +
    +

    + Query: Maintenance Windows +

    +

    + Inspect planned and active maintenance windows. The optional filter + argument accepts active, upcoming, or past. +

    + +

    maintenanceWindows

    +
    +
    <%= raw(~S[{
    +  maintenanceWindows(filter: "upcoming") {
    +    id
    +    name
    +    startsAt
    +    endsAt
    +    suppressAlerts
    +    siteId
    +    deviceId
    +  }
    +}]) %>
    +
    +
    +

    @@ -1070,6 +1307,224 @@ mutation { cancelInvitation(id: "invitation-uuid") { success message } }]) %>

    + +
    +

    + Mutation: Service Checks +

    + +

    createCheck

    +
    +
    <%= raw(~S[mutation {
    +  createCheck(input: {
    +    name: "API health"
    +    checkType: "http"
    +    deviceId: "device-uuid"
    +    intervalSeconds: 60
    +    config: {url: "https://api.example.com/health", method: "GET"}
    +  }) {
    +    id
    +    name
    +    checkType
    +  }
    +}]) %>
    +
    + +

    + updateCheck / deleteCheck +

    +
    +
    <%= raw(~S[mutation {
    +  updateCheck(id: "check-uuid", input: {intervalSeconds: 30}) {
    +    id
    +    intervalSeconds
    +  }
    +}
    +
    +mutation {
    +  deleteCheck(id: "check-uuid") {
    +    success
    +  }
    +}]) %>
    +
    +
    + + +
    +

    + Mutation: Schedules +

    + +

    Schedule CRUD

    +
    +
    <%= raw(~S[mutation {
    +  createSchedule(input: {
    +    name: "Primary on-call"
    +    timezone: "America/Chicago"
    +  }) {
    +    id
    +    name
    +  }
    +}
    +
    +mutation {
    +  updateSchedule(id: "schedule-uuid", input: {description: "Weekly rotation"}) {
    +    id
    +    description
    +  }
    +}
    +
    +mutation {
    +  deleteSchedule(id: "schedule-uuid") {
    +    success
    +  }
    +}]) %>
    +
    + +

    Layers + members

    +
    +
    <%= raw(~S[mutation {
    +  createLayer(scheduleId: "schedule-uuid", input: {
    +    name: "Primary"
    +    position: 0
    +    rotationType: "weekly"
    +    rotationInterval: 1
    +    handoffTime: "09:00:00"
    +    startDate: "2026-06-01T00:00:00Z"
    +  }) {
    +    id
    +  }
    +}
    +
    +mutation {
    +  addLayerMember(layerId: "layer-uuid", userId: "user-uuid", position: 0) {
    +    id
    +  }
    +}
    +
    +mutation {
    +  removeLayerMember(id: "member-uuid") {
    +    success
    +  }
    +}]) %>
    +
    + +

    Overrides

    +
    +
    <%= raw(~S[mutation {
    +  createOverride(scheduleId: "schedule-uuid", input: {
    +    userId: "user-uuid"
    +    startTime: "2026-06-07T17:00:00Z"
    +    endTime:   "2026-06-09T09:00:00Z"
    +  }) {
    +    id
    +  }
    +}
    +
    +mutation {
    +  deleteOverride(id: "override-uuid") {
    +    success
    +  }
    +}]) %>
    +
    +
    + + +
    +

    + Mutation: Escalation Policies +

    + +

    Policy CRUD

    +
    +
    <%= raw(~S[mutation {
    +  createEscalationPolicy(input: {
    +    name: "Critical alerts"
    +    repeatCount: 3
    +    repeatIntervalMinutes: 15
    +  }) {
    +    id
    +    name
    +  }
    +}
    +
    +mutation {
    +  updateEscalationPolicy(id: "policy-uuid", input: {name: "Critical alerts (revised)"}) {
    +    id
    +  }
    +}
    +
    +mutation {
    +  deleteEscalationPolicy(id: "policy-uuid") {
    +    success
    +  }
    +}]) %>
    +
    + +

    Rules + targets

    +
    +
    <%= raw(~S[mutation {
    +  createEscalationRule(escalationPolicyId: "policy-uuid", input: {
    +    position: 0
    +    delayMinutes: 5
    +  }) {
    +    id
    +  }
    +}
    +
    +mutation {
    +  createEscalationTarget(escalationRuleId: "rule-uuid", input: {
    +    targetType: "schedule"
    +    scheduleId: "schedule-uuid"
    +  }) {
    +    id
    +  }
    +}
    +
    +mutation {
    +  deleteEscalationTarget(id: "target-uuid") {
    +    success
    +  }
    +}]) %>
    +
    +
    + + +
    +

    + Mutation: Maintenance Windows +

    + +

    CRUD

    +
    +
    <%= raw(~S[mutation {
    +  createMaintenanceWindow(input: {
    +    name: "Quarterly firmware roll"
    +    startsAt: "2026-06-01T05:00:00Z"
    +    endsAt:   "2026-06-01T09:00:00Z"
    +    siteId: "site-uuid"
    +    suppressAlerts: true
    +  }) {
    +    id
    +    name
    +  }
    +}
    +
    +mutation {
    +  updateMaintenanceWindow(id: "window-uuid", input: {endsAt: "2026-06-01T10:00:00Z"}) {
    +    id
    +    endsAt
    +  }
    +}
    +
    +mutation {
    +  deleteMaintenanceWindow(id: "window-uuid") {
    +    success
    +  }
    +}]) %>
    +
    +
    +