---
id: watch
slug: /apis/watch
title: 'Watch (api/watch)'
description: 'Subscribe the calling member to an asset, check whether they are watching it, and list everything they watch.'
sidebar_label: 'Watch (api/watch)'
sidebar_position: 13
tags: ['api', 'watch', 'subscriptions']
---

Manages the calling member's asset subscriptions — "watching" an asset so they are notified
about it.

Everything here is scoped to **the authenticated member**. There is no parameter for acting on
another member's watches, and the list endpoint returns only your own.

:::info Not the same thing as webhooks

`api/watch` is per-member notification within Agility. [Webhooks](/apis/webhooks) are
instance-level HTTP callbacks to an external system. They share the word "subscription" and
nothing else.

:::

All routes require authentication.

## Subscribe or Unsubscribe

```http
POST ~/api/watch
Content-Type: application/json

{ "oid": "Story:1234", "action": "Subscribe" }
```

| Field | Required | Description |
| --- | --- | --- |
| `oid` | yes | OID token of the asset to watch or unwatch |
| `action` | yes | `Subscribe` or `Unsubscribe` |

`action` is matched **case-insensitively**, so `subscribe`, `Subscribe` and `SUBSCRIBE` all
work.

### Response

```json
{ "oid": "Story:1234", "isWatching": true, "action": "subscribe" }
```

Note the echoed `action` comes back **lower-case** regardless of how you sent it. Do not
round-trip it into a case-sensitive comparison.

`isWatching` is `true` after a subscribe and `false` after an unsubscribe.

### Errors

All are `400`. The validation **wording** varies between versions — key on the
status code and the missing field, not on the message text:

| Body | Cause |
| --- | --- |
| `"Missing request body"` | No body sent |
| A ModelState object | `oid` or `action` missing — both are `[Required]` |
| `"Invalid action"` | `action` is null, or is neither `Subscribe` nor `Unsubscribe` |

## Check One Asset

```http
GET ~/api/watch?oid=Story:1234
```

```json
{ "oid": "Story:1234", "isWatching": false }
```

Returns `400` with `"Missing oid"` when the parameter is present but blank
(`?oid=`). Omitting the parameter entirely returns `405` instead — a GET with
no query routes against the POST-only subscribe endpoint.

## List Everything Watched

```http
GET ~/api/watch/list
```

```json
{ "watchedOids": ["Story:1234", "Defect:5678"] }
```

The watched OIDs for the calling member. Note the array is wrapped in an object rather than
returned bare.


## What a Subscribe Actually Creates

A watch is not hidden state — it is ordinary assets, and `api/watch` is a
convenience that assembles them so you don't have to know the machinery. A
subscribe creates:

- a **`Subscription`** — `Name` is a generated title ("Watching Logon
  (S-01001)"), `EventDefinition` is the watched type's changed event
  (`Story.Changed`), `Subscriber` is the calling member;
- a **`SubscriptionTerm`** in its `FilterTerms` — `AttributeToken: "ID"`,
  `Value` the watched OID — created *in the subscription's context* so its
  required pieces default correctly.

Both are queryable:

```json
{
  "from": "Subscription",
  "select": ["Name", "EventDefinition", "FilterTerms.AttributeToken", "FilterTerms.Value"],
  "where": { "Subscriber": "Member:20" },
  "page": { "size": 50, "start": 0 }
}
```

Unsubscribing removes them again.

Two consequences worth knowing:

- Watches are the narrow case of a wider model: the application's
  subscription editor composes `SubscriptionTerm`s over attributes such as
  `Scope.ParentMeAndUp`, `Owners`, `Status` and `Team` against any event
  definition — richer subscriptions than a single-asset watch.
- Building the same assets by hand through the Bulk API needs the term
  created in its subscription's context — the
  [`#ContextOid`](/apis/asset-bulk/aliases-and-projection) initialization —
  which is exactly the machinery `api/watch` exists to spare you.

## Related

- [Webhooks](/apis/webhooks) — external HTTP callbacks, a different mechanism
- [Activity stream](/apis/activity-stream) — what changed on an asset
