Firm API (accounting firms)¶
Accounting firms (cabinets d'expertise comptable) get a dedicated API to work across their whole client portfolio: list the companies they manage, read and post accounting entries, upload documents, run exports, all per client company.
Clients and token¶
<?php
use Pennylane\Sdk\PennylaneFirm;
$firm = new PennylaneFirm(); // reads PENNYLANE_FIRM_API_TOKEN
The token is generated from your firm account (no sandbox needed). The Firm API is rate limited at 5 requests per second; the SDK throttles accordingly.
A firm token reaches real client companies
There is no sandbox for the Firm API: every call authenticated with a firm token acts on the live data of a real client company. Test carefully, especially before posting ledger entries or running exports, since a mistake affects a client's actual books, not a throwaway test account.
Iterate your portfolio¶
foreach ($firm->companies->list() as $company) {
echo $company->id . ' ' . $company->name . ' ' . $company->siren . PHP_EOL;
}
Every other resource takes the company id as its first argument:
$companyId = 4217;
// Fiscal years
foreach ($firm->fiscalYears->list($companyId) as $fy) {
echo $fy->start->format('Y-m-d') . ' ' . $fy->finish->format('Y-m-d') . PHP_EOL;
}
// Trial balance over a period
foreach ($firm->trialBalance->list(
$companyId,
periodStart: '2026-01-01',
periodEnd: '2026-06-30',
) as $row) {
echo $row->number . ' ' . $row->debits . ' ' . $row->credits . PHP_EOL;
}
Three endpoints paginate by page number
Most list endpoints paginate with a cursor, but three Firm API endpoints instead paginate with page and perPage: companies, fiscalYears and trialBalance. They return a NumberedPage with totalPages and currentPage properties instead of the usual CursorPage. Iterating with foreach walks every page transparently either way.
Post accounting entries¶
$firm->ledgerEntries->create(
$companyId,
date: '2026-07-08',
label: 'OD de régularisation',
journalId: 12,
ledgerEntryLines: [
['ledger_account_id' => 411, 'debit' => '120.00', 'credit' => '0.00'],
['ledger_account_id' => 706, 'debit' => '0.00', 'credit' => '120.00'],
],
);
As on the Company API, every ledger entry line needs both a debit and a credit key ("0.00" on the unused side), and the lines must be balanced.
Documents (DMS)¶
The firm-only DMS resource manages the document library of each client company:
use Pennylane\Sdk\FileUpload;
$firm->dms->folders->list($companyId);
$firm->dms->files->upload($companyId, FileUpload::fromPath('path/to/piece.pdf'));
Exports¶
$export = $firm->exports->fecs->create($companyId, periodStart: '2026-01-01', periodEnd: '2026-12-31');
$export = $firm->exports->fecs->get($companyId, $export->id); // poll until status is "done"
Read-only beta invoicing endpoints¶
The Firm API can read a client company's customer and supplier invoices, but this surface is in beta and read-only: list() and get() only, no creation or state changes.
foreach ($firm->customerInvoices->list($companyId) as $invoice) {
// ...
}
$firm->supplierInvoices->get($companyId, $supplierInvoiceId);
Invoice creation and lifecycle actions (finalize, send, mark as paid, and so on) happen through each company's own Company API, with its own PENNYLANE_API_TOKEN.
Mass synchronization¶
Changelogs let you sync many client companies incrementally without re-reading everything:
foreach ($firm->companies->list() as $company) {
foreach ($firm->changelogs->ledgerEntryLines($company->id) as $event) {
// ...
}
}
Company vs Firm scopes
The Firm API is read-write on accounting (entries, accounts, journals, banking) but read-only on invoicing (customer and supplier invoices, in beta). Invoice creation happens through each company's own Company API.