Recipe · automation

The fleet lives in git.

Monitors that only exist in a dashboard drift: someone edits an interval, nobody knows why. Keep the fleet in a JSON file next to your infrastructure code and sync it from CI — reviewable, diffable, revertable.

The fleet file

// monitors.json
[
  { "name": "api",     "url": "https://api.example.com/health", "monitor_type": "http", "interval_seconds": 10, "regions": ["default"] },
  { "name": "website", "url": "https://example.com",            "monitor_type": "http", "interval_seconds": 60, "regions": ["default"] },
  { "name": "shop",    "url": "https://shop.example.com",       "monitor_type": "http", "interval_seconds": 30, "regions": ["default"],
    "alert_type": "keyword", "keyword": "Add to cart" }
]

The sync script

Create-if-missing, keyed by name. Existence is checked via the search parameter (q=); creation carries an idempotency key derived from the name, so parallel CI runs cannot double-create:

#!/usr/bin/env bash
set -euo pipefail
: "${UP4_API_KEY:?set UP4_API_KEY first}"
API=https://app.upfour.io/v1

jq -c '.[]' monitors.json | while read -r monitor; do
  name=$(echo "$monitor" | jq -r .name)
  found=$(curl -fsS "$API/monitors?q=$name&limit=250" \
    -H "Authorization: Bearer $UP4_API_KEY" \
    | jq --arg n "$name" '[.data[] | select(.name == $n)] | length')
  if [ "$found" -eq 0 ]; then
    curl -fsS -X POST "$API/monitors" \
      -H "Authorization: Bearer $UP4_API_KEY" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: fleet-$(echo -n "$name" | shasum | cut -c1-32)" \
      -d "$monitor" > /dev/null
    echo "created: $name"
  else
    echo "exists:  $name"
  fi
done

Updating and pruning

This recipe deliberately stops at create-if-missing — it can run against a fleet people also touch by hand without ever overwriting their work. If the file should be the single source of truth, extend the else-branch with a PUT /monitors/{id} of the desired state, and treat monitors that exist remotely but not in the file as candidates for deletion — behind a --prune flag you have to mean.

Notes

Run it against the sandbox first — same script, pls_test_ key. Give CI a key scoped to monitor.read + monitor.write. Agencies: add client_id to each entry and keep one file per client.