> ## Documentation Index
> Fetch the complete documentation index at: https://docs.simplefunctions.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Research monitors

> Natural-language research monitors — keep an LLM-driven loop watching a topic, with results delivered to dashboard, webhook, or Telegram.

A **research monitor** is a long-running, natural-language research loop. You describe what you want watched (e.g. *"semiconductor sanctions affecting Taiwan markets"*) and SimpleFunctions runs the research on a cadence, posts results to your delivery channels, and stores history.

This is different from a [watch + alert rule](/build/watchlist-alerts), which fires on a structured condition (price threshold, econ release, ...). Research monitors are open-ended LLM runs over a topic.

## CLI

```bash theme={null}
sf monitor list                                  # list your monitors
sf monitor create "<intent>"                     # create + run once immediately
sf monitor show <id>                             # detail
sf monitor run <id>                              # run now
sf monitor run <id> --dry-run                    # plan only, no delivery
sf monitor delete <id>                           # delete
```

`sf monitor create` returns the new monitor and the first run result inline.

## API

All routes require `Authorization: Bearer sf_live_...` or browser session.

| Method   | Path                              | Purpose                                                 |
| -------- | --------------------------------- | ------------------------------------------------------- |
| `GET`    | `/api/research-monitors`          | List your monitors.                                     |
| `POST`   | `/api/research-monitors`          | Create a monitor (and optionally run it immediately).   |
| `GET`    | `/api/research-monitors/{id}`     | Detail.                                                 |
| `PATCH`  | `/api/research-monitors/{id}`     | Update fields.                                          |
| `DELETE` | `/api/research-monitors/{id}`     | Delete.                                                 |
| `POST`   | `/api/research-monitors/{id}/run` | Run now. Optional `?dryRun=true` or `{ dryRun: true }`. |

### POST /api/research-monitors

**Body**

| Field                              | Type                | Required | Default                    | Notes                                            |
| ---------------------------------- | ------------------- | -------- | -------------------------- | ------------------------------------------------ |
| `intent` (or `query`, `text`)      | string              | yes      | —                          | Natural-language description of what to watch.   |
| `name`                             | string              | optional | first 96 chars of `intent` | Display label.                                   |
| `topics` (or `topic`)              | string \| string\[] | optional | derived from intent        | Topic tags for filtering and dedupe.             |
| `cadenceMinutes` (or `cadence`)    | number              | optional | `360`                      | Run cadence in minutes.                          |
| `webhookEndpointId` (or `webhook`) | string              | optional | none                       | Existing webhook endpoint id.                    |
| `telegramChatId`                   | string              | optional | none                       | Telegram chat to deliver into.                   |
| `deliveryChannels`                 | array               | optional | inferred                   | Override the default channel list.               |
| `status`                           | string              | optional | `active`                   | `active`, `paused`, or `archived`.               |
| `runImmediately`                   | boolean             | optional | `true`                     | Skip the first immediate run by sending `false`. |
| `metadata`                         | object              | optional | —                          | Free-form metadata.                              |

**Response 201**

```json theme={null}
{
  "ok": true,
  "monitor": {
    "id": "rm_01J0...",
    "name": "Taiwan semiconductor sanctions",
    "intent": "semiconductor sanctions affecting Taiwan markets",
    "topics": ["geo", "semiconductor"],
    "cadenceMinutes": 360,
    "status": "active",
    "webhookEndpointId": null,
    "telegramChatId": null,
    "deliveryChannels": ["dashboard"],
    "nextRunAt": "2026-05-05T20:11:00.000Z",
    "createdAt": "2026-05-05T20:11:00.000Z"
  },
  "meta": { "fetchedAt": "2026-05-05T20:11:00.000Z" }
}
```

### PATCH /api/research-monitors/{id}

All fields are optional. Pass only the fields you want to change.

| Field                           | Notes                                                                    |
| ------------------------------- | ------------------------------------------------------------------------ |
| `name`                          | Up to 160 chars.                                                         |
| `intent`                        | Empty string returns `INVALID_INTENT`. Causes `topics` to be re-derived. |
| `topics` / `topic`              | Override topic list.                                                     |
| `cadence` / `cadenceMinutes`    | New cadence.                                                             |
| `status`                        | `active`, `paused`, `archived`. Other values return `INVALID_STATUS`.    |
| `webhookEndpointId` / `webhook` | Pass `null` to detach.                                                   |
| `telegramChatId`                | Pass `""` or `null` to detach.                                           |
| `deliveryChannels`              | Replace the channel list.                                                |
| `metadata`                      | Replace metadata.                                                        |

### POST /api/research-monitors/{id}/run

```bash theme={null}
curl -X POST -H "Authorization: Bearer $SF_API_KEY" \
  "https://simplefunctions.dev/api/research-monitors/rm_01J0.../run"

# dry run — compute the plan without delivering
curl -X POST -H "Authorization: Bearer $SF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"dryRun": true}' \
  "https://simplefunctions.dev/api/research-monitors/rm_01J0.../run"
```

Returns `{ ok: <bool>, result: <runResult>, meta: { fetchedAt } }`. Failures bubble up as 500 with `ok: false` so a webhook receiver can retry on transient errors.

### Errors

| Status | Code              | Cause                                         |
| ------ | ----------------- | --------------------------------------------- |
| `400`  | `INTENT_REQUIRED` | Missing `intent`.                             |
| `400`  | `INVALID_INTENT`  | Empty intent on update.                       |
| `400`  | `INVALID_STATUS`  | Status not in `active`, `paused`, `archived`. |
| `401`  | unauthorized      | No / invalid auth.                            |
| `404`  | `NOT_FOUND`       | Monitor doesn't exist or isn't yours.         |

## Patterns

### Watch a topic, post to Telegram

```bash theme={null}
sf monitor create "Taiwan semiconductor sanctions" --telegram-chat -100123 --cadence 240
```

### Programmatic create with webhook

```bash theme={null}
curl -X POST -H "Authorization: Bearer $SF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "intent": "Fed-cut speculation in OIS and Kalshi",
    "cadenceMinutes": 360,
    "webhookEndpointId": "we_01J0...",
    "deliveryChannels": ["webhook", "dashboard"]
  }' \
  "https://simplefunctions.dev/api/research-monitors"
```

### Run on demand from a CI job

```bash theme={null}
curl -X POST -H "Authorization: Bearer $SF_API_KEY" \
  "https://simplefunctions.dev/api/research-monitors/rm_01J0.../run"
```

## See also

<CardGroup cols={2}>
  <Card title="Watchlist + alerts" href="/build/watchlist-alerts">
    Structured price / econ / gov alerts.
  </Card>

  <Card title="Webhooks" href="/build/webhooks">
    Signed delivery and replay.
  </Card>

  <Card title="Telegram bot" href="/cli/telegram">
    Bridge the CLI to a chat surface.
  </Card>

  <Card title="Direct API access" href="/build/direct-api-access">
    Auth, base URLs, language examples.
  </Card>
</CardGroup>
