Clients¶
The SDK has two entry points. Both are final classes with readonly resource properties, so every resource is available right after construction and cannot be reassigned.
Pennylane¶
Pennylane\Sdk\Pennylane talks to the Company API v2, scoped to a single company. Base URL: https://app.pennylane.com/api/external/v2. Requests are throttled to 25 requests per 5 seconds.
$client = new Pennylane\Sdk\Pennylane();
Constructor options¶
| Option | Type | Default | Description |
|---|---|---|---|
apiToken |
?string |
null |
The bearer token. Falls back to the PENNYLANE_API_TOKEN environment variable (checked in $_SERVER, $_ENV, then getenv()). Throws ConfigurationException when neither is set. |
baseUrl |
?string |
null (uses Pennylane::DEFAULT_BASE_URL) |
Must be https, except http://localhost, http://127.0.0.1 or http://[::1] for local development. Any other scheme throws ConfigurationException so the token is never sent in cleartext. |
httpClient |
?ClientInterface |
null |
Injected PSR-18 HTTP client. When omitted, the SDK uses Guzzle if installed (with redirects disabled and http_errors off), otherwise discovers any PSR-18 client via php-http/discovery. Disable automatic redirects on any client you inject yourself, since a redirect could leak the Authorization header. |
requestFactory |
?RequestFactoryInterface |
null |
Injected PSR-17 request factory. Discovered automatically when omitted. |
streamFactory |
?StreamFactoryInterface |
null |
Injected PSR-17 stream factory. Discovered automatically when omitted. |
autoThrottle |
bool |
true |
Keeps outgoing requests under the API rate limit on the client side, so a loop over thousands of records never trips a 429. Only paces the current process. |
maxRetries |
int |
3 |
Retry budget for retries that are safe, meaning GET requests and connection level failures. The API has no idempotency mechanism, so a POST is never retried after a server error. |
timeout |
?float |
null |
Request timeout in seconds. Only has an effect on the SDK's default Guzzle client; passing it together with an injected httpClient throws ConfigurationException, since the SDK cannot enforce a timeout on a client it does not own. |
Resource properties¶
| Property | Class | Endpoint prefix |
|---|---|---|
customers (with ->companies, ->individuals) |
Company\Customers, Company\CompanyCustomers, Company\IndividualCustomers |
/customers, /company_customers, /individual_customers |
customerInvoices |
Company\CustomerInvoices |
/customer_invoices |
customerInvoiceTemplates |
Company\CustomerInvoiceTemplates |
/customer_invoice_templates |
quotes |
Company\Quotes |
/quotes |
commercialDocuments |
Company\CommercialDocuments |
/commercial_documents |
products |
Company\Products |
/products |
billingSubscriptions |
Company\BillingSubscriptions |
/billing_subscriptions |
suppliers |
Company\Suppliers |
/suppliers |
supplierInvoices |
Company\SupplierInvoices |
/supplier_invoices |
purchaseRequests |
Company\PurchaseRequests |
/purchase_requests |
transactions |
Company\Transactions |
/transactions |
bankAccounts |
Company\BankAccounts |
/bank_accounts |
bankEstablishments |
Company\BankEstablishments |
/bank_establishments |
journals |
Company\Journals |
/journals |
ledgerAccounts |
Company\LedgerAccounts |
/ledger_accounts |
ledgerEntries |
Company\LedgerEntries |
/ledger_entries |
ledgerAttachments |
Company\LedgerAttachments |
/ledger_attachments (deprecated) |
ledgerEntryLines |
Company\LedgerEntryLines |
/ledger_entry_lines |
trialBalance |
Company\TrialBalance |
/trial_balance |
fiscalYears |
Company\FiscalYears |
/fiscal_years |
categories |
Company\Categories |
/categories |
categoryGroups |
Company\CategoryGroups |
/category_groups |
exports (with ->fecs, ->generalLedgers, ->analyticalGeneralLedgers) |
Company\Exports, Company\FecExports, Company\GeneralLedgerExports, Company\AnalyticalGeneralLedgerExports |
/exports/* |
sepaMandates |
Company\SepaMandates |
/sepa_mandates |
gocardlessMandates |
Company\GocardlessMandates |
/gocardless_mandates |
proAccount |
Company\ProAccount |
/pro_account/* |
eInvoices |
Company\EInvoices |
/e-invoices/imports (deprecated beta) |
paRegistrations |
Company\PaRegistrations |
/pa_registrations |
fileAttachments |
Company\FileAttachments |
/file_attachments |
changelogs |
Company\Changelogs |
/changelogs/{resource} |
webhookSubscriptions |
Company\WebhookSubscriptions |
/webhook_subscriptions (beta) |
me |
Company\Me |
/me |
PennylaneFirm¶
Pennylane\Sdk\PennylaneFirm talks to the Firm API v1, for accounting firms managing several client companies. Base URL: https://app.pennylane.com/api/external/firm/v1. Requests are throttled to 5 requests per second.
$firm = new Pennylane\Sdk\PennylaneFirm();
Every resource method except companies takes the client company id as its first argument: $firm->ledgerEntries->list($companyId).
Constructor options¶
PennylaneFirm accepts the exact same options as Pennylane, with one difference: apiToken falls back to the PENNYLANE_FIRM_API_TOKEN environment variable instead of PENNYLANE_API_TOKEN.
| Option | Type | Default | Description |
|---|---|---|---|
apiToken |
?string |
null |
Falls back to PENNYLANE_FIRM_API_TOKEN. Throws ConfigurationException when neither is set. |
baseUrl |
?string |
null (uses PennylaneFirm::DEFAULT_BASE_URL) |
Same https rule as Pennylane. |
httpClient |
?ClientInterface |
null |
Same as Pennylane. |
requestFactory |
?RequestFactoryInterface |
null |
Same as Pennylane. |
streamFactory |
?StreamFactoryInterface |
null |
Same as Pennylane. |
autoThrottle |
bool |
true |
Paces requests to the Firm API limit (5 req/s) instead of the Company limit. |
maxRetries |
int |
3 |
Same retry policy as Pennylane. |
timeout |
?float |
null |
Same rule as Pennylane: incompatible with an injected httpClient. |
Resource properties¶
| Property | Class | Endpoint prefix |
|---|---|---|
companies |
Firm\FirmCompanies |
/companies |
fiscalYears |
Firm\FirmFiscalYears |
/companies/{company_id}/fiscal_years |
trialBalance |
Firm\FirmTrialBalance |
/companies/{company_id}/trial_balance |
journals |
Firm\FirmJournals |
/companies/{company_id}/journals |
ledgerAccounts |
Firm\FirmLedgerAccounts |
/companies/{company_id}/ledger_accounts |
ledgerEntries |
Firm\FirmLedgerEntries |
/companies/{company_id}/ledger_entries |
ledgerEntryLines |
Firm\FirmLedgerEntryLines |
/companies/{company_id}/ledger_entry_lines |
exports (with ->fecs, ->analyticalGeneralLedgers) |
Firm\FirmExports, Firm\FirmFecExports, Firm\FirmAnalyticalGeneralLedgerExports |
/companies/{company_id}/exports/* |
dms (with ->files, ->folders) |
Firm\FirmDms, Firm\FirmDmsFiles, Firm\FirmDmsFolders |
/companies/{company_id}/dms/* |
fileAttachments |
Firm\FirmFileAttachments |
/file_attachments |
customerInvoices |
Firm\FirmCustomerInvoices |
/companies/{company_id}/customer_invoices |
supplierInvoices |
Firm\FirmSupplierInvoices |
/companies/{company_id}/supplier_invoices |
customers |
Firm\FirmCustomers |
/companies/{company_id}/customers (read only, beta) |
suppliers |
Firm\FirmSuppliers |
/companies/{company_id}/suppliers (read only, beta) |
bankAccounts |
Firm\FirmBankAccounts |
/companies/{company_id}/bank_accounts |
transactions |
Firm\FirmTransactions |
/companies/{company_id}/transactions |
categories |
Firm\FirmCategories |
/companies/{company_id}/categories |
categoryGroups |
Firm\FirmCategoryGroups |
/companies/{company_id}/category_groups |
changelogs |
Firm\FirmChangelogs |
/companies/{company_id}/changelogs/* |
lastRateLimit()¶
Both clients expose the same method:
public function lastRateLimit(): ?RateLimitInfo
Returns the rate limit state read from the ratelimit-* response headers of the most recent API call, or null if no such header was present (for example, before any request has been made). This is purely informational: the SDK throttles proactively on the client side regardless of what this method returns.
RateLimitInfo (Pennylane\Sdk\RateLimitInfo) exposes three nullable properties:
| Property | Type | Description |
|---|---|---|
limit |
?int |
Request budget of the current window (ratelimit-limit header). |
remaining |
?int |
Requests left in the current window (ratelimit-remaining header). |
reset |
?int |
Unix timestamp at which the window resets (ratelimit-reset header). |
$client->customerInvoices->list();
$rateLimit = $client->lastRateLimit();
if ($rateLimit !== null && $rateLimit->remaining !== null) {
echo "Requests left in the current window: {$rateLimit->remaining}\n";
}