Runs onWrit CloudDesktop
On this page
Monitors.
A monitor watches a page and reports real change against a baseline. On the top cloud plans it checks as often as every 10 seconds, and a detected change can fire a workflow the moment it lands.
Writ runs on your own accounts, with your own credentials and data, on sites you are authorized to use.
types ▸ two checks
Two check types.
A target is a URL plus a check_type:
| check_type | What it checks |
|---|---|
content | Fetches the page and compares what you watch — a selector, the structure, or a screenshot zone — against its stored baseline. |
uptime | Checks that the page answers: HTTP status, response time, certificate validity. |
JS rendering is a separate switch: set requires_playwright and the check runs in a real browser instead of a plain fetch. A JS check weighs 5× an HTML check in your plan’s check budget.
watch ▸ three modes
What a content check can watch.
A content target watches in one of three modes, each with its own baseline:
| Mode | How change is detected |
|---|---|
selector | The text at a CSS selector, hashed and compared. With no selector set, the whole page is watched. |
html | The structured markup, compared structurally rather than as raw text. |
visual | A screenshot zone — visual_region is {x, y, width, height} — compared against a stored baseline image. |
Noise control: an ignore_regex can be set on the target and on each selector — matching fragments are struck from the comparison, so counters and timestamps stop producing false changes.
Extractors
On selector content, extractors turn the matched region into named values that travel with the change event. Each extractor has a key, an optional multiple flag, and a default_value:
| type | What it pulls |
|---|---|
text | The element’s text content. |
attribute | A named attribute of the element. |
regex | The first (or every) match of a pattern. |
css | A nested CSS selection inside the watched region. |
json_path | A path into JSON found in the watched content. |
Create one from code
On your own machine, the SDKs create a monitor against the local agent and read its change history back:
monitor.ts
import { WritAgent } from "@usewrit/agent-sdk";
const client = new WritAgent();
const mon = await client.monitors.create({ url: "https://example.com/pricing" });
const history = await client.monitors.changes(mon.id, { limit: 50 });
console.log(mon.id, history); monitor.py
from writ_agent import WritAgent
with WritAgent() as client:
mon = client.monitors.create({"url": "https://example.com/pricing"})
history = client.monitors.changes(mon["id"], limit=50)
print(mon["id"], history) monitor.go
client, err := writ.Discover(ctx)
if err != nil { log.Fatal(err) }
mon, _ := client.Monitors.Create(ctx, map[string]any{"url": "https://example.com/pricing"})
history, _ := client.Monitors.Changes(ctx, mon.ID, nil)
fmt.Println(mon.ID, history) monitor.rs
use writ_client::WritAgent;
use serde_json::json;
let agent = WritAgent::discover().await?;
let mon = agent.monitors().create(json!({ "url": "https://example.com/pricing" })).await?;
let history = agent.monitors().changes_with(mon.id, &[("limit", "50")]).await?; monitor.sh
# Local agent daemon — loopback, wlt_/wlk_ token (use 127.0.0.1, not localhost)
curl -X POST http://127.0.0.1:8131/v1/monitors \
-H "Authorization: Bearer $WRIT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/pricing"}' context ▸ around the check
Logged-in checks, and acting in the same session.
A target can carry the context its page needs — and hand the live session straight to a workflow when change is detected:
| Field | What it does |
|---|---|
pre_check_workflow_id | A workflow that runs before the check — typically a sign-in — so the check sees the page your account sees. |
on_change workflow | A workflow fired when change is detected. It can run in the same live session, so it acts on the exact page state the check just saw. |
persona_id | The persona whose saved sign-in state the check uses. |
use_residential | Route the check through residential egress where your plan allows it. |
cadence ▸ floors per plan
Cadence floors — rejected, not clamped.
Every plan sets a fastest allowed interval, separately for HTML checks and JS-rendered checks. An interval below your plan’s floor is rejected with 402 and code interval_too_short — it is never silently slowed down to the floor.
| Plan | HTML floor | JS floor |
|---|---|---|
| Free | 5 min | 15 min |
| Starter | 1 min | 10 min |
| Pro | 1 min | 10 min |
| Growth | 30 s | 5 min |
| Scale / Enterprise | 10 s | 2 min |
What you set is what runs. If a request would need a faster plan, the API says so up front instead of quietly degrading your monitor.
Two separate gates sit beside the floor: a weighted checks-per-minute budget across all your targets (10 on Free, up to 3000 on Enterprise; a JS check counts 5×) answered with 402 budget_exceeded when spent — and a hard maximum number of targets per check type.
pipeline ▸ change to action
From change to action.
When a check lands, every fired action has walked the same pipeline:
- Extract — Extractors turn the watched content into named values.
- Match — Triggers watching this target (or this selector) are collected.
- Build context — The
{{…}}template context is assembled: extracted values,now/now_dateand friends,change_detected_at,target_idand the target URL. - Dedup — Selector-less triggers fire once per (trigger, target) per batch — one check cannot double-fire the same rule.
- Conditions — Each trigger’s conditions are evaluated, plus its guardrails: schedule windows and cooldown.
- Log, dispatch, settle — The firing is logged as pending, actions dispatch, and the log settles with status, action_results and trigger_count.
Condition operators (11): changed, exists, equals, not_equals, contains, not_contains, matches, gt, gte, lt, lte.
templates ▸ filters
Template filters.
Inside {{…}} templates — messages, webhook payloads, workflow inputs — values can be piped through filters:
| Filter | What it does |
|---|---|
default (alias: or) | Fallback when the value is empty. |
upper / lower | Case conversion. |
trim | Strip surrounding whitespace. |
truncate:N | Cut to N characters. |
replace:a:b | Replace a with b. |
round[:digits] | Round a number, optionally to a digit count. |
add / sub / mul / div | Arithmetic on numeric values. |
match:<regex> | Keep the regex match (patterns up to 512 characters). |
alerts ▸ nine channels
Where alerts go.
Monitor and trigger alerts deliver through nine channels: pushover, email, twilio (SMS), whatsapp, signal, webhook, slack, discord and telegram.
Recipients are addressed as "channel:id" strings — e.g. ["pushover:1", "email:3"] — so one trigger can fan out to several configured destinations at once. Outbound webhook deliveries are signed; see webhooks.
next ▸ where to go
Keep going.
- Automations & webhooks — the full trigger/action model and signed deliveries.
- Workflows — what an on_change workflow can do once it fires.
- Watch-and-act — the product story around this reference.