# List watch runs

> Page through every scheduled attempt for a watch and inspect dispatched or skipped outcomes.

`GET /v1/watches/:id/runs` returns the watch's run history, newest first, with cursor pagination.

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

## Tenant scope

Reads are owner-scoped: another tenant's watch id is `404 WATCH_NOT_FOUND`, never `403`. Restricted (`sk_*`) keys always read 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.

## Query parameters

<ParamField query="limit" type="integer" default="20">
  Page size, `1`–`100`.
</ParamField>

<ParamField query="cursor" type="string">
  Opaque cursor from a previous page's `nextCursor`. Omit for the first page.
</ParamField>

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

## Request

<CodeGroup>

```bash cURL
curl "https://api.aisearchapi.dev/v1/watches/watch_8f2c1a7e/runs?limit=20" \
  -H "Authorization: Bearer $AISEARCH_API_KEY"
```

```javascript Node
const res = await fetch(
  'https://api.aisearchapi.dev/v1/watches/watch_8f2c1a7e/runs?limit=20',
  { headers: { Authorization: `Bearer ${process.env.AISEARCH_API_KEY}` } },
);
const page = await res.json();
```

```python Python
import os, requests

res = requests.get(
    "https://api.aisearchapi.dev/v1/watches/watch_8f2c1a7e/runs",
    params={"limit": 20},
    headers={"Authorization": f"Bearer {os.environ['AISEARCH_API_KEY']}"},
)
page = res.json()
```

</CodeGroup>

## Response

```json 200 OK
{
  "items": [
    {
      "id": "wrun_2b7d4e9a",
      "watchId": "watch_8f2c1a7e",
      "jobId": "job_8t2q",
      "scheduledFor": "2026-07-11T14:00:00Z",
      "status": "dispatched",
      "detail": "search job dispatched",
      "createdAt": "2026-07-11T14:00:01Z"
    },
    {
      "id": "wrun_6c3a1f8d",
      "watchId": "watch_8f2c1a7e",
      "jobId": null,
      "scheduledFor": "2026-07-10T14:00:00Z",
      "status": "skipped_credits",
      "detail": "insufficient credits at dispatch time",
      "createdAt": "2026-07-10T14:00:01Z"
    }
  ],
  "nextCursor": null
}
```

<ResponseField name="items" type="object[]">
  Scheduled attempts, newest first.

  <Expandable title="item">
    <ResponseField name="id" type="string">Run id (`wrun_...`).</ResponseField>
    <ResponseField name="watchId" type="string">Owning watch id.</ResponseField>
    <ResponseField name="jobId" type="string | null">Created parent job, or `null` when skipped.</ResponseField>
    <ResponseField name="scheduledFor" type="string">Tick this run represents, ISO-8601.</ResponseField>
    <ResponseField name="status" type="string">Run outcome.</ResponseField>
    <ResponseField name="detail" type="string">Human-readable elaboration.</ResponseField>
    <ResponseField name="createdAt" type="string">Run-row creation time, ISO-8601.</ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="nextCursor" type="string | null">
  Pass as `?cursor=` to fetch the next page. `null` on the last page.
</ResponseField>

## Run statuses

| Status            | Meaning                                                                                                          |
| ----------------- | ---------------------------------------------------------------------------------------------------------------- |
| `dispatched`      | A search job was created and `jobId` is set. Read it like any other async job.                                   |
| `skipped_credits` | Balance was insufficient at dispatch time; nothing was billed. Three consecutive skips pause the watch.          |
| `skipped_limit`   | An account or plan ceiling prevented dispatch; nothing was billed.                                               |
| `failed`          | The scheduled attempt failed to submit. A captured-but-empty or failed job is still `dispatched` with a `jobId`. |

## Errors

<ResponseField name="404" type="WATCH_NOT_FOUND">
  No watch exists for the given id, or it belongs to another tenant. The API
  does not expose another tenant's watch or run history.
</ResponseField>

```json 404
{
  "code": "WATCH_NOT_FOUND",
  "error": "no watch with id 'watch_8f2c1a7e'",
  "request_id": "req_9f2c1a7e4b5d48f1a3c6e8d0b2a4f6c8",
  "docs_url": "https://docs.aisearchapi.dev/guides/errors#watch_not_found"
}
```

## Related

<CardGroup cols={2}>
  <Card title="Watches" icon="satellite-dish" href="/guides/watches">
    Scheduling, lifecycle, billing, and run outcomes.
  </Card>
  <Card title="Get a watch" icon="file-lines" href="/api-reference/watches/get">
    Read a watch with its ten newest runs.
  </Card>
  <Card
    title="Read a job"
    icon="magnifying-glass"
    href="/api-reference/get-job"
  >
    Inspect a dispatched run's parent job.
  </Card>
  <Card title="Webhooks" icon="webhook" href="/guides/webhooks">
    Receive signed deliveries from dispatched runs.
  </Card>
</CardGroup>
