> ## 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/state/{pid} — read process state

> Fetch the semantic state snapshot for a process — its objects map, action schemas, navigational context, and probe metadata. Cached or fresh on demand.

The `/state` endpoint is the primary way to read what an application is doing right now. It returns a `SubstrateState` — a flat JSON document containing every semantic state key the probe has extracted from the process's runtime heap, along with the full action schema, navigational context, and probe diagnostics. You'll call this endpoint before planning an agent action, after executing one, or any time you need the freshest possible view of a process.

## Endpoint

```http theme={null}
GET /substrate/v1/state/{pid}?fresh=false
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. Get this value from the `pid` field in `GET /apps`.
</ParamField>

<ParamField query="fresh" type="boolean" default="false">
  When `false` (the default), the daemon returns the most recently cached state snapshot immediately — typically under 5 ms. When `true`, the daemon triggers a new probe cycle before responding. Fresh probes add 50 ms to 90 s of latency depending on the runtime and application size. Use `fresh=true` when you need to confirm the current state before a high-stakes action, or after an action has just completed.
</ParamField>

## Response Fields

The response is a flat JSON object. All `SubstrateState` fields are at the top level — there is no wrapper envelope.

<ResponseField name="capture_id" type="string" required>
  A UUIDv7 (time-ordered) identifier for this exact state snapshot. Use this value as the `since` parameter in `GET /diff/{pid}` to compute what changed between two captures.
</ResponseField>

<ResponseField name="app_id" type="string" required>
  Stable semantic identifier for the application, e.g. `"gmail-chrome-12847"`.
</ResponseField>

<ResponseField name="app_name" type="string" required>
  Human-readable application name.
</ResponseField>

<ResponseField name="pid" type="integer" required>
  OS process ID at the time of capture. If the process has restarted since the capture, this may differ from the PID you requested.
</ResponseField>

<ResponseField name="runtime" type="string" required>
  Runtime kind: `v8`, `jvm`, `clr`, or `native`.
</ResponseField>

<ResponseField name="schema_version" type="string" required>
  Semantic schema version in semver format, e.g. `"1.4.2"`. The schema version increments whenever the semantic engine updates its labeling model for this application, either automatically or via `POST /learn/{pid}`.
</ResponseField>

<ResponseField name="captured_at" type="datetime" required>
  ISO 8601 timestamp of when this snapshot was taken.
</ResponseField>

<ResponseField name="confidence" type="float" required>
  Overall probe quality score from `0.0` to `1.0`. Values above `0.90` indicate reliable semantic labeling. If confidence is low, read the `probe_metadata` fields to diagnose which tier was used and whether the heap was partially filtered.
</ResponseField>

<ResponseField name="context" type="AppContext" required>
  Navigational context for the application.

  <Expandable title="AppContext fields">
    <ResponseField name="url" type="string | null">
      Current URL for V8/browser apps.
    </ResponseField>

    <ResponseField name="location" type="string | null">
      URL pathname component, e.g. `"/inbox"`.
    </ResponseField>

    <ResponseField name="view" type="string | null">
      Semantic view identifier, e.g. `"gmail_inbox"`. Derived by the semantic engine from the URL and visible DOM structure.
    </ResponseField>

    <ResponseField name="window_title" type="string | null">
      OS window title at the time of capture.
    </ResponseField>

    <ResponseField name="window_id" type="string | null">
      Internal CDP tab or window identifier. Useful for multi-tab agents.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="objects" type="object" required>
  A flat map of dot-notation state keys to their current values. Keys use dot-notation for nested fields and bracket notation for array indices, e.g. `"inbox.emails[0].subject"`. Values are primitives: strings, numbers, booleans, or `null`. This is the primary data structure your agent reads and reasons over.
</ResponseField>

<ResponseField name="actions" type="object" required>
  A map of action names to their `ActionSchema` definitions. Each entry describes how to call the action. See `GET /schema/{pid}` for the full field list with documentation.
</ResponseField>

<ResponseField name="probe_metadata" type="object" required>
  Internal probe diagnostics. Useful for performance tuning and debugging.

  <Expandable title="probe_metadata fields">
    <ResponseField name="capture_latency_ms" type="integer">
      Time in milliseconds to read raw data from the runtime.
    </ResponseField>

    <ResponseField name="labeling_latency_ms" type="integer">
      Time in milliseconds for the semantic engine to label the raw objects.
    </ResponseField>

    <ResponseField name="total_latency_ms" type="integer">
      End-to-end wall time for the probe cycle in milliseconds.
    </ResponseField>

    <ResponseField name="raw_object_count" type="integer">
      Number of objects extracted from the runtime before semantic filtering.
    </ResponseField>

    <ResponseField name="filtered_object_count" type="integer">
      Number of objects remaining after relevance filtering. This matches the number of keys in `objects`.
    </ResponseField>

    <ResponseField name="cache_hit" type="boolean">
      Whether the response was served from the daemon's in-memory cache (`true`) or produced by a live probe cycle (`false`).
    </ResponseField>

    <ResponseField name="dom_tier_used" type="boolean">
      Whether the DOM snapshot tier was used as a fallback for this capture (V8 apps only).
    </ResponseField>

    <ResponseField name="probe_tier" type="string">
      The probe strategy used. One of `aso_fast`, `heap_snapshot`, `jvmti`, or `clr_mmf`.
    </ResponseField>
  </Expandable>
</ResponseField>

## Examples

<CodeGroup>
  ```bash curl — cached (fast) theme={null}
  curl -H "X-Substrate-Key: sk_nsp_..." \
       http://127.0.0.1:7842/substrate/v1/state/12847
  ```

  ```bash curl — fresh probe theme={null}
  curl -H "X-Substrate-Key: sk_nsp_..." \
       "http://127.0.0.1:7842/substrate/v1/state/12847?fresh=true"
  ```

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

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

  # Cached read — fast, suitable for most agent loops.
  state = await client.get_state(12847)
  print(state.confidence)
  print(state.objects.get("inbox.unread_count"))

  # Fresh read — triggers a new probe cycle.
  state = await client.get_state(12847, fresh=True)

  # Via a session attached to an app name.
  session = await client.attach("Gmail")
  state = await session.refresh()
  print(state.context.view)  # "gmail_inbox"
  ```
