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

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

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
python

5. Delete a rule

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