# Update a watch

> Change a watch cadence, extraction targets, webhook destination, or active state without replacing it.

`PATCH /v1/watches/:id` updates selected watch fields. Send only what changes.

<ParamField path="id" type="string" required>
  Watch id (`watch_...`).
</ParamField>

## Tenant scope

Writes are owner-scoped: another tenant's watch id is `404 WATCH_NOT_FOUND`, never `403`. Restricted (`sk_*`) keys always write to their own owner. Internal keys can act on behalf of one tenant for watch reads **and writes** with `?owner=<ownerId>` or `X-AISearch-On-Behalf-Of: <ownerId>`; the query parameter wins when both are present. A restricted key's override is ignored.

When an override is applied, the response adds a top-level `owner` field and an `X-AISearch-Owner` header containing the enforced owner id.

<ParamField query="owner" type="string">
  Internal keys only. Owner id to enforce for this write. Overrides the
  `X-AISearch-On-Behalf-Of` header when both are sent.
</ParamField>

## Body

Every body field is optional.

<ParamField body="name" type="string">
  New display name.
</ParamField>

<ParamField body="schedule" type="object">
  New cadence as `{ "every": "15m|30m|1h|6h|12h|1d|7d" }` or `{ "intervalMinutes": integer }`. The interval must meet the plan floor.
</ParamField>

<ParamField body="extract" type="boolean | object">
  New Auto Extract config. `true` and `{}` enable opportunistic extraction;
  `{ "targets": [...] }` pins up to 10 targets of at most 100 characters each;
  `false` disables extraction.
</ParamField>

<ParamField body="webhookUrl" type="string | null">
  New public HTTPS destination. Send `null` to remove the webhook.
</ParamField>

<ParamField body="status" type="string">
  `active` or `paused` only. Pausing sets `pausedReason: "user"`. Resuming from
  `paused` clears `pausedReason` and reschedules `nextRunAt` to `now +
  intervalMinutes`.
</ParamField>

<Warning>
  You cannot set `status: "canceled"` with `PATCH`. Use [`DELETE
  /v1/watches/:id`](/api-reference/watches/delete) to soft-cancel the watch.
</Warning>

## Request

<CodeGroup>

```bash cURL
curl -X PATCH https://api.aisearchapi.dev/v1/watches/watch_8f2c1a7e \
  -H "Authorization: Bearer $AISEARCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "schedule": { "every": "6h" },
    "extract": { "targets": ["HubSpot", "attio.com"] },
    "status": "active"
  }'
```

```javascript Node
const res = await fetch(
  'https://api.aisearchapi.dev/v1/watches/watch_8f2c1a7e',
  {
    method: 'PATCH',
    headers: {
      Authorization: `Bearer ${process.env.AISEARCH_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      schedule: { every: '6h' },
      extract: { targets: ['HubSpot', 'attio.com'] },
      status: 'active',
    }),
  },
);
const { watch } = await res.json();
```

```python Python
import os, requests

res = requests.patch(
    "https://api.aisearchapi.dev/v1/watches/watch_8f2c1a7e",
    headers={
        "Authorization": f"Bearer {os.environ['AISEARCH_API_KEY']}",
        "Content-Type": "application/json",
    },
    json={
        "schedule": {"every": "6h"},
        "extract": {"targets": ["HubSpot", "attio.com"]},
        "status": "active",
    },
)
watch = res.json()["watch"]
```

</CodeGroup>

## Response

```json 200 OK
{
  "watch": {
    "id": "watch_8f2c1a7e",
    "name": "Startup CRM monitor",
    "query": "best crm for startups",
    "surfaces": ["chatgpt", "perplexity"],
    "regions": [{ "country": "US" }],
    "extract": { "targets": ["HubSpot", "attio.com"] },
    "webhookUrl": "https://example.com/hooks/aisearch",
    "schedule": { "every": "6h", "intervalMinutes": 360 },
    "status": "active",
    "pausedReason": null,
    "nextRunAt": "2026-07-11T20:30:00Z",
    "lastRunAt": "2026-07-11T14:00:00Z",
    "lastJobId": "job_8t2q",
    "createdAt": "2026-07-01T14:00:00Z"
  }
}
```

<ResponseField name="watch" type="object">
  The complete public Watch after the update. The schedule always includes both
  `every` and source-of-truth `intervalMinutes`.
</ResponseField>

## Errors

<ResponseField name="400" type="VALIDATION_FAILED">
  An update field is invalid, including `status: "canceled"`, a malformed
  schedule, or an invalid extraction target.
</ResponseField>

<ResponseField name="404" type="WATCH_NOT_FOUND">
  No watch exists for the given id, or it belongs to another tenant.
</ResponseField>

<ResponseField name="422" type="WATCH_INTERVAL_TOO_SHORT">
  The new schedule is below the plan's minimum interval. The message names the
  floor and plan.
</ResponseField>

```json 422
{
  "code": "WATCH_INTERVAL_TOO_SHORT",
  "error": "minimum watch interval is 60 minutes on the starter plan",
  "request_id": "req_9f2c1a7e4b5d48f1a3c6e8d0b2a4f6c8",
  "docs_url": "https://docs.aisearchapi.dev/guides/errors#watch_interval_too_short"
}
```

## Related

<CardGroup cols={2}>
  <Card title="Watches" icon="satellite-dish" href="/guides/watches">
    Lifecycle, plan limits, billing, and run history.
  </Card>
  <Card title="Get a watch" icon="file-lines" href="/api-reference/watches/get">
    Read the current configuration and recent runs.
  </Card>
  <Card
    title="Delete a watch"
    icon="trash-can"
    href="/api-reference/watches/delete"
  >
    Soft-cancel a watch permanently.
  </Card>
  <Card title="Webhooks" icon="webhook" href="/guides/webhooks">
    Verify signed deliveries from watch runs.
  </Card>
</CardGroup>