</CodeGroup>

### Example Response

```json theme={null}
{
  "capture_id": "0196f4a2-3b5c-7d00-8e1f-9a0b2c3d4e5f",
  "app_id": "gmail-chrome-12847",
  "app_name": "Gmail",
  "pid": 12847,
  "runtime": "v8",
  "schema_version": "1.4.2",
  "captured_at": "2026-07-20T13:00:00Z",
  "confidence": 0.97,
  "context": {
    "url": "https://mail.google.com/mail/u/0/#inbox",
    "location": "/inbox",
    "view": "gmail_inbox",
    "window_title": "Inbox (47) - Gmail",
    "window_id": "tab_8821af3"
  },
  "objects": {
    "inbox.unread_count": 47,
    "inbox.total_count": 1284,
    "inbox.emails[0].id": "msg_18821af3",
    "inbox.emails[0].from": "alice@example.com",
    "inbox.emails[0].subject": "Q3 Performance Report",
    "inbox.emails[0].is_read": false,
    "inbox.emails[0].has_attachments": true
  },
  "actions": {
    "send_reply": {
      "signature": "fn(thread_id: string, body: string, cc?: string[]) -> bool",
      "reversibility": "irreversible_write",
      "description": "Send a reply to an email thread",
      "confidence": 0.99
    },
    "archive": {
      "signature": "fn(email_id: string) -> bool",
      "reversibility": "reversible_write",
      "description": "Move an email to the archive",
      "confidence": 0.99
    }
  },
  "probe_metadata": {
    "capture_latency_ms": 28,
    "labeling_latency_ms": 12,
    "total_latency_ms": 51,
    "raw_object_count": 8472,
    "filtered_object_count": 23,
    "cache_hit": true,
    "dom_tier_used": false,
    "probe_tier": "aso_fast"
  }
}
```

## Error Responses

| Status | Code              | Description                                                                                                                                |
| ------ | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `404`  | `not_found`       | The PID is not tracked. Verify the process is running and check `GET /apps`.                                                               |
| `503`  | `probe_not_ready` | The cold probe for this process has not yet completed. Poll `GET /apps` until `has_state` is `true`.                                       |
| `504`  | `probe_timeout`   | A `fresh=true` probe cycle exceeded the timeout. The process may be under heavy load. Retry with `fresh=false` to get the cached snapshot. |
