Skip to content
Web data APIsMonitors

Web data APIs

On this page
POST/api/v1/monitors

Monitors

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

FieldTypeDescription
urlstringThe public page to watch. Required.
extractstringWhat to watch, in plain language. Required unless schema is given.
schemaobjectJSON Schema of the answer.
everyintegerMinutes between runs, 15 to 10,080. Default 60.
webhookUrlstringhttps URL to POST changes to. The signing secret is returned once, in the create response.

Create

POST /api/v1/monitors → 201 with the baseline
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

GET /api/v1/monitors/:id · events[0]
{
  "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.

everyRuns per dayCredits per day at 1 a run
159696
60 (default)2424
144011

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.