# STELQ Monitors API

Standing watches on any topic. Describe what to watch in natural language; STELQ compiles it into diverse search queries, authoritative domains, a per-topic significance rubric and direct sources (feeds, JSON endpoints, high-signal pages — each confirmed by a live fetch). The first check establishes a baseline; every later check on the cadence you chose is recorded and badged minor / notable / major against it, with a summary and the new items. Price follows size: $0.05 to create per block of 12 queries, $0.01 per check per block — preview both with a free plan call before you pay.

- **Endpoint:** `POST https://api.stelq.com/v1/monitors/create`
- **Auth:** `Authorization: Bearer $STELQ_KEY`
- **Price:** $0.05 / monitor per block of 12 queries, then $0.01 per check per block
- **Mode:** standing — create once, checks run on a cadence; read events back or take a webhook
- **Canonical URL:** https://stelq.com/api/docs/monitors (public, no auth, always current)

## Authentication

Every request needs a bearer token: `Authorization: Bearer $STELQ_KEY`. Create and scope keys in the console under API Keys — the secret is shown once. In the in-console Playground the key is injected for you; copied payloads reference the `$STELQ_KEY` environment variable, so set it in your agent's environment instead of pasting a live secret into a prompt.

## Endpoints

| Route | Price | Purpose |
| --- | --- | --- |
| `POST /v1/monitors/plan` | free | Compile the watch plan + price WITHOUT creating anything. Body: `query`, `interval?`, `max_queries?`. Returns `watch_plan` + `pricing`. Rate-limited with create. |
| `POST /v1/monitors/create` | $0.05 / block | Create the monitor (this page's request). Charged by size on a 201 only. |
| `GET /v1/monitors` | free | List your monitors with status, pricing and the latest event badge. |
| `GET /v1/monitors/{id}` | free | One monitor: watch plan, baseline, pricing and its recent events (`?limit=`, ≤100). |
| `GET /v1/monitors/{id}/events` | free | The event feed only — one entry per check: `significance`, `headline`, `summary_md`, `new_items[]`. |
| `GET /v1/monitors/{id}/events/{event_id}` | free | One check in full, plus `webhook` delivery status (state, attempts, last status) — the webhook's `result_url`. |
| `PATCH /v1/monitors/{id}` | free | Edit `name`, `interval`, `notify_threshold`, `webhook` (null removes) or `queries` (re-sizes the price). |
| `POST /v1/monitors/{id}/pause` | free | Stop checking (and billing) until resumed. |
| `POST /v1/monitors/{id}/resume` | free | Resume; the next check runs on the next sweep. |
| `DELETE /v1/monitors/{id}` | free | Delete the monitor and its record. |

## Request

`POST /v1/monitors/create`

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `query` | string | yes | What to watch, in natural language — a topic, vendor, regulation, competitor set. 3–2000 chars. `prompt` is accepted as an alias. |
| `interval` | enum | no | Check cadence: 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 24h, 7d (default 24h). The cadence ladder is the price ladder — see `pricing.est_daily_usd` on the response. |
| `notify_threshold` | enum | no | all, notable, or major (default all). Gates the WEBHOOK only — every check is recorded and readable regardless. |
| `webhook` | url (https) | no | Optional. Signed `monitor.baseline` / `monitor.change` events are POSTed here — same envelope, signature and retries as Research webhooks. Must be https. |
| `watch_plan` | object | no | Optional. The `watch_plan` returned by `POST /v1/monitors/plan`, as-is or edited (trim `queries` to lower the price, drop a source, add a domain). Skips the compile; you pay exactly the price the plan showed. |
| `max_queries` | integer | no | Optional price ceiling, 1–120: keep at most this many compiled queries. 12 queries = one price block. |
| `name` | string | no | Optional label (≤120 chars). Defaults to the compiled name. |

```json
{
  "query": "Anything about Regulation E (electronic fund transfers): rule changes, CFPB guidance, enforcement actions",
  "interval": "6h",
  "notify_threshold": "notable",
  "webhook": "https://example.com/hooks/stelq"
}
```

## How it runs

How a monitor runs: create (or plan → create) compiles the watch plan and returns the monitor with `pricing` and `next_check_at`. Within 5 minutes the first check runs and writes the BASELINE (the state of affairs; readable as `baseline_md` on the detail call). Every later check on your cadence retrieves the plan's queries and direct sources, diffs against everything the monitor has already seen, gates off-topic pages, and — when something is new — writes one event with `significance` (none / minor / notable / major, scored against the baseline using the compiled rubric), a one-line `headline`, a `summary_md` and the `new_items`. Quiet checks are recorded too (`significance: "none"`). Each completed check bills `pricing.per_check_usd`; failed checks are never billed. If the balance can't cover a check it is skipped and the schedule pushed; three consecutive skips auto-pause the monitor (`paused_reason: "insufficient_balance"`) — top up and resume. Read events back with the events routes, or take a webhook for the ones that clear your `notify_threshold`.

## Response

```json
{
  "monitor": {
    "id": "a01aede0-ffb0-41a8-ae64-b92190596f59",
    "name": "Regulation E watch",
    "status": "active",
    "interval_minutes": 360,
    "notify_threshold": "notable",
    "webhook_url": "https://example.com/hooks/stelq",
    "pricing": {
      "size_blocks": 1,
      "queries": 8,
      "create_usd": 0.05,
      "per_check_usd": 0.01,
      "checks_per_day": 4,
      "est_daily_usd": 0.04
    },
    "next_check_at": "2026-08-28T14:05:00Z",
    "watch_plan": {
      "queries": [
        "Regulation E amendments 2026",
        "CFPB Regulation E guidance",
        "…"
      ],
      "preferred_domains": [
        "consumerfinance.gov",
        "federalregister.gov"
      ],
      "freshness_days": 7,
      "significance_rubric": "major: a final rule or enforcement action; notable: proposed rule, official guidance; minor: commentary…",
      "sources": [
        {
          "type": "feed",
          "url": "https://www.consumerfinance.gov/about-us/newsroom/feed/",
          "healthy": true
        }
      ]
    }
  },
  "webhook": {
    "url": "https://example.com/hooks/stelq",
    "signed_with": "account_signing_secret",
    "events": [
      "monitor.baseline",
      "monitor.change"
    ]
  },
  "first_check": "within 5 minutes; the first check establishes the baseline your updates are measured against"
}
```

## Webhooks

Pass a `webhook` (https URL) on create — or set one later with PATCH — and STELQ POSTs a signed event for the first check (`monitor.baseline`, always, so you can verify the integration) and for every later check whose `significance` clears your `notify_threshold` (`monitor.change`). Quiet checks never notify. The event is a claim-check with a routing hint: `data.significance` and `data.headline` let you route without a fetch; `data.result_url` returns the full check (summary, items) over the authenticated API. Same envelope, headers, secret and retries as Research webhooks — one receiver handles both.

The event we POST:

```json
{
  "id": "evt_01JX9A4M2KQ7",
  "type": "monitor.change",
  "api_version": "2026-06-01",
  "occurred_at": "2026-08-28T14:07:12Z",
  "attempt": 1,
  "data": {
    "monitor_id": "a01aede0-ffb0-41a8-ae64-b92190596f59",
    "monitor_name": "Regulation E watch",
    "event_id": "5c1b7e2a-0d4f-4c8e-9a3b-2f6d8e1c4b7a",
    "significance": "notable",
    "headline": "CFPB issues proposed rule expanding Reg E error-resolution to P2P fraud",
    "new_count": 3,
    "result_url": "https://api.stelq.com/v1/monitors/a01aede0-ffb0-41a8-ae64-b92190596f59/events/5c1b7e2a-0d4f-4c8e-9a3b-2f6d8e1c4b7a"
  }
}
```

Signature headers: `webhook-id`, `webhook-timestamp`, `webhook-signature`.

**Verify the signature.** Verify before you trust the body: recompute HMAC-SHA256 over `{webhook-id}.{webhook-timestamp}.{rawBody}` with your account signing secret (`whsec_…`, revealed any time under API Keys in the console — the SAME secret that signs Research webhooks), then constant-time compare it against the `webhook-signature` header (format `v1,<base64>`). Reject timestamps older than ~5 minutes, and always verify against the raw request bytes.

Delivery contract:

- At-least-once delivery — dedupe on `data.event_id` (+ `type`). One check can never produce two different events.
- Acknowledge fast: return a 2xx within 10 seconds, then do any slow work asynchronously — otherwise we time out and retry.
- On any non-2xx or timeout we retry with exponential backoff, up to 8 attempts over ~24h, then dead-letter. `GET …/events/{event_id}` shows the delivery state for that check.
- Webhook down? Nothing is lost — every check is recorded; read the events feed.
- `monitor.baseline` fires once (the first check). `monitor.change` fires only for checks at or above `notify_threshold`; a check with `significance: "none"` never notifies.
- The secret is snapshotted onto the monitor when the webhook is set. Rotating your account secret never breaks a running monitor; re-set the webhook (PATCH) to pick up a new secret.

## Errors

| Status | Meaning |
| --- | --- |
| 400 | Invalid request — a field is missing or malformed (the body says which). |
| 401 | Missing or invalid API key. |
| 402 | Insufficient balance — top up credits to continue. |
| 429 | Rate limited — back off and retry after the Retry-After header. |
| 502 | Upstream temporarily unavailable — safe to retry; not charged. |
| 404 | No monitor (or event) with that id on this account. |

## Rate limits

`plan` and `create` each compile a watch plan (one model call + live source verification), so they share a limit of 10 per minute per account; a 429 carries a Retry-After header. Reads, edits, pause/resume and delete are unmetered. Checks themselves run on our schedule and never count against your limit.
