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

# Subledgers

> Tenant API direct-mutation endpoints for subledger rows — reject and per-cell edit. Reserved for ops / legacy callers; HITL UI flows should go via Temporal signals.

Two direct HTTP endpoints for subledger row mutations. They exist for ops tooling and legacy callers; **interactive Tenant UI** edits for HITL tables should go through workflow signals (`POST /tasks/:taskId/row-action`) so Temporal stays the source of truth — see [UI and Temporal signals](/sdk/ui-and-temporal-signals).

`:ledger` is the registered subledger name (`expenses`, `journal_proposals`, runbook-owned types). `:rowId` is a UUID — the controller validates this with a regex check before dispatching.

## Reject a row

```http theme={null}
POST /v1/subledgers/:ledger/:rowId/reject
```

Apply the per-ledger reject rule. For `expenses`, only rows in `NEEDS_ATTENTION` are rejectable via this endpoint (see [`expenses_mutations.can_reject`](/sdk/subledgers/types/expenses#mutation-helpers--ntrosubledgertypesexpenses_mutations)). Other types may carry their own reject rules.

**Path params**

| Param    | Notes                             |
| -------- | --------------------------------- |
| `ledger` | Subledger name (e.g. `expenses`). |
| `rowId`  | Subledger row UUID.               |

**Response** — service-level `RejectResult`:

```json theme={null}
{ "status": "OK", "rowId": "…", "newStatus": "REJECTED" }
```

### Errors

| Status            | When                                                                                                 |
| ----------------- | ---------------------------------------------------------------------------------------------------- |
| `400 Bad Request` | `rowId` not a UUID, or row's current status doesn't allow reject (response carries `currentStatus`). |
| `404 Not Found`   | Row not found in `:ledger`.                                                                          |

## Update one cell

```http theme={null}
PATCH /v1/subledgers/:ledger/:rowId
```

Update one editable cell on a row. The set of editable fields is per-ledger — for `expenses`, see [`EditableExpenseField`](/sdk/subledgers/types/expenses#mutation-helpers--ntrosubledgertypesexpenses_mutations). Non-editable fields return `400 INVALID_FIELD`.

**Body** — `UpdateSubledgerCellDto`:

```json theme={null}
{
  "field": "amount_gross",
  "value": "12.50"
}
```

| Field   | Type    | Required | Notes                                                                                |
| ------- | ------- | -------- | ------------------------------------------------------------------------------------ |
| `field` | string  | Yes      | Column key. Per-ledger allow-list; coercion happens in the per-ledger adapter.       |
| `value` | unknown | Yes      | New scalar / null. Intentionally permissive — per-ledger adapters validate / coerce. |

**Response** — service-level `UpdateResult`:

```json theme={null}
{ "status": "OK", "rowId": "…", "field": "amount_gross", "value": "12.50" }
```

### Errors

| Status            | When                                                                                                                                                       |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400 Bad Request` | `rowId` not a UUID; field not editable on this ledger; value coercion failed; row's current status doesn't allow edits (response carries `currentStatus`). |
| `404 Not Found`   | Row not found.                                                                                                                                             |

## When to use these vs `/tasks/:id/row-action`

| Use this endpoint                                     | Use `POST /tasks/:taskId/row-action`                     |
| ----------------------------------------------------- | -------------------------------------------------------- |
| Ops tooling fixing a stuck row outside a workflow run | Tenant UI inline edit / reject during a HITL review step |
| Migration / backfill scripts                          | Anything that needs Temporal to be the system of record  |
| One-off forensic correction                           | All standard runbook flows                               |

Direct mutations bypass the workflow — replay / idempotency / step lifecycle don't see them. Prefer the row-action signal whenever a workflow is alive for the row.

## Cross-links

<CardGroup cols={2}>
  <Card title="SDK Subledgers" icon="table" href="/sdk/subledgers">
    `Row` base, `SubledgerStatus` lifecycle, mutation helpers.
  </Card>

  <Card title="Tasks — row-action" icon="right-left" href="/api-reference/tenant-api/tasks#data_table-row-action">
    The signal-based path used by the Tenant UI.
  </Card>
</CardGroup>
