Skip to main content
You want a notification the moment an opportunity’s APY drops below a threshold — or a curator, vault, or oracle event fires — without polling for it. Alert rules watch a data source server-side and dispatch to your channels (or just your in-app inbox) when every condition matches.
Alert rules live under /v1, not /v2. Responses are the resource itself (AlertRule, AlertRuleList, …), not wrapped in the {data, meta} envelope that /v2 endpoints use.

What you build

A rule that fires on APY drop, plus the list/update/pause/delete/audit operations you need once it’s live.

1. Create a rule

Creating and modifying rules requires the write:alerts scope (which also grants read:alerts — see Scopes).
curl -H "Authorization: Bearer $ALTERSCOPE_API_KEY" \
     -H "Content-Type: application/json" \
     -X POST -d '{
       "name": "APY drop on USDC vault",
       "data_source": "yield_radar",
       "asset_symbol": "USDC",
       "conditions": [
         { "field": "apy", "operator": "lt", "value": 5.0 }
       ],
       "message_template": "{{.asset_symbol}} APY fell to {{.apy}}%",
       "cooldown_minutes": 60
     }' \
  "https://api.alterscope.org/v1/alerts/rules"
data_source is one of yield_radar, hyperliquid, watchlist, vault_events, curator_events, oracle_events, and it cannot be changed after creation. conditions is evaluated as AND — every condition must match for the rule to fire. channel_ids is optional and omitted here: an empty or absent list means in-app notification only. To fan out to email or webhook channels, create those channels first in the dashboard under Settings → Notifications, then pass their IDs — channel management itself is not part of this API. A 404 on create means one or more channel_ids don’t belong to you; a 409 means a rule with that name already exists; a 400 means the conditions or noise-control fields (cooldown_minutes, require_persistence_minutes, dedupe_window_minutes, quiet_hours_*) failed validation.

2. List and inspect rules

curl -H "Authorization: Bearer $ALTERSCOPE_API_KEY" \
  "https://api.alterscope.org/v1/alerts/rules?enabled=true"
enabled is an optional query filter on the list endpoint; omit it to get every rule regardless of state.

3. Update a rule — partial-merge PUT

PUT /v1/alerts/rules/{id} is a partial merge, not a full replace: only the fields present in the body change, and everything you omit keeps its current value. data_source is never accepted here. The trap: “present” means present in the JSON, not “non-empty”. If you send "channel_ids": [], that’s an explicit instruction to clear the channels — different from leaving channel_ids out entirely, which leaves them untouched. The same applies to conditions: there’s no per-condition merge, so sending conditions replaces the whole array. To add one condition to an existing rule, resend all of its existing conditions plus the new one — send just the new one and you silently drop the rest.
curl
# Only cooldown_minutes and message_template change; everything else
# on the rule — including its existing conditions — is untouched.
curl -H "Authorization: Bearer $ALTERSCOPE_API_KEY" \
     -H "Content-Type: application/json" \
     -X PUT -d '{
       "cooldown_minutes": 120,
       "message_template": "{{.asset_symbol}} APY dropped to {{.apy}}% (re-check before acting)"
     }' \
  "https://api.alterscope.org/v1/alerts/rules/$RULE_ID"

4. Pause or resume without touching the definition

Toggling doesn’t count as an update to the rule’s conditions, channels, or template — use it for a temporary pause instead of re-sending the full rule.
curl
curl -H "Authorization: Bearer $ALTERSCOPE_API_KEY" \
     -H "Content-Type: application/json" \
     -X POST -d '{"enabled": false}' \
  "https://api.alterscope.org/v1/alerts/rules/$RULE_ID/toggle"
python
client.post(f"/v1/alerts/rules/{rule['id']}/toggle", json={"enabled": False})

5. Delete a rule

curl
curl -H "Authorization: Bearer $ALTERSCOPE_API_KEY" \
     -X DELETE \
  "https://api.alterscope.org/v1/alerts/rules/$RULE_ID"
A successful delete returns 204 with no body. Deleting is permanent — there’s no undo endpoint; recreate the rule if you delete it by mistake.

6. Read the fire history

GET /v1/alerts/events returns your rule firings, most recent first — useful for confirming a rule actually fired, or for building your own audit log.
curl -H "Authorization: Bearer $ALTERSCOPE_API_KEY" \
  "https://api.alterscope.org/v1/alerts/events?rule_id=$RULE_ID&limit=20"
Each event carries data_snapshot (the observation that triggered the rule), status (sent, suppressed, or failed), and a dispatches array with one entry per channel — each with its own status, sent_at, and error_message if delivery failed. Filter by status, or by start_date/end_date (RFC3339) to bound a window; limit maxes out at 1000.

7. Troubleshooting

  • 403 insufficient_scope on read-only calls: list/get on rules and events only need read:alerts; a write:alerts key also has it, but a read:alerts-only key can’t create, update, toggle, or delete.
  • A PUT didn’t change what you expected: re-read step 3 — you likely sent a field that overwrote something you meant to leave alone (or omitted one you meant to change).
  • Rule doesn’t fire: check enabled (toggle may have flipped it), confirm cooldown_minutes or quiet_hours_* isn’t currently suppressing it, and pull /v1/alerts/events?rule_id=...&status=suppressed to see suppressed-not-silent fires.
  • Rule fires but you get no notification: check the event’s dispatches[].status — a channel-side failure (bad webhook URL, bounced email) shows there with error_message, even though the rule itself fired.