# /v1/parallel/monitors

**Parallel Monitors.** A [Parallel](https://parallel.ai) monitor watches the web
for you: you give it a question in plain words, such as "New releases or security
advisories of the PostgreSQL database", and a schedule from every hour to every 30
days. Each scheduled run (an **execution**) searches the web and records an
**event** when it finds something new and material. You read the events whenever
you like. A monitor runs until you cancel it, or until RouterPlus stops it because
it can no longer be paid for (see [When a monitor is stopped](#when-a-monitor-is-stopped)).

| Route | What it does |
|---|---|
| `POST /v1/parallel/monitors` | Create a monitor. It runs once at once, then on its schedule |
| `GET /v1/parallel/monitors` | List your organization's monitors, newest first |
| `GET /v1/parallel/monitors/{id}` | Read one monitor |
| `POST /v1/parallel/monitors/{id}/update` | Change its schedule, processor, query or spend limit |
| `POST /v1/parallel/monitors/{id}/trigger` | Run it once now, off schedule |
| `POST /v1/parallel/monitors/{id}/cancel` | Stop it for good |
| `GET /v1/parallel/monitors/{id}/events` | Read what it found |

The field names are Parallel's, so [Parallel's Monitor docs](https://docs.parallel.ai/monitor-api/monitor-quickstart)
apply to the body you send, within the limits below.

> [!NOTE]
> Your query goes to Parallel, a third party, which may use it and the events to
> improve its services and stores data in the US. Do not send what you would not
> share with Parallel. See [Data](#data).

## Who can use it

An **organization API key of a paid organization**: a key you created in
[API keys](https://app.routerplus.com/console/keys) for an organization that has added paid
credit. Playground and evaluation keys, and organizations still on the free trial,
get 403 `forbidden`: "Parallel Monitors need an organization API key from a paid
organization: create one in API keys and add paid credit in Billing". Nothing is
reserved or sent.

Monitor executions are billed to your organization's credit. A key limited to your
own provider keys or to a routing policy cannot spend it, so a create, an update or a
trigger with such a key is a 403 `forbidden` too: "This API key is limited to its own
provider keys or a routing policy, and Parallel Monitors are billed to marketplace
credit: use an organization API key without that limit.
A routing policy set on the organization or workspace applies to every key, so an owner must change it in the console". Nothing is reserved or sent.
Reads, lists, events and cancels are unchanged.

## Prices

Parallel's list price for the processor, **per execution**, with no markup. Every
execution is billed, whether it found something or not:

| Processor | Price per execution | Per 1,000 executions |
|---|---|---|
| `lite` | $0.003 | $3 |
| `base` | $0.01 | $10 |

`core` is not offered. A failed execution (an `error` event) is not billed.

**What a monitor costs:** executions in a period × the price. A `1h` `lite` monitor
runs 24 times a day: 24 × 30 = 720 executions in 30 days, **$2.16**. The same
monitor at `1d` is 30 executions, $0.09; at `1h` on `base`, 720 × $0.01 = $7.20.
Each trigger is one more execution, and creating a monitor runs it once at once.

How the money moves:

- **The credit check.** Creating a monitor needs credit for **24 hours of its
  executions**: `ceil(86400 / frequency in seconds) + 1` executions at its price,
  held for a moment and released (a `1h` `lite` monitor: 25 × $0.003 = $0.075). Without
  that credit the create is a 402 `insufficient_credit` and nothing runs. The check
  is run again every 24 hours while the monitor runs, and when you change its
  frequency or processor.
- **The charge.** RouterPlus reads each monitor's events at least once an hour and
  charges every execution it has not charged before, once, at the price of the
  processor it ran on. Each execution is one attempt in
  [GET /v1/usage & /v1/generation](/docs/api-usage): provider `parallel`, model
  `parallel:monitor:<processor>`, the price as `server_tool_fee_usd`. So a charge
  can appear up to an hour after the execution ran.
- **After a stop.** An execution still running when a monitor is stopped (by you or
  by RouterPlus) is charged when it finishes: RouterPlus keeps reading a stopped
  monitor's events for 15 minutes (first after 60 seconds, then every 5 minutes)
  and charges what it has not charged yet. Nothing new starts after the stop.
- **Never below zero.** An execution that cannot be charged (no credit left, a
  spend cap reached, the key disabled, or past `spend_limit_usd`) is not charged at
  all, and the monitor is stopped in the same pass.
- **Your spend limit.** `spend_limit_usd` (default $10) caps what one monitor can
  charge: when the next execution would pass it, the monitor is stopped.
- The monitor's `charged_usd` and `executions_charged` say what it has charged so far.

## Create a monitor

```bash
curl https://api.routerplus.com/v1/parallel/monitors \
  -H "Authorization: Bearer $TM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "event_stream", "frequency": "1d", "processor": "lite",
       "settings": {"query": "New releases or security advisories of the PostgreSQL database"},
       "spend_limit_usd": 2}'
```

201, with the monitor:

```json
{"id": "pmon_6f0c1e2d3a4b5c6d7e8f901a2b3c4d5e", "object": "parallel.monitor", "status": "active",
 "type": "event_stream", "frequency": "1d", "processor": "lite",
 "query": "New releases or security advisories of the PostgreSQL database",
 "spend_limit_usd": 2, "charged_usd": 0, "executions_charged": 0,
 "last_run_at": "2026-11-06T01:40:24.767Z", "created_at": "2026-11-06T01:40:24.512Z",
 "cancelled_at": null, "cancel_reason": null}
```

The body, at most 32 KB:

| Field | Required | What it takes |
|---|---|---|
| `type` | no | `event_stream` (the default and the only type offered) |
| `frequency` | yes | `<number><h, d or w>` from `1h` to `30d`: `1h`, `6h`, `1d`, `7d`, `2w`, `30d` |
| `processor` | no | `lite` (default) or `base` |
| `settings.query` | yes | What to watch for, 1 to 2,000 characters |
| `settings.output_schema` | no | `{"type": "json", "json_schema": {...}}`, at most 16 KB: each event's output then follows the schema. A schema no answer can satisfy (an empty `enum`, a `required` field `properties` does not define) is refused |
| `settings.include_backfill` | no | `true`: the first execution also returns a sample of recent past events |
| `spend_limit_usd` | no | RouterPlus's own: the most this monitor may charge, $0.01 to $100, default $10 |

Refused with a 400 `invalid_request` that names the field, before anything is held
or sent:

| Field | Why |
|---|---|
| `type: "snapshot"` | Snapshot monitors are not offered yet |
| `processor: "core"` | It has no list price |
| `webhook` | Not supported: read the events |
| `metadata` | RouterPlus sets the monitor's metadata itself |
| `memory_scope_key` | RouterPlus does not use Parallel memory |
| `settings.advanced_settings` | Not supported |
| any other field | Unknown |

## Read and list

`GET /v1/parallel/monitors/{id}` answers the monitor:

| Field | Meaning |
|---|---|
| `id` | Our id, `pmon_` and 32 hex digits. Parallel's own monitor id is never shown |
| `status` | `active`; `cancelling` (being stopped; see Troubleshooting); `cancelled`; `failed` (never created); `creating` (for a moment) |
| `frequency`, `processor`, `query` | As set. `query` is read from Parallel and is `null` in a list |
| `spend_limit_usd`, `charged_usd`, `executions_charged` | USD and a count |
| `last_run_at` | When Parallel last ran it, refreshed from Parallel at most every 30 seconds |
| `created_at`, `cancelled_at` | RFC 3339 times |
| `cancel_reason` | `null`, or `{"code", "message"}`: why it was stopped and what to do (see below) |

`GET /v1/parallel/monitors` lists your organization's monitors, newest first:
`?limit=` 1 to 100 (default 20), `?status=` `creating`, `active`, `cancelled` or
`failed`, and `?cursor=` the `next_cursor` of the previous page.

An unknown id, a malformed id and another organization's monitor all get the same
404 `not_found`: `monitor "<id>" not found`.

## Read events

`GET /v1/parallel/monitors/{id}/events` reads what the monitor found, newest first:

```json
{"object": "list", "next_cursor": null, "events": [
 {"event_id": "...", "event_group_id": "mevtgrp_...", "event_date": "2026-11-05", "event_type": "event_stream",
  "output": {"type": "text", "content": "PostgreSQL 18.1 was released ...",
             "basis": [{"field": "output", "citations": [{"url": "https://www.postgresql.org/...", "title": "...", "excerpts": ["..."]}],
                        "reasoning": "...", "confidence": "high"}]}},
 {"event_type": "completion", "timestamp": "2026-11-06T01:40:25.175Z"}]}
```

| Parameter | What it takes |
|---|---|
| `limit` | 1 to 100 (default 20) |
| `cursor` | The `next_cursor` of the previous page of this monitor's events. It is sealed: opaque, and it works only on the monitor it came from |
| `include_completions` | `true` also lists the executions that found nothing (`completion` events) |
| `event_group_id` | One execution's events only (`mevtgrp_...`) |

- One execution gives one of: one or more `event_stream` events sharing an
  `event_group_id` (it found something); one `completion` event (it found nothing,
  listed only with `include_completions=true`); one `error` event (it failed:
  `error_message` is always "the execution failed", and it is not billed).
- **The event window:** Parallel lists only about the **300 most recent
  executions** of a monitor: at `1h` that is about 12 days, at `1d` about 10
  months. Older events roll off without notice. Read the events of a frequent
  monitor at least every few days, and keep what you need: RouterPlus keeps no event.
- Events are fetched from Parallel each time you ask, including after the monitor
  is cancelled.
- While Parallel Monitors are off or being wound down on the gateway, every route,
  events included, answers 404 (see Troubleshooting). A page over 2 MB is a 502 `too_large`: ask for fewer with `limit`.
- Only the fields above are passed on.

## Update, trigger, cancel

**Update:** `POST /v1/parallel/monitors/{id}/update` with any of `frequency`,
`processor`, `settings.query` and `spend_limit_usd`; only what you send changes.
An empty body or any other field is a 400. A new frequency or processor first runs
the credit check for the new schedule on the key that created the monitor, which its
executions are charged to (402 `insufficient_credit` changes nothing; a key that can
no longer be charged is a 409 `monitor_key_unusable`), and the executions already run
are charged at the old price. When those cannot be read and charged now, the update is
a 503 `gateway_error` and nothing changes: retry after `retry-after`. A cancelled
monitor cannot be updated: 409 `monitor_cancelled`.

**Trigger:** `POST /v1/parallel/monitors/{id}/trigger` runs the monitor once now and
answers 202 with the monitor. It needs credit for one execution (402 otherwise) and
room under `spend_limit_usd` (409 `spend_limit_reached` otherwise). The execution is
charged to the key that created the monitor, so that key must still be usable: if it
was disabled, the trigger is a 409 `monitor_key_unusable`, and RouterPlus stops the
monitor on its next pass (within minutes). At most 5 triggers a day per monitor (a
sixth is a 429 `rate_limit` until 00:00 UTC). A trigger Parallel refused (a 502 that
says "nothing was charged") does not count. One Parallel did not confirm (a 502 that
says it "did not confirm the execution") counts, since it may have run: read the events
before triggering again. If Parallel answers that it no longer runs the monitor, the
trigger is a 409 `monitor_cancelled` and the monitor is closed (`upstream_cancelled`). The execution is charged when
RouterPlus reads it, like any other. A cancelled monitor: 409.

**Cancel:** `POST /v1/parallel/monitors/{id}/cancel` stops the monitor for good.
RouterPlus first reads its events once more and charges the executions not yet
charged, then stops it at Parallel, then answers 200 with the monitor
(`status: "cancelled"`, `cancel_reason.code: "buyer_cancelled"`). Cancelling again
answers the same. If Parallel does not confirm the stop, the answer is a 502
`cancel_failed`: the monitor shows `cancelling` and RouterPlus keeps retrying the
stop on its own; read the monitor to see it end.

## When a monitor is stopped

RouterPlus stops a monitor at Parallel when it can no longer charge for it, and
says why in `cancel_reason`:

| `cancel_reason.code` | Why | What to do |
|---|---|---|
| `insufficient_credit` | The credit check for 24 hours of executions failed, or an execution could not be charged | Add credit in Billing, then create a new monitor |
| `spend_limit_reached` | The next execution would pass `spend_limit_usd` | Create a new monitor with a higher limit |
| `spend_cap_reached` | A monthly spend cap of the organization or of the key was reached | Raise the cap or wait for its reset, then create a new monitor |
| `key_disabled` | The key that created it was disabled or deleted | Create a new monitor with an active key |
| `org_closed` | The organization was closed | |
| `org_unpaid` | The organization no longer has paid credit | Add credit in Billing, then create a new monitor |
| `buyer_cancelled` | You cancelled it | |
| `service_disabled` | Parallel Monitors were turned off on this gateway | Waiting is safe: nothing more runs or is charged. Contact support if you need Monitors there |
| `orphan` | It was never fully created | Create it again |
| `upstream_cancelled` | Parallel stopped it | Create a new monitor |

An execution that ran but could not be charged is not billed to you.

**RouterPlus does not notify you when it stops a monitor** (no e-mail, no webhook):
read the monitor (or list `?status=cancelled`) and its `cancel_reason`. A monitor
stopped with `service_disabled` cannot be read while Parallel Monitors are off on the
gateway (every route answers 404); its `cancel_reason` shows once they are back.

## Follow-up research

To research an event further, start a [Parallel Task run](/docs/parallel-tasks):
`POST /v1/parallel/tasks/runs` with the event's content in `input`. Monitor
events cannot be chained into a Task with `previous_interaction_id`; send the
context in `input` instead.

## Limits

| Limit | Value | Over it |
|---|---|---|
| Creates per organization per minute | 10 | 429 `rate_limit`: wait `retry-after` |
| Reads, lists and event pages per organization per minute | 60 | 429 `rate_limit`: wait `retry-after` |
| Updates, triggers and cancels per organization per minute | 10 | 429 `rate_limit`: wait `retry-after` |
| Active monitors (`creating` or `active`) per organization | 10 | 429 `monitor_limit`: cancel one first |
| Triggers per monitor per day (UTC) | 5 | 429 `rate_limit` until 00:00 UTC |
| Frequency | `1h` to `30d` | 400 `invalid_request` |
| `spend_limit_usd` | $0.01 to $100, default $10 | 400 `invalid_request` |
| Request body | 32 KB | 400 `invalid_request` |
| `settings.query` | 2,000 characters | 400 `invalid_request` |
| `settings.output_schema` | 16 KB | 400 `invalid_request` |
| Events listed by Parallel | about the 300 most recent executions | Older ones are no longer listed |
| An events page | 2 MB | 502 `too_large` |
| `cursor` | 1024 characters | 400 `invalid_request` |

## Errors

| Status | `error_type` | When |
|---|---|---|
| 400 | `invalid_request` | A field refused or over its limit (the message names it) |
| 400 | `upstream_rejected` | Parallel refused the monitor. Not billed |
| 402 | `insufficient_credit` | Not enough credit for the credit check (24 hours of executions, or one for a trigger). The message says how much is needed |
| 403 | `forbidden` | Not an organization key of a paid organization: create one in API keys and add paid credit. Or, on a create, an update or a trigger, a key limited to its own provider keys or a routing policy: use one without that limit (a policy on the organization or workspace applies to every key: an owner changes it in the console) |
| 404 | `not_found` | No such monitor for your organization |
| 409 | `monitor_cancelled` | Update or trigger on a monitor that is cancelled or being cancelled: create a new one |
| 409 | `spend_limit_reached` | A trigger would pass `spend_limit_usd`: raise it with update |
| 409 | `monitor_not_ready` | Cancel, update or trigger while the monitor is still being created: retry in a few seconds |
| 409 | `monitor_key_unusable` | Trigger, or an update of the frequency or processor, when the key that created the monitor can no longer be charged: create a new monitor with an active key |
| 429 | `rate_limit` | A per-minute or per-day limit: wait `retry-after` |
| 429 | `monitor_limit` | 10 monitors already active: cancel one first |
| 429 | `insufficient_quota` | A monthly spend cap: it resets at the time in `x-tm-cap-reset` |
| 502 | `upstream_error` | Parallel did not answer. A failed create is not billed |
| 502 | `cancel_failed` | Parallel did not confirm a cancel: RouterPlus keeps retrying it |
| 502 | `too_large` | An events page over 2 MB |
| 503 | `provider_unavailable` | See Troubleshooting |
| 503 | `gateway_error` | Our monitor store or ledger is briefly unavailable, or an update could not charge the executions already run: nothing changed; retry after `retry-after` |

Parallel's own error text is never passed on.

## Troubleshooting

- **Every `/v1/parallel/monitors` path answers 404 `not_found`, like an unknown
  route.** Parallel Monitors are not available on the gateway you called: either
  they are not enabled there, or they are being wound down (monitors that were
  running are being stopped and charged for what they ran). Check that you call
  the gateway your organization uses; if you expected Monitors there, contact
  support.
- **503 `provider_unavailable`: "Parallel Monitors are not set up on this
  gateway".** The routes are enabled but not fully configured. Retrying will not
  help: contact support.
- **503 `provider_unavailable`: "Parallel Monitors are unavailable right now" or
  "Parallel is limiting new monitors".** Parallel is refusing or limiting us for
  the moment. Nothing was charged; retry later.
- **A monitor's `status` is `cancelling`.** RouterPlus has stopped it and is
  confirming the stop with Parallel; it becomes `cancelled` on its own. Until then
  it is listed under `?status=active` and counts toward your 10 active monitors.
- **`charged_usd` lags behind `last_run_at`.** Executions are charged when
  RouterPlus reads them, at least once an hour.

## Data

- Your `settings.query` and `settings.output_schema` go to Parallel, a third party,
  with our Parallel key, for every execution. Under Parallel's standard terms it may
  use them and the events to improve its services. Our agreement with Parallel does
  not yet change that, so treat monitors as retained and possibly used for training
  by Parallel. Parallel stores data in the US. See [Data policy](/docs/data-policy).
- RouterPlus keeps, per monitor, ids, the schedule, the processor, the status, the
  spend limit, what was charged and times, and per execution its kind, time and
  charge: no query, schema, event, output or Parallel text. The create body is
  logged only if your organization has [request logging](/docs/data-policy) on, as
  any request is. Events are never logged or kept.
- RouterPlus never sends `memory_scope_key` or `webhook`, and tags each monitor at
  Parallel with its own id and environment only.
