Accounting operations¶
Beyond invoicing, the API exposes the accounting core: journals, chart of accounts, entries, lettering, trial balance and legal exports.
Chart of accounts and journals¶
# Chart of accounts (plan comptable)
for account in client.ledger_accounts.list():
print(account.number, account.label)
client.ledger_accounts.create(number="706100", label="Prestations de services")
# Journals
for journal in client.journals.list():
print(journal.code, journal.label)
Ledger entries (écritures)¶
An entry is a balanced set of debit/credit lines:
from decimal import Decimal
entry = client.ledger_entries.create(
date="2026-07-08",
journal_id=12,
label="Vente prestation",
ledger_entry_lines=[
{"ledger_account_id": 411, "debit": Decimal("120.00"), "credit": Decimal("0.00")},
{"ledger_account_id": 706, "debit": Decimal("0.00"), "credit": Decimal("100.00")},
{"ledger_account_id": 445, "debit": Decimal("0.00"), "credit": Decimal("20.00")},
],
)
If the lines do not balance, the API answers 422 and the SDK raises ValidationError with the totals in err.details.
No idempotency on creations
Posting the same entry twice creates a duplicate: the API does not deduplicate. The SDK never auto-retries a POST after a server error for this reason. Deduplicate on your side if your pipeline can replay.
Lettering (lettrage)¶
Lettering links entry lines that settle each other, typically an invoice line with its payment line:
client.ledger_entry_lines.letter(
unbalanced_lettering_strategy="none",
ledger_entry_lines=[{"id": 111}, {"id": 222}],
)
client.ledger_entry_lines.unletter(
unbalanced_lettering_strategy="none",
ledger_entry_lines=[{"id": 111}, {"id": 222}],
)
client.ledger_entry_lines.list_lettered_lines(111)
Trial balance (balance générale)¶
for row in client.trial_balance.list(
period_start="2026-01-01",
period_end="2026-06-30",
):
print(row.number, row.debits, row.credits)
Analytical categories¶
Pennylane's analytics let you tag almost everything (invoices, transactions, entry lines) with categories organized in groups (axes):
groups = client.category_groups.list()
client.categories.create(label="Agence Lyon", category_group_id=3)
client.customer_invoices.categorize(
invoice_id,
categories=[{"id": 42, "weight": "1.0"}],
)
Legal exports: FEC, general ledger¶
Exports run asynchronously: create the export, then poll until the file is ready.
import time
export = client.exports.fecs.create(
period_start="2026-01-01", period_end="2026-12-31"
)
while export.status not in ("done", "failed"):
time.sleep(5)
export = client.exports.fecs.get(export.id)
print(export.file_url) # download link
Same pattern for client.exports.general_ledgers and client.exports.analytical_general_ledgers.
Bank data¶
for account in client.bank_accounts.list():
...
for tx in client.transactions.list(filter=[filters.gte("date", "2026-01-01")]):
...
# Reconcile: which invoices does this transaction settle?
client.transactions.list_matched_invoices(tx.id)