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

# Underwriting edits

> Update transaction tags, debt positions, funder matches, and analytics exclusions.

A read-write PAT can make the same stored underwriting edits available in the Pathway app. These routes update the canonical bank result inside the Book. The next `GET /books/{book_id}/analytics` call calculates from the new state immediately.

Fetch the current analytics after a mutation when your application needs the resulting true revenue, debt metrics, schedules, or screening decision.

## Update transaction tags

Get the current tag registry from:

```http theme={null}
GET /parse/tags
```

Then apply additions, removals, and replacements to any set of transaction IDs.

```http theme={null}
PATCH /books/{book_id}/transactions-v2/tags
Content-Type: application/json
```

<CodeGroup>
  ```python Python theme={null}
  response = requests.patch(
      f"{API_BASE}/books/{BOOK_ID}/transactions-v2/tags",
      headers={"Authorization": f"Bearer {TOKEN}"},
      json={
          "transaction_ids": [1021, 1022],
          "operation": {
              "add": ["payment_processor"],
              "remove": ["internal_transfer"],
              "replace": [],
          },
      },
      timeout=30,
  )
  response.raise_for_status()
  print(response.json())
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(
    `${API_BASE}/books/${BOOK_ID}/transactions-v2/tags`,
    {
      method: "PATCH",
      headers: {
        Authorization: `Bearer ${TOKEN}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        transaction_ids: [1021, 1022],
        operation: {
          add: ["payment_processor"],
          remove: ["internal_transfer"],
          replace: [],
        },
      }),
    },
  );

  if (!response.ok) {
    throw new Error(`${response.status}: ${await response.text()}`);
  }
  console.log(await response.json());
  ```

  ```bash cURL theme={null}
  curl --fail-with-body \
    -X PATCH \
    "https://api.lendpathway.com/api/books/{book_id}/transactions-v2/tags" \
    -H "Authorization: Bearer pat_your_token_here" \
    -H "Content-Type: application/json" \
    --data '{
      "transaction_ids": [1021, 1022],
      "operation": {
        "add": ["payment_processor"],
        "remove": ["internal_transfer"],
        "replace": []
      }
    }'
  ```
</CodeGroup>

```json theme={null}
{
  "message": "Tags updated",
  "transactions_updated": 2
}
```

Invalid values in `add` are ignored. A replacement only removes `old_tag` when `new_tag` is valid. Use the registry response instead of hard-coding the available set.

## Manage debt positions

A stored position groups transactions that belong to one debt relationship. Analytics enrich that stored group with payment schedules, episodes, remittance burden, misses, modifications, and status.

| Operation                       | Request                                                    |
| ------------------------------- | ---------------------------------------------------------- |
| Create a position               | `POST /books/{book_id}/positions`                          |
| Assign or unassign transactions | `PATCH /books/{book_id}/transactions-v2/position`          |
| Merge positions                 | `POST /books/{book_id}/positions/merge`                    |
| Match or clear a funder         | `PATCH /books/{book_id}/positions/{position_id}/funder`    |
| Change the loan type            | `PATCH /books/{book_id}/positions/{position_id}/loan-type` |
| Delete positions                | `DELETE /books/{book_id}/positions`                        |

### Create and populate a position

```python theme={null}
# Create the stored position.
response = requests.post(
    f"{API_BASE}/books/{BOOK_ID}/positions",
    headers={"Authorization": f"Bearer {TOKEN}"},
    json={
        "name": "OnDeck",
        "loan_type": "merchant_cash_advance",
        "funder_id": "a6f59ae3-f423-4acc-841d-1c73297f35e2",
    },
    timeout=30,
)
response.raise_for_status()

# Read the Book to obtain the new position_id.
book = requests.get(
    f"{API_BASE}/books/{BOOK_ID}",
    headers={"Authorization": f"Bearer {TOKEN}"},
    timeout=30,
).json()
meta = book["book_meta"]
plaid = meta.get("parser_v2_plaid_result") or {}
mca = plaid.get("holy_mca") or meta["parser_v2_mca_result"]
position_id = next(p["position_id"] for p in mca["positions"] if p["name"] == "OnDeck")

# Attach the funding and payment transactions.
response = requests.patch(
    f"{API_BASE}/books/{BOOK_ID}/transactions-v2/position",
    headers={"Authorization": f"Bearer {TOKEN}"},
    json={"position_id": position_id, "transaction_ids": [118, 142, 165]},
    timeout=30,
)
response.raise_for_status()
```

Send `position_id: null` to the assignment endpoint to remove the listed transactions from every position.

<Accordion title="Position request models">
  ```python theme={null}
  from pydantic import BaseModel, Field


  class CreatePosition(BaseModel):
      name: str
      loan_type: str = "merchant_cash_advance"
      funder_id: str | None = None


  class PositionAssignment(BaseModel):
      transaction_ids: list[int]
      position_id: str | None = None


  class MergePositions(BaseModel):
      position_ids: list[str]
      merged_name: str


  class UpdatePositionFunder(BaseModel):
      funder_id: str | None = None


  class MovePositionLoanType(BaseModel):
      new_loan_type: str


  class DeletePositions(BaseModel):
      position_ids: list[str]
      remove_tag: bool = True
  ```
</Accordion>

Changing a position's loan type also replaces the old loan tag on its assigned transactions. Deleting with `remove_tag: true` removes that position's loan tag from its transactions.

## Control analytics exclusions

```http theme={null}
PATCH /books/{book_id}/exclusions
Content-Type: application/json
```

Each supplied field replaces the complete stored list for that field. Omitted fields remain unchanged. Send an empty list to clear one.

```python theme={null}
response = requests.patch(
    f"{API_BASE}/books/{BOOK_ID}/exclusions",
    headers={"Authorization": f"Bearer {TOKEN}"},
    json={
        "excluded_document_ids": ["edb70371-5d2c-45f7-b3c1-c19cbf97d5e8"],
        "excluded_account_ids": [],
        "excluded_position_ids": ["74ce93ee-0127-4831-95ed-810333e41e34"],
        "revenue_exclusion_tags": ["internal_transfer", "merchant_cash_advance"],
    },
    timeout=30,
)
response.raise_for_status()

analytics = requests.get(
    f"{API_BASE}/books/{BOOK_ID}/analytics",
    headers={"Authorization": f"Bearer {TOKEN}"},
    timeout=60,
)
analytics.raise_for_status()
print(analytics.json()["true_revenue"])
```

```python theme={null}
class UpdateExclusions(BaseModel):
    excluded_document_ids: list[str] | None = None
    excluded_account_ids: list[int] | None = None
    excluded_position_ids: list[str] | None = None
    revenue_exclusion_tags: list[str] | None = None


class ExclusionsAck(BaseModel):
    message: str
    excluded_document_ids: list[str]
    excluded_account_ids: list[int]
    excluded_position_ids: list[str]
    revenue_exclusion_tags: list[str] | None = None
```

<Warning>
  A reparse replaces `book_meta`, including transaction tags, stored positions, and these exclusion lists. Apply edits after the parse you intend to keep.
</Warning>
