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

# Refresh and monitors

> "Run it again" is three different products with three different costs. The API names them separately instead of overloading one verb.

<Warning>
  **Refresh and monitors are not yet available — planned.**
  `POST /v1/sessions/{id}/refresh`, `POST /v1/extractions/{id}/refresh`, and the whole
  `/v1/monitors` surface are specced but **not yet served**: these routes answer
  `501 not_implemented`. The **re-reading** story below (result, events, rows — free, forever
  within retention) is live today; the refresh and monitor sections are the contract
  those surfaces ship against. The [changelog](/changelog) announces when they go live.
</Warning>

* **Re-reading** a finished run is free, forever within retention. `GET …/result`,
  `…/events`, `…/rows` never re-run and never re-bill. If your app re-opens
  yesterday's answer, nothing is fetched from any merchant.
* **Refreshing** gets fresh data for the same question at *marginal* cost — the run
  skips everything it already learned.
* **Monitoring** is a standing refresh on a cadence, with a webhook that carries the
  diff.

## Refresh a session — same question, fresh data

A session already holds what a cold run spends most of its budget discovering: the
parsed intent, the resolved merchants, the located catalog URLs, the per-source
receipts. A refresh re-passes **known sources only** — no parse, no resolve, no
locate, no triage:

```bash theme={null}
curl https://api.fiveninelabs.com/v1/sessions/sess_9m2kd/refresh \
  -H "Authorization: Bearer $FIVENINE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"max_spend_usd": 1, "max_age_s": 21600, "sources": ["endofanear.shop"]}'
```

| Field           | Required | Meaning                                                                                                     |
| --------------- | -------- | ----------------------------------------------------------------------------------------------------------- |
| `max_spend_usd` | yes      | This refresh run's own reservation and ceiling.                                                             |
| `max_age_s`     | no       | Sources whose rows are younger than this are served from the record at **\$0**; only stale sources re-pass. |
| `sources`       | no       | Restrict the refresh to these domains (sessions only).                                                      |

Cost ≈ the passes alone. Many "refreshes" are actually satisfied by recency — with
`max_age_s` set, those are free by construction. The refresh is a new run in the same
session: it reserves and settles its own budget, and streams like any other run.

## Refresh an extraction — same URL, with a diff

`POST /v1/extractions/{id}/refresh` creates a **new extraction** (fresh id,
`refresh_of` set) against the same url/entity/guidance:

```bash theme={null}
curl https://api.fiveninelabs.com/v1/extractions/ext_6b1st/refresh \
  -H "Authorization: Bearer $FIVENINE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"max_spend_usd": 0.5, "max_age_s": 21600}'
```

* Prior result younger than `max_age_s`? It returns immediately with the recorded
  rows at **\$0** — `"served_from": "record"`, stated on the object.
* Otherwise the pass re-runs, and the delivery carries a `diff` against the
  refreshed-from result:

```json theme={null}
{
  "id": "ext_9c4mm",
  "object": "extraction",
  "status": "delivered",
  "refresh_of": "ext_6b1st",
  "claim": "complete",
  "records": 221,
  "spend_usd": 0.31,
  "diff": { "added": 9, "removed": 2, "changed": 3 },
  "created_at": "2026-08-27T06:00:04Z"
}
```

Chains are first-class: refreshing a refresh diffs against the latest delivery.

## Monitors — a standing refresh

A monitor is exactly "refresh on a cadence, plus a webhook carrying the diff":

```bash theme={null}
curl https://api.fiveninelabs.com/v1/monitors \
  -H "Authorization: Bearer $FIVENINE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "name": "powells used poetry",
    "watch": { "extraction": "ext_6b1st" },
    "cadence_s": 86400,
    "max_spend_usd_per_run": 0.5,
    "webhook_url": "https://api.myapp.com/hooks/fivenine"
  }'
```

* `watch` names exactly one of `session` (refresh its known sources) or `extraction`
  (refresh the one URL).
* `cadence_s` minimum is **3600** — no sub-hourly monitors.
* Each firing is a refresh run, billed like one, capped by `max_spend_usd_per_run`.
* The monitor object states `worst_case_monthly_usd` **before you create it** — the
  cadence times the per-run cap, so standing spend is never a surprise.

The webhook events are `monitor.changed`, `monitor.unchanged` (opt-in — default is
change-only), and `monitor.failed`, carrying the diff, the run's cost, and the receipt
id — see [Webhooks](/guides/webhooks) for payloads and signing.

**Failure is bounded:** after 3 consecutive failed runs the monitor pauses itself and
sends a final `monitor.failed` with `"paused": true`. It never burns budget
indefinitely against a broken target.

Manage monitors with `GET /v1/monitors`, `PATCH /v1/monitors/{id}` (pause, resume,
retune cadence/cap/webhook), `DELETE /v1/monitors/{id}` (its runs remain readable),
and `GET /v1/monitors/{id}/runs` for the full firing history with diffs.

## What refresh does not promise

A refresh re-passes a known source; it does not (yet) replay the source's previously
discovered acquisition route. Repeat-source cost is therefore in the same range as the
original pass for stale sources — the savings today come from skipping
parse/resolve/locate/triage and from `max_age_s` serving recent data free. If refresh
economics change, that will appear in the [changelog](/changelog), not silently in
your ledger.
