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

# GET /substrate/v1/diff/{pid} — state diff since capture

> Retrieve the exact fields that changed, were added, or were removed between two consecutive state captures for a tracked process, keyed by capture ID.

The `/diff` endpoint returns a field-level comparison between two consecutive state snapshots for a tracked process. Rather than fetching the entire `objects` map and diffing it yourself, you provide a `capture_id` from a previous snapshot and the daemon returns only the keys that changed. This is the most efficient way to understand what an action did, why a `/watch` event fired, or how a process's state evolved over a time interval.

## Endpoint

```http theme={null}
GET /substrate/v1/diff/{pid}?since={capture_id}
X-Substrate-Key: <key>
```

**Rate limit:** 100 requests per second.

## Parameters

<ParamField path="pid" type="integer" required>
  The OS process ID of the target application. Obtain this from `GET /apps`.
</ParamField>

<ParamField query="since" type="string" required>
  A `capture_id` (UUIDv7) from a previous `GET /state` response or a `StateChangeNotification` message from `WS /watch`. The diff is computed between this capture and the most recent successful capture.
</ParamField>

## Response Fields

<ResponseField name="pid" type="integer" required>
  The OS process ID that was queried.
</ResponseField>

<ResponseField name="app_id" type="string" required>
  Stable semantic identifier for the application.
</ResponseField>

<ResponseField name="capture_id" type="string" required>
  UUIDv7 of the most recent (current) state snapshot — the "after" side of the diff.
</ResponseField>

<ResponseField name="prev_capture_id" type="string" required>
  UUIDv7 of the earlier snapshot — the "before" side of the diff. This matches the `since` query parameter you passed.
</ResponseField>

<ResponseField name="changed" type="array" required>
  Array of fields that existed in both snapshots but whose values differ. Each entry is a `{ path, before, after }` object.

  <Expandable title="changed entry fields">
    <ResponseField name="path" type="string">
      Dot-notation key of the changed field, e.g. `"inbox.unread_count"`.
    </ResponseField>

    <ResponseField name="before" type="any">
      The field's value in the earlier snapshot.
    </ResponseField>

    <ResponseField name="after" type="any">
      The field's value in the current snapshot.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="added" type="array" required>
  Array of `{ path, value }` objects for keys that are present in the current snapshot but were absent in the earlier one. This happens when a new application view loads and exposes state that wasn't visible before.
</ResponseField>

<ResponseField name="removed" type="array" required>
  Array of `{ path, value }` objects for keys that existed in the earlier snapshot but are absent in the current one. This happens when the user navigates away from a view or the application unloads a data structure.
</ResponseField>

## Examples

<CodeGroup>
  ```bash curl theme={null}
  # Step 1: Capture a baseline state and save its capture_id.
  CAPTURE_ID=$(curl -s \
    -H "X-Substrate-Key: sk_nsp_..." \
    http://127.0.0.1:7842/substrate/v1/state/12847 \
    | jq -r '.capture_id')

  # Step 2: Execute an action or wait for a change.
  # Step 3: Fetch the diff since the baseline.
  curl -H "X-Substrate-Key: sk_nsp_..." \
       "http://127.0.0.1:7842/substrate/v1/diff/12847?since=${CAPTURE_ID}"
  ```

  ```python Python SDK theme={null}
  from nelieo import NspClient

  client = NspClient(api_key="sk_nsp_...", base_url="http://127.0.0.1:7842")
  session = await client.attach("Gmail")

  # Capture the pre-action state.
  before = await session.refresh()
  baseline_id = before.capture_id

  # Execute the action.
  await session.execute("mark_as_read", parameters={"email_id": "msg_18821af3"})

  # Diff what changed.
  diff = await session.diff(since=baseline_id)

  for change in diff.changed:
      print(f"Changed  {change.path}: {change.before!r} → {change.after!r}")

  for entry in diff.added:
      print(f"Added    {entry['path']}: {entry['value']!r}")

  for entry in diff.removed:
      print(f"Removed  {entry['path']}: {entry['value']!r}")
  ```
</CodeGroup>

### Example Response

```json theme={null}
{
  "pid": 12847,
  "app_id": "gmail-chrome-12847",
  "capture_id": "0196f4a3-1c2d-7e00-9f3a-0b1c2d3e4f50",
  "prev_capture_id": "0196f4a2-3b5c-7d00-8e1f-9a0b2c3d4e5f",
  "changed": [
    {
      "path": "inbox.unread_count",
      "before": 48,
      "after": 47
    },
    {
      "path": "inbox.emails[0].is_read",
      "before": false,
      "after": true
    }
  ],
  "added": [],
  "removed": []
}
```

## Common Patterns

**Verifying an action had the expected effect** — Call `GET /state` before dispatching an action to save `capture_id`, run the action, then call `/diff?since=<capture_id>` to confirm exactly which fields changed.

**Processing `/watch` events efficiently** — When a `StateChangeNotification` arrives, the `prev_capture_id` field is already included. Pass it to `/diff` to get the full before/after values without re-reading the whole state.

**Auditing state over time** — Chain consecutive `capture_id` values to reconstruct a complete audit trail of field-level changes across multiple probe cycles.

## Notes

* Returns `{ "changed": [], "added": [], "removed": [] }` if state has not changed between the two captures.
* Returns `404` if the `since` capture ID is no longer in the daemon's capture history window. The daemon retains a fixed number of recent captures per PID; if the requested capture has been evicted, re-capture a fresh baseline via `GET /state`.
* The `since` parameter must correspond to a capture of the same PID. Cross-PID diff lookups return `400 Bad Request`.
