Skip to main content
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: Subledger proposals (BillProposal, etc.) strip Ntropii-only metadata before crossing into gl — see ntro.subledger.proposals and the unified-adapter docs in source.

Install

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.
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:
Idempotency: writers take external_id; providers must honour deduplication and expose find_by_external_id so Temporal retries stay safe.

Errors you should handle

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

Workflows consume reports via the typed classmethod:
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

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: Same from_extracted_payload(...) constructor shape across all of them.

Accounting

Domain journals and proposals before external posting.

Subledgers

Typed rows and propose_for_gl metadata.

Data plane

Resolving tenant DB connections inside activities.