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

# General ledgers

> ntro.capabilities.gl — resolve the tenant's external GL (Xero, QuickBooks, etc.) and create Bills, journal entries, trial balance reads, and more.

**`ntro.capabilities.gl`** is the **external accounting system** integration layer: one stable Python surface (`GLProvider`, resource clients, typed models) regardless of which underlying GL the customer connects.

It is distinct from:

| Layer                                             | Role                                                                                            |
| ------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| [`ntro.accounting`](/sdk/capabilities/accounting) | Fund-ops **domain** models (trial balance shape, journal proposals, validation) inside Ntropii. |
| [`ntro.subledger`](/sdk/subledgers)               | Tenant **`ledgers.*`** rows and lifecycle before/at posting.                                    |
| **`ntro.capabilities.gl`**                        | **Read/write the customer's GL** (bills, invoices, TB reports, …) through the unified provider. |

Subledger proposals (`BillProposal`, etc.) strip Ntropii-only metadata before crossing into **`gl`** — see `ntro.subledger.proposals` and the unified-adapter docs in source.

## Install

```bash theme={null}
pip install 'ntro[gl]'
```

The **`[gl]`** extra pulls the unified-GL SDK. Worker images that post to Xero / QuickBooks / Sage should include this extra — same as runbooks that call `gl.for_entity`.

Workflow authoring typically still pins **`ntro[workflow]`** for Temporal + capabilities; add **`[gl]`** when the runbook posts to a connected ledger.

## Resolve a provider

Providers are selected from **`tenant.config.gl`**:

* **`provider`** — the registered GL adapter (e.g. the bundled unified adapter, or a future direct provider).
* **`options`** — provider-specific connection (consumer id, service id, etc.), populated after the tenant completes GL connect in Ntropii Workspace.

```python theme={null}
from uuid import UUID

import ntro.capabilities.gl as gl

provider = await gl.for_entity(
    UUID("…"),
    tenant_config=ctx.tenant_config,  # includes merged tenant.config.gl
)

# Resource-oriented API:
# provider.journal_entries, provider.bills, provider.trial_balance, …
```

If **`tenant.config.gl.provider`** is missing or unknown, resolution raises **`ProviderNotRegisteredError`** or **`ConnectionError_`** — handle these in activities (many runbooks short-circuit posting when no GL is configured).

## Example: post bills from approved expenses

From **`expense_processor.post_expenses_to_gl`** — proposals built from subledger rows, then **`provider.bills.create`** with an **idempotency** external id:

```python theme={null}
from ntro.capabilities import gl
from ntro.capabilities.gl.errors import (
    ConnectionError_,
    IdempotencyConflictError,
    ValidationFailedError,
)

provider = await gl.for_entity(input.entity_id, tenant_config=input.tenant_config)
proposals = ExpenseRow.propose_for_gl(rows, task_id=input.task_id)

for proposal in proposals:
    result = await provider.bills.create(
        proposal,
        external_id=proposal.idempotency_key,
    )
```

**Idempotency:** writers take **`external_id`**; providers must honour deduplication and expose **`find_by_external_id`** so Temporal retries stay safe.

## Errors you should handle

| Exception                  | Typical meaning                                                 |
| -------------------------- | --------------------------------------------------------------- |
| `ConnectionError_`         | GL not linked / credentials missing.                            |
| `ValidationFailedError`    | Provider rejected payload (posted journal, invalid account, …). |
| `IdempotencyConflictError` | Same `external_id`, different body — logic bug or race.         |
| `TransientProviderError`   | Retryable — often re-raised so Temporal retries the activity.   |

## Models

Types such as **`JournalEntry`**, **`Bill`**, **`LedgerAccount`**, … are re-exported from **`ntro.capabilities.gl.models`**. Prefer importing from **`gl.models`** in runbook code so upgrades stay centralized.

## Reports — typed read-only views of GL state

A **report** is a typed, point-in-time view of the customer's GL state — `TrialBalanceReport`, future `BalanceSheetReport`, `ProfitAndLossReport`. They live at **`ntro.capabilities.gl.reports`** (sibling to `models`).

Reports differ from subledgers: no per-row HITL lifecycle, no posting back, no transactional shape. They're **inputs** to a workflow run, ephemeral after the run closes. The same typed shape is hydrated regardless of source.

### Pattern B — the canonical flow for reports

```
customer uploads doc        document-ingest extracts          consuming
        │                            │                           workflow
        ▼                            ▼                              │
ingest.submitted_documents  →  ingest.extracted_payloads  ─────────┘
                                       (typed JSON cached       reads + hydrates
                                        once at upload time)    via from_extracted_payload
```

Workflows consume reports via the typed classmethod:

```python theme={null}
from ntro.capabilities.gl.reports import TrialBalanceReport
from ntro.workflow.task import find_committed_document

# In the workflow step (mirror of mortgage_agreement / rental_statement reads):
cached = await self.find_committed_document(
    workflow_slug="document-ingest",
    entity_id=ctx.entity_slug,
    source="xero-trial-balance",
)
tb = TrialBalanceReport.from_extracted_payload(cached, period=ctx.period)
tb.assert_balanced()
```

That's `<100ms`, vs \~30s if the workflow re-runs `ai.extract` itself (the original `parse_starting_tb` shape pre-N-70).

### Two source paths, one shape

| Source                            | When                                                                                  | How                                                                 |
| --------------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| **Cached extraction** (Pattern B) | Greenfield — customer's doc-uploaded TB; no GL bound or no opening balance seeded yet | `TrialBalanceReport.from_extracted_payload(cached_payload, period)` |
| **Live API** (future)             | Once customer's GL is bound and opening balances posted                               | `await provider.trial_balance.get(period=…)`                        |

The classmethod `from_extracted_payload` adapts the document-ingest payload dict; the `TrialBalanceClient.get(...)` Protocol returns the same `TrialBalanceReport` from the underlying provider. Workflow code is invariant across sources.

### The `<capability>.reports.<Type>` convention

This pattern generalises across capabilities. Each "external ledger" capability (gl, banking, …) gets a sibling `reports` module:

| Module                                                  | Types it owns                                                            |
| ------------------------------------------------------- | ------------------------------------------------------------------------ |
| `ntro.capabilities.gl.reports`                          | `TrialBalanceReport`, future `BalanceSheetReport`, `ProfitAndLossReport` |
| `ntro.capabilities.banking.reports` (forthcoming, N-72) | `BankStatementReport`                                                    |

Same `from_extracted_payload(...)` constructor shape across all of them.

## Related

<CardGroup cols={2}>
  <Card title="Accounting" icon="calculator" href="/sdk/capabilities/accounting">
    Domain journals and proposals before external posting.
  </Card>

  <Card title="Subledgers" icon="table" href="/sdk/subledgers">
    Typed rows and `propose_for_gl` metadata.
  </Card>

  <Card title="Data plane" icon="database" href="/sdk/capabilities/data">
    Resolving tenant DB connections inside activities.
  </Card>
</CardGroup>
