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
doneUpdating 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.