/api/v1/monitorsMonitors
A monitor is a standing question about one page. Contrie answers it now, answers it again on a schedule, and when the answer changes it records the before and after answers with a diff of the fields that moved — and tells your webhook. Each answer carries its metadata.evidenceStatus; it is signed when that says signed. Every accepted run costs what one extraction costs, changed or not; an unchanged page writes no event.
Parameters
| Field | Type | Description |
|---|---|---|
| url | string | The public page to watch. Required. |
| extract | string | What to watch, in plain language. Required unless schema is given. |
| schema | object | JSON Schema of the answer. |
| every | integer | Minutes between runs, 15 to 10,080. Default 60. |
| webhookUrl | string | https URL to POST changes to. The signing secret is returned once, in the create response. |
Create
curl -X POST https://www.contrie.com/api/v1/monitors \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $CONTRIE_API_KEY" \
-d '{
"url": "https://example.com/widget",
"extract": "price, availability",
"every": 60,
"webhookUrl": "https://hooks.example.com/contrie"
}'The response is 201 with monitor, baseline and, when you gave a webhook, webhookSecret (shown once). If the baseline is refused, the monitor is still created and active, and baseline is { "success": false, "data": null, "error": { "code", "message" } } — the full response when a rejected answer exists. A refusal costs 0 credits. Read baseline.error.code: PAGE_LIMIT_REACHED resumes when credits return; SOURCE_REFUSED usually keeps refusing, so stop that monitor — unless its message says the source is rate-limiting, which can recover on a later run. Past your plan’s cap, creation returns 429 PAGE_LIMIT_REACHED and nothing is created.
A change event
{
"id": "…",
"kind": "change",
"at": "2026-09-08T14:15:03.201Z",
"diff": [ { "path": "$.price", "before": 24.99, "after": 19.99 } ],
"before": { "success": true, "data": { "price": 24.99, "availability": "in stock" }, "metadata": { "evidenceStatus": "signed", "evidence": { "…": "…" } } },
"after": { "success": true, "data": { "price": 19.99, "availability": "in stock" }, "metadata": { "evidenceStatus": "signed", "evidence": { "…": "…" } } }
}Events are baseline, change and error; GET /api/v1/monitors/:id returns the latest 20. An error event’s after is the failure with its public error.code and message. A run that fails exactly like the latest event updates that event instead of adding one: its at becomes the newest failure, so a page that keeps refusing cannot push your changes out of those 20, and a failure after a recovery still shows as the newest event. The diff itself is not signed.
Webhook
One POST per change, with this body:
{
"event": "monitor.change",
"monitorId": "…",
"url": "https://example.com/widget",
"changedAt": "2026-09-08T14:15:03.412Z",
"diff": [ { "path": "$.price", "before": 24.99, "after": 19.99 } ],
"before": { "success": true, "data": { "…": "…" }, "metadata": { "…": "…" } },
"after": { "success": true, "data": { "…": "…" }, "metadata": { "…": "…" } }
}Only changes are delivered; errors are not. It is signed the way Stripe signs: X-Contrie-Signature: t=<unix>,v1=<hex> where v1 is HMAC-SHA256 of t + "." + body with your monitor’s secret. Reject stale timestamps. Delivery is one attempt; the event is stored either way and readable from the monitor.
Credits per day
Credits per day = runs per day × credits per accepted run, and runs per day = 1,440 ÷ every. An accepted run costs what one extraction costs: at least 1 credit, at most the plan’s per-request cap (Free 3, Builder 5, Pro 17), rendering included. Refused and rejected runs cost 0 credits. A monitor keeps spending until you stop it.
| every | Runs per day | Credits per day at 1 a run |
|---|---|---|
| 15 | 96 | 96 |
| 60 (default) | 24 | 24 |
| 1440 | 1 | 1 |
One default monitor at 1 credit a run spends the Free plan’s 200 monthly credits in about 8 days; at every: 15, in about 2. Active monitors per plan: 3 Free, 25 Builder, 100 Pro.
List, read and stop
GET /api/v1/monitors lists your active monitors. GET /api/v1/monitors/:id returns one with its events. DELETE /api/v1/monitors/:id stops one; it and its history stay readable by id (active: false). From MCP: contrie_watch creates, contrie_monitors lists, contrie_changes reads a monitor’s events, and contrie_unwatch stops one. Stopping one that is already stopped differs: REST DELETE returns 404, while contrie_unwatch succeeds and returns the monitor unchanged.