> ## 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 /learn/{pid} — submit semantic label corrections

> Submit semantic label corrections for a tracked application to fix unknown or mislabeled state keys, raising confidence scores on subsequent probe cycles.

The `/learn` endpoint lets you feed corrections back into the NSP semantic engine. When the engine mislabels a state key — or surfaces it as an opaque `unknown_0x...` identifier — you can submit the correct label, type, and an optional note explaining the mapping. The daemon stores each correction in the per-application schema registry and applies it starting with the next probe cycle. Over time, a well-corrected schema reaches near-perfect confidence and produces cleaner, more predictable state maps for your agents.

## Endpoint

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

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

## Parameters

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

## Request Fields

<ParamField body="capture_id" type="string" required>
  The `capture_id` of the state snapshot on which you observed the keys you are correcting. This ties each correction to a specific point in time, which helps the semantic engine understand the runtime context of the correction.
</ParamField>

<ParamField body="corrections" type="array" required>
  Array of label correction objects. Each object describes one key mapping to fix.

  <Expandable title="Correction object fields">
    <ParamField body="corrections[].observed_key" type="string" required>
      The key as it currently appears in the `objects` map — this may be an opaque identifier like `"unknown_0x4a2f"`, a misnamed label like `"mail.count_b"`, or a correctly named but wrongly typed key.
    </ParamField>

    <ParamField body="corrections[].correct_label" type="string" required>
      The correct dot-notation label you want this key to have in future snapshots, e.g. `"inbox.unread_count"`. Use the same naming conventions as the existing keys in the `objects` map.
    </ParamField>

    <ParamField body="corrections[].correct_type" type="string" required>
      The correct JSON type for this field. One of: `"number"`, `"string"`, `"boolean"`, `"object"`, or `"array"`.
    </ParamField>

    <ParamField body="corrections[].confidence" type="float">
      Your confidence that this correction is accurate, from `0.0` to `1.0`. The semantic engine weights corrections by this value when merging them with its internal model. Defaults to `1.0` if omitted.
    </ParamField>

    <ParamField body="corrections[].notes" type="string">
      Optional free-text description of why this correction is correct, e.g. `"This field always matches the unread badge count visible in the browser tab title"`. Notes are stored in the schema registry and visible in the developer console.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="feedback_type" type="string" default="label_correction">
  Category of feedback being submitted. One of:

  | Value               | Meaning                                                              |
  | ------------------- | -------------------------------------------------------------------- |
  | `label_correction`  | Relabeling an existing or unknown key                                |
  | `type_correction`   | Fixing the inferred type of a correctly labeled key                  |
  | `action_correction` | Correcting the mapping of an action to its underlying runtime method |
</ParamField>

<ParamField body="schema_version" type="string" required>
  The `schema_version` value from the `SubstrateState` that contained the keys you are correcting. This prevents stale corrections from being applied on top of a newer schema version.
</ParamField>

## Response Fields

<ResponseField name="accepted" type="integer" required>
  Number of corrections successfully applied to the schema registry.
</ResponseField>

<ResponseField name="rejected" type="integer" required>
  Number of corrections rejected. Corrections are rejected when the `observed_key` no longer exists in the current schema, the `schema_version` does not match the current version, or the `correct_label` conflicts with an already-confirmed key.
</ResponseField>

<ResponseField name="new_schema_version" type="string" required>
  The schema version after applying all accepted corrections. If any corrections were accepted, this will be higher than the version you submitted.
</ResponseField>

<ResponseField name="message" type="string" required>
  Human-readable summary of the result, e.g. `"2 corrections applied. Schema updated to 1.4.4."`.
</ResponseField>

## Examples

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST http://127.0.0.1:7842/substrate/v1/learn/12847 \
    -H "X-Substrate-Key: sk_nsp_..." \
    -H "Content-Type: application/json" \
    -d '{
      "capture_id": "0196f4a2-3b5c-7d00-8e1f-9a0b2c3d4e5f",
      "schema_version": "1.4.2",
      "feedback_type": "label_correction",
      "corrections": [
        {
          "observed_key": "unknown_0x4a2f",
          "correct_label": "inbox.unread_count",
          "correct_type": "number",
          "confidence": 0.95,
          "notes": "This field always matches the unread badge count in the tab title"
        },
        {
          "observed_key": "mail.count_b",
          "correct_label": "inbox.total_count",
          "correct_type": "number",
          "confidence": 0.90
        }
      ]
    }'
  ```

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

  state = await session.refresh()

  result = await client.learn(
      pid=session.pid,
      capture_id=state.capture_id,
      schema_version=state.schema_version,
      feedback_type="label_correction",
      corrections=[
          {
              "observed_key": "unknown_0x4a2f",
              "correct_label": "inbox.unread_count",
              "correct_type": "number",
              "confidence": 0.95,
              "notes": "Matches the unread badge count visible in the tab title",
          },
      ],
  )

  print(f"Accepted: {result.accepted}")
  print(f"New schema version: {result.new_schema_version}")
  ```
</CodeGroup>

### Example Response

```json theme={null}
{
  "accepted": 2,
  "rejected": 0,
  "new_schema_version": "1.4.4",
  "message": "2 corrections applied. Schema updated to 1.4.4."
}
```

## When to Use This Endpoint

**Unknown keys** — The semantic engine surfaces opaque identifiers like `unknown_0x4a2f` when it cannot confidently label a heap object. If you can identify what the field represents by correlating it with visible application data, submit a correction to name it permanently.

**Type mismatches** — If a field you expect to be a number is typed as a string (or vice versa), submit a `type_correction`. Wrong types break numeric comparison expressions in `/verify` and `/action`.

**Action mapping errors** — If an action in the schema is triggering the wrong runtime method, submit an `action_correction` with the correct method binding.

**Improving confidence** — A confidence score below `0.85` on `GET /apps` indicates the semantic engine is uncertain about some labels. Submitting corrections for the uncertain keys is the fastest way to raise confidence to `>0.95`.

<Note>
  Corrections take effect on the next probe cycle, which runs within 2 seconds by default. The schema version is bumped on every accepted correction — update any cached schema references in your agent after calling `/learn`.
</Note>
