Skip to main content

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.

ntro.capabilities.gl is the external accounting system integration layer: one stable Python surface (GLProvider, resource clients, typed models) regardless of whether the customer connects via ApiDeck Unified or a future direct provider. It is distinct from:
LayerRole
ntro.accountingFund-ops domain models (trial balance shape, journal proposals, validation) inside Ntropii.
ntro.subledgerTenant ledgers.* rows and lifecycle before/at posting.
ntro.capabilities.glRead/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 ApiDeck adapter docs in source.

Install

pip install 'ntro[gl]'
The [gl] extra pulls apideck_unify (Unity Accounting SDK). Worker images that post to Xero/QBO via ApiDeck 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 — e.g. "apideck".
  • options — provider-specific connection (consumer id, service id, etc.), populated after the tenant completes GL connect in Ntropii Workspace.
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 — mirrors ApiDeck Unified vocabulary:
# 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:
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

ExceptionTypical meaning
ConnectionError_GL not linked / credentials missing.
ValidationFailedErrorProvider rejected payload (posted journal, invalid account, …).
IdempotencyConflictErrorSame external_id, different body — logic bug or race.
TransientProviderErrorRetryable — often re-raised so Temporal retries the activity.

Models

Types such as JournalEntry, Bill, LedgerAccount, … are re-exported from ntro.capabilities.gl.models (ApiDeck Unified shapes). Prefer importing from gl.models in runbook code so upgrades stay centralized.

Accounting

Domain journals and proposals before external posting.

Subledgers

Typed rows and propose_for_gl metadata.

Data plane

Resolving tenant DB connections inside activities.