> ## 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.

# POST /substrate/v1/action — execute a typed action

> Dispatch a typed action on a tracked process via its runtime probe, with optional verification and a before/after field delta in the response.

The `/action` endpoint lets you call any action listed in an application's state schema. NSP dispatches the call directly through the runtime probe for the target process. The response includes an optional post-action verification result and a state delta so you know exactly what changed.

## Endpoint

```http theme={null}
POST /substrate/v1/action
X-Substrate-Key: <key>
Content-Type: application/json
```

**Rate limit:** 10 requests per second. **Timeout:** 120 seconds (extended from the standard 30 s to accommodate cold probe attachment, post-action state settlement, and verification polling).

## Request Fields

<ParamField body="pid" type="integer">
  OS process ID of the target application. Provide either `pid` or `app_id` — the daemon resolves the other automatically.
</ParamField>

<ParamField body="app_id" type="string">
  Stable app identifier from `GET /apps`. Provide either `app_id` or `pid`.
</ParamField>

<ParamField body="action" type="string" required>
  Name of the action to execute, exactly as it appears in the `actions` map returned by `GET /state` or `GET /schema`.
</ParamField>

<ParamField body="parameters" type="object" required>
  Key-value map of action parameters matching the action's schema. Required parameters must be present; omitting optional parameters is allowed.
</ParamField>

<ParamField body="verify_expression" type="string">
  A JavaScript-style predicate evaluated against the post-action state, e.g. `"inbox.sent_count > 0"`. **Required** for actions with `reversibility: "irreversible_write"`. The daemon polls until the expression evaluates to `true` or `verify_timeout_ms` elapses. Maximum 2048 bytes.
</ParamField>

<ParamField body="verify_timeout_ms" type="integer" default="5000">
  How long to wait (in milliseconds) for `verify_expression` to become true. Accepted range: 100–30000 ms.
</ParamField>

<ParamField body="fresh_state" type="boolean" default="false">
  When `true`, the daemon captures a fresh state snapshot before dispatching the action. Use this if you need pre-action state in `state_delta` to reflect the very latest runtime values rather than the cached snapshot.
</ParamField>

## Response Fields

<ResponseField name="success" type="boolean" required>
  `true` if the action ran and — when a `verify_expression` was provided — the verification passed. `false` in all other cases.
</ResponseField>

<ResponseField name="action_id" type="string" required>
  UUIDv7 unique identifier for this execution. Use this for logging and correlation.
</ResponseField>

<ResponseField name="action" type="string" required>
  The action name that was executed.
</ResponseField>

<ResponseField name="executed" type="boolean" required>
  Whether the action itself was dispatched. This can be `false` if the action was rejected at the pre-flight safety gate before dispatch (e.g., missing `verify_expression` for an irreversible write).
</ResponseField>

<ResponseField name="runtime" type="string" required>
  The runtime used for execution (`v8`, `jvm`, `clr`, or `native`).
</ResponseField>

<ResponseField name="total_latency_ms" type="integer" required>
  Total wall time in milliseconds from receipt of the request to sending the response.
</ResponseField>

<ResponseField name="message" type="string" required>
  Human-readable status summary, e.g. `"Action executed successfully"` or `"Verification failed after 5000ms"`.
</ResponseField>

<ResponseField name="verification" type="VerificationOutcome | null">
  Post-action verification result. Present only when a `verify_expression` was provided.

  <Expandable title="VerificationOutcome fields">
    <ResponseField name="passed" type="boolean">
      Whether the expression evaluated to `true` before `verify_timeout_ms` elapsed.
    </ResponseField>

    <ResponseField name="expression" type="string">
      The expression that was evaluated.
    </ResponseField>

    <ResponseField name="computed_value" type="any">
      The final value the expression resolved to.
    </ResponseField>

    <ResponseField name="explanation" type="string">
      Human-readable summary of what changed, e.g. `"inbox.sent_count changed from 14 to 15"`.
    </ResponseField>

    <ResponseField name="latency_ms" type="integer">
      Time spent polling from action dispatch to expression passing or timeout.
    </ResponseField>

    <ResponseField name="poll_cycles" type="integer">
      Number of probe cycles evaluated during verification.
    </ResponseField>

    <ResponseField name="changed_fields" type="array">
      Array of `{ path, before, after }` objects listing every field that changed during verification.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="updated_state" type="SubstrateState | null">
  Full state snapshot captured after the action. Useful when you want the current state in one round-trip without a separate `GET /state` call.
</ResponseField>

<ResponseField name="state_delta" type="object | null">
  Field-level before/after diff of changed keys, e.g. `{ "inbox.sent_count": { "before": 14, "after": 15 } }`.
</ResponseField>

<ResponseField name="raw_return" type="any | null">
  The raw return value from the method invocation. Populated for JVM and CLR runtime actions; `null` for V8 browser actions.
</ResponseField>

## Action Safety Gates

NSP enforces three safety checks before dispatching any action:

**1. Irreversible Write Gate** — Actions with `reversibility: "irreversible_write"` (such as `send_reply` or `delete_email`) require a `verify_expression`. Requests without one are rejected with `400 missing_verify_expression`.

**2. Dangerous Name Heuristic** — If the PID cannot be resolved and the action name contains any of `delete`, `destroy`, `purge`, `wipe`, `reset`, `clear_all`, `terminate`, `shutdown`, or `kill`, the request is rejected with `400 dangerous_action_unverified`.

**3. Expression Size Limit** — `verify_expression` is capped at 2048 bytes.

## CDP Built-in Actions (V8 Apps)

For V8/browser apps, NSP exposes a set of built-in actions that map directly to Chrome DevTools Protocol commands:

| Action Name | Parameters         | Description                                                     |
| ----------- | ------------------ | --------------------------------------------------------------- |
| `click`     | `x`, `y`           | Click at pixel coordinates via `Input.dispatchMouseEvent`       |
| `type`      | `text`             | Type text into the focused element via `Input.dispatchKeyEvent` |
| `navigate`  | `url`              | Navigate to a URL via `Page.navigate`                           |
| `clear`     | `selector`         | Clear an input field                                            |
| `focus`     | `selector`         | Focus a DOM element                                             |
| `scroll`    | `x`, `y`, `deltaY` | Scroll at a position                                            |

## Examples

<CodeGroup>
  ```bash curl — reversible action theme={null}
  curl -X POST http://127.0.0.1:7842/substrate/v1/action \
    -H "X-Substrate-Key: sk_nsp_..." \
    -H "Content-Type: application/json" \
    -d '{
      "app_id": "gmail-chrome-12847",
      "action": "archive",
      "parameters": { "email_id": "msg_18821af3" }
    }'
  ```

  ```bash curl — irreversible action with verify theme={null}
  curl -X POST http://127.0.0.1:7842/substrate/v1/action \
    -H "X-Substrate-Key: sk_nsp_..." \
    -H "Content-Type: application/json" \
    -d '{
      "pid": 12847,
      "action": "send_reply",
      "parameters": {
        "thread_id": "thread_7af2b3",
        "body": "Thank you for reaching out."
      },
      "verify_expression": "inbox.sent_count > 0",
      "verify_timeout_ms": 5000
    }'
  ```

  ```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")

  # Reversible action — no verify required.
  result = await session.execute("archive", parameters={"email_id": "msg_18821af3"})
  print(result.success)         # True
  print(result.total_latency_ms)

  # Irreversible action — verify_expression required.
  result = await session.execute(
      "send_reply",
      parameters={
          "thread_id": "thread_7af2b3",
          "body": "Thank you for reaching out.",
      },
      verify_expression="inbox.sent_count > 0",
      verify_timeout_ms=5000,
  )
  print(f"Success: {result.success}, verified: {result.verification.passed}")
  print(result.verification.explanation)
  ```
</CodeGroup>

### Example Response

```json theme={null}
{
  "success": true,
  "action_id": "01j5abc123def456",
  "action": "send_reply",
  "executed": true,
  "runtime": "v8",
  "total_latency_ms": 312,
  "message": "Action executed successfully",
  "verification": {
    "passed": true,
    "expression": "inbox.sent_count > 0",
    "computed_value": true,
    "explanation": "inbox.sent_count changed from 14 to 15",
    "latency_ms": 280,
    "poll_cycles": 2,
    "changed_fields": [
      { "path": "inbox.sent_count", "before": 14, "after": 15 },
      { "path": "inbox.emails[0].is_read", "before": false, "after": true }
    ]
  },
  "state_delta": {
    "inbox.sent_count": { "before": 14, "after": 15 }
  },
  "raw_return": null
}
```

## Error Responses

| Status | Code                          | Description                                                                                            |
| ------ | ----------------------------- | ------------------------------------------------------------------------------------------------------ |
| `400`  | `missing_verify_expression`   | Action has `reversibility: "irreversible_write"` but no `verify_expression` was provided               |
| `400`  | `verify_expression_too_large` | `verify_expression` exceeds 2048 bytes                                                                 |
| `400`  | `dangerous_action_unverified` | Action name matches a dangerous keyword pattern and PID could not be resolved                          |
| `401`  | `unauthorized`                | Missing or invalid `X-Substrate-Key` header                                                            |
| `404`  | `pid_not_found`               | Neither `pid` nor `app_id` resolves to a tracked process                                               |
| `422`  | `verification_failed`         | The action executed but the `verify_expression` did not become true before `verify_timeout_ms` elapsed |
| `429`  | `rate_limited`                | Exceeded the 10 actions/second rate limit                                                              |
| `503`  | `confidence_too_low`          | State confidence is below the minimum threshold required for action dispatch                           |
| `504`  | `action_timeout`              | The action did not complete within the 120-second timeout                                              |
