URL: /code/data/monitors/create · Id: `page:/code/data/monitors/create`

# Monitor.create

> Run any data call on a schedule and record an Event only when it changes: a rank moves, an answer names you, a link drops.

`POST https://api.sb/{your-domain}/monitors`

Run any data call on a schedule and record an [Event](https://startupbuilder.dev/business/nouns/event) only when it changes: a rank moves, an answer names you, a link drops.

- **Price:** Free
- **Success rate:** 99.2% of 2,079,088 calls
- **Freshness:** Read fresh each call
- **Emits:** `Monitor.created`
- **Capability:** [Schedules](https://startupbuilder.dev/code/platform/schedules)

```text title="Run it · Ask"
Watch where leaseline.app ranks for “rent tracking app for landlords” every day, and tell me when it moves 3 places.
```

```ts title="Run it · SDK"
import { $ } from 'startup-builder'

const result = await $.try.Monitor.create({ verb: 'Keyword.search', input: { keyword: 'rent tracking app for landlords', venue: 'google-search' }, change: 'rank-change', threshold: 3, every: '1d', maxCost: 0 })
```

```json title="Run it · Response"
{
  "$id": "https://api.sb/leaseline.app/monitors/monitor_Rk2a",
  "verb": "Keyword.search",
  "input": {
    "keyword": "rent tracking app for landlords",
    "venue": "google-search"
  },
  "change": "rank-change",
  "threshold": 3,
  "every": "1d",
  "nextRunAt": "2026-10-28T06:00:00Z",
  "lastResult": null,
  "costPerRun": {
    "usd": 0,
    "maxUsd": 0.0003,
    "units": 1,
    "misses": 0
  }
}
```

## Options and prices
| You get | Input that selects it | Price | Success rate |
| --- | --- | --- | --- |
| Rank tracking, daily | `{"change":"rank-change","every":"1d"}` | About 0.9¢ a term a month | 99.1% of 1,710,120 calls |
| AI answer watch, weekly, 3 runs on 4 engines | `{"change":"answer-citation","every":"1w"}` | About 19¢ a question a month | 99.96% of 131,093 calls |
| Buying triggers on an account, weekly | `{"change":"funding","every":"1w"}` | About 4.3¢ an account a month | 99.98% of 34,785 calls |
| A page changes, daily | `{"change":"page-change","every":"1d"}` | About 0.6¢ a page a month | 100% of 14,847 calls |
| Problem posts on a site, daily | `{"change":"problem-question","every":"1d"}` | About 5.7¢ a phrase a month | 99.4% of 142,199 calls |
| Lost links, weekly | `{"change":"link-lost","every":"1w"}` | About 10¢ a domain a month | 100% of 298 calls |
| Index state of key pages, daily | `{"change":"index-change","every":"1d"}` | Free | 99.8% of 45,746 calls |

## Inputs
| Name | Type | Default | Meaning |
| --- | --- | --- | --- |
| `verb` | `string` | Required | The data call to run, such as `Keyword.search`; any call on this page’s list but the Monitor calls. |
| `input` | `object` | Required | The wrapped call’s input, exactly as on its page. |
| `change` | `'rank-change' \| 'answer-citation' \| 'funding' \| 'hiring' \| 'job-change' \| 'technology-change' \| 'page-change' \| 'index-change' \| 'problem-question' \| 'brand-mention' \| 'link-lost' \| 'link-gained' \| 'new-ad' \| 'new-term' \| 'new-issue' \| 'volume-change'` | Required | What counts as a change. A run that sees none records nothing. |
| `threshold` | `number` | `3` | With `rank-change`: places moved before it counts. With `answer-citation`: share points moved, default 5. |
| `every` | `string` | `'1w'` | How often it runs: `1d` for ranks, posts and index state, `1w` for answers, accounts and links. |
| `window` | `string` | `per `change`` | How long a recorded change stays actionable for a Trigger, such as 30 days for funding. |
| `maxCostPerRun` | `number` | `the wrapped call’s default` | The most one run may spend, in USD. A run over it is skipped and says so. |

## Outputs
| Field | Type | What `null` means |
| --- | --- | --- |
| `$id` | `string` | Never null: the Monitor’s address. |
| `verb` | `string` | Never null. |
| `input` | `object` | Never null. |
| `change` | `string` | Never null. |
| `threshold` | `number \| null` | The change has no threshold. |
| `every` | `string` | Never null. |
| `nextRunAt` | `string`, in ISO 8601 | Never null. |
| `lastResult` | `string \| null` | It has not run yet. |
| `costPerRun` | [`Cost`](/code/data/types#cost), in USD | Never null: what one run costs at the wrapped call’s price. |

## Read the result
| Field | Value | What it means | What to do |
| --- | --- | --- | --- |
| `costPerRun` | Times the runs a month | What the Monitor costs you: daily is about 30 runs, weekly about 4.3. | Run ranks daily and answers weekly; an answer engine re-reads a page in 2 to 8 weeks, so daily answer checks buy noise. (the AEO playbook) |
| `threshold` | Under 3 for ranks | Ranks move a place or two day to day without anything changing. | Keep the default of 3; a smaller move is not a change. (our planning number) |
| `threshold` | Under 5 for answers | At 200 answers a 10% share reads ±4.2 points. | Flag a move of 5 points or more that holds for 2 weeks. (binomial interval, the strategy numbers) |
| `change` | `funding` or `hiring` | A dated reason to write to that account. | Set `window` to 30 days and let a Trigger draft the first email from the Event. (the trigger rule for cold email) |

## Effects
**Action class**

None: no approval rule names it, so it runs within its seat’s Permissions and Limits

**Modes**

- `try`: Returns the Monitor it would create and its cost per run; nothing is scheduled and nothing is billed.
- `send`: Returns a pending Action at once; the Monitor is scheduled when the Action settles.
- `do`: Creates and schedules the Monitor, and returns it.

**Idempotency**

The same `Idempotency-Key` with the same input returns the first Monitor; with other input it returns 409.

**Saved or shared**

Saves the Monitor to your records. Each run records the wrapped call’s Event, and a change records one more Event that a Trigger can fire on.

**Emits**

`Monitor.created` with `{ monitor, verb, input, change, every }`

**Performed by**

[Analyst](https://startupbuilder.dev/agents/marketing/analyst)

**Price**

Free. Free: creating, pausing and deleting a Monitor; each run bills at its wrapped call’s price. Rank tracking on 40 terms for a month: 40 × 0.9¢ a term a month is about 36¢.

**Reliability**

The first route succeeds 99.2% of 2,079,088 calls. If it fails, the call refuses with `sources-busy` and nothing is spent.

**Freshness**

Every call reads fresh. Readings about people are never shared across startups.

**Batch and pages**

Up to 1 Monitor a call, up to 1,000 a startup. It answers in one response.

**Rate**

[The shared rate rules](/code/data#rate): 10 calls a second per key, and an array input counts as one call.

## Refuses when
This call has no refusals of its own. Every data call can also meet [the shared data-call refusals](/code/refusals#data-calls): [`filter-unsupported`](/code/refusals#filter-unsupported), [`input-missing`](/code/refusals#input-missing), [`limit-exceeded`](/code/refusals#limit-exceeded), [`max-cost`](/code/refusals#max-cost), [`rate-limited`](/code/refusals#rate-limited), [`sources-busy`](/code/refusals#sources-busy), [`venue-unsupported`](/code/refusals#venue-unsupported).

## When it waits
Returns in one response; nothing waits.

## Example

**Ask**

```text
Watch where leaseline.app ranks for “rent tracking app for landlords”
every day, and tell me when it moves 3 places.
```

**API**

```bash
curl -X POST "https://api.sb/leaseline.app/monitors?mode=try" \
  -H "Authorization: Bearer $SB_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"verb":"Keyword.search","input":{"keyword":"rent tracking app for landlords","venue":"google-search"},"change":"rank-change","threshold":3,"every":"1d","maxCost":0}'
```

The response is the one in the Run it window above.

**CLI**

```bash
npx startup-builder do Monitor.create --try --data '{"verb":"Keyword.search","input":{"keyword":"rent tracking app for landlords","venue":"google-search"},"change":"rank-change","threshold":3,"every":"1d","maxCost":0}'
```

**MCP**

```json
{
  "name": "do",
  "arguments": {
    "mode": "try",
    "code": "return await $.Monitor.create({ verb: 'Keyword.search', input: { keyword: 'rent tracking app for landlords', venue: 'google-search' }, change: 'rank-change', threshold: 3, every: '1d', maxCost: 0 })"
  }
}
```

**SDK**

```ts
import { $ } from 'startup-builder'

const result = await $.try.Monitor.create({
  verb: 'Keyword.search',
  input: { keyword: 'rent tracking app for landlords', venue: 'google-search' },
  change: 'rank-change',
  threshold: 3,
  every: '1d',
  maxCost: 0,
})
```

To retry, send the same `Idempotency-Key`: the retry returns the first answer for $0. A new key reads again.

A `sk_test_` key answers these the same way every time, at $0 ([every test value](/code/data#test-values)).

| Test value | Always returns | What it is for |
| --- | --- | --- |
| `maxCost: 0.0001` | Refuses with [`max-cost`](/code/refusals#max-cost) | Any priced call with a maxCost of 0.0001 is refused before it spends: the price is above the cap. |
| `rate-limited.test` | Refuses with [`rate-limited`](/code/refusals#rate-limited) | Any call naming this hostname answers 429 with Retry-After, so your retry logic runs. |
| `sources-busy.test` | Refuses with [`sources-busy`](/code/refusals#sources-busy) | Any call naming this hostname answers 503 as if every route were busy, and nothing is spent. |

## Used by
- **Jobs:** [Check AI answers](/capabilities/check-ai-answers) · [Earn first links](/capabilities/earn-first-links) · [Get indexed](/capabilities/get-indexed) · [Publish and post](/capabilities/publish-and-post) · [Research demand](/capabilities/research-demand) · [Size up competitors](/capabilities/size-up-competitors)

## Related
[`Monitor.check`](/code/data/monitors/check)