lenorix / beel-sdk
Unofficial community PHP SDK for the BeeL invoicing API
Fund package maintenance!
Requires
- php: ^8.4
- guzzlehttp/guzzle: ^8.2
- jane-php/open-api-runtime: ^7.14
- php-http/client-common: ^2.0
- php-http/discovery: ^1.6
- psr/http-client: ^1.0
- symfony/serializer: ^5.4 || ^6.4 || ^7.0 || ^8.0
Requires (Dev)
- jane-php/open-api-3: ^7.14
- laravel/pint: ^1.0
- pestphp/pest: ^4.0
- phpstan/phpstan: ^2.1
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
An instance-based PHP client for the BeeL invoicing API, including company and account scoped resources, VeriFactu invoicing, retries, idempotency, PDF downloads, and webhook signature verification.
Unofficial SDK, developed by lenorix. BeeL does not make or endorse this package. Lenorix develops it as an independent client for the BeeL API.
Requires PHP 8.4+. The HTTP API is backed by the JanePHP client generated from BeeL's OpenAPI contract.
Installation
composer require lenorix/beel-sdk
Quick start
<?php use Lenorix\BeelSdk\Beel; use Lenorix\BeelSdk\Builder\InvoiceBuilder; use Lenorix\BeelSdk\Generated\Model\CreateInvoiceRequestLinesItem; use Lenorix\BeelSdk\Generated\Model\CreateInvoiceRequestLinesItemMainTax; $beel = new Beel(apiKey: getenv('BEEL_API_KEY') ?: throw new RuntimeException('Set BEEL_API_KEY.')); $company = $beel->company('company-uuid'); $line = (new CreateInvoiceRequestLinesItem()) ->setLineType('NORMAL') ->setDescription('Consulting services') ->setQuantity(1) ->setUnitPrice(100) ->setDiscountPercentage(0) ->setMainTax((new CreateInvoiceRequestLinesItemMainTax()) ->setType('IVA') ->setPercentage(21) ->setRegimeKey('01')); $request = InvoiceBuilder::create() ->forCustomer('customer-uuid') ->addLineObject($line) ->build(); $invoice = $company->invoices->create($request); $issued = $company->invoices->issue($invoice->getId()); echo $issued->getInvoiceNumber();
Create the invoice first and issue it when it is ready. The generated Jane model returned by the SDK is available directly, so its getters and the complete BeeL response remain accessible.
Client options
$beel = new Beel( apiKey: 'beel_sk_live_...', baseUrl: 'https://app.beel.es/api', // default maxRetries: 3, // default: retries after the initial request retryDelayMs: 500, // default maxRetryDelayMs: 30_000, // default autoIdempotencyKey: true, // default );
Use a test key (beel_sk_test_...) while developing and a live key (beel_sk_live_...) in production. The key selects the environment; the base URL stays the same.
maxRetries is the maximum number of retries after the first attempt. The SDK retries 429 and 5xx responses with exponential backoff, honors Retry-After when provided up to maxRetryDelayMs, and uses the same idempotency key for every retry of a POST. It does not retry other client errors.
The client is instance-based. Each Beel instance has its own API key and transport; there is no global configuration or shared authentication state.
Companies (NIFs)
Address the company explicitly so one client can work with multiple NIFs:
$company = $beel->company('company-uuid'); $invoices = $company->invoices->list(['status' => 'ISSUED']); $invoice = $company->invoices->get('invoice-uuid'); $company->invoices->issue($invoice->getId()); $company->invoices->void( $invoice->getId(), (new \Lenorix\BeelSdk\Generated\Model\VoidInvoiceRequest())->setReason('Billing error'), ); $pdf = $company->invoices->getPdf($invoice->getId()); $customer = $company->customers->create($customerRequest); $product = $company->products->create($productRequest); $company->series->ensureDefaults(); $summary = $company->fiscalSummary(['year' => 2026]); $readiness = $company->issuingReadiness();
The company scope exposes invoices, customers, products, series, recurringInvoices, paymentConnections, taxConfiguration, and verifactuConfiguration, along with company operations such as get(), update(), delete(), fiscalSummary(), and issuingReadiness().
Recurring invoices, schedules, VeriFactu settings, and other request bodies use the corresponding generated models under Lenorix\BeelSdk\Generated\Model. For example:
use Lenorix\BeelSdk\Generated\Model\SetRecurringInvoiceStatusRequest; $company->recurringInvoices->setStatus( 'recurring-invoice-uuid', (new SetRecurringInvoiceStatusRequest())->setStatus('PAUSED'), );
Accounts and payment connections
Account resources are scoped independently from NIF resources:
$account = $beel->account('account-uuid'); $accountData = $account->get(); $companies = $account->companies->list(); $members = $account->members->list(); $invitations = $account->invitations->list(); $webhooks = $account->webhooks->list();
Payment connections belong to a company. A connection is identified by its ID; its events resource is scoped to that connection:
$company = $beel->company('company-uuid'); $connections = $company->paymentConnections->list(); $events = $company->paymentConnections->events('connection-uuid'); $pending = $events->list(['needs_action' => true]); $events->retry('event-uuid'); $draft = $events->draft('event-uuid');
Builders
Builders are optional helpers. They return Jane-generated request models, not SDK-specific DTOs.
use Lenorix\BeelSdk\Builder\CustomerBuilder; $customerRequest = CustomerBuilder::create() ->name('Acme SL') ->nif('B12345678') ->email('billing@acme.es') ->address('Calle Mayor', '1', '28001', 'Madrid', 'Madrid', 'Spain') ->build(); $customer = $company->customers->create($customerRequest);
InvoiceBuilder supports type(), forCustomer(), operationDate(), dueDate(), series(), externalRef(), metadata(), notes(), addLine(), and addLineObject(). BeeL requires an explicit main_tax on every normal invoice line; addLine() is a convenience shortcut without tax fields, so use addLineObject() when building a valid taxable line. CustomerBuilder supports name, NIF, email, phone, notes, and address. Each builder checks its documented required fields when build() is called.
Raw Jane client
$beel->raw exposes the generated Jane client for operations without a handwritten convenience wrapper. It uses the same configured authentication and transport:
$identity = $beel->raw->getMyIdentity();
New operations become available there after the OpenAPI client is regenerated. Generated classes live under Lenorix\BeelSdk\Generated; src/Generated/ is generated output and should not be edited by hand.
PDF downloads
$company->invoices->getPdf($invoiceId) returns the generated PDF response model, including its signed download URL. The legacy convenience method $beel->downloadPdf($invoiceId) returns ['buffer' => ..., 'fileName' => ...]; it uses the deprecated session-focus invoice route. Prefer the company-scoped route for new integrations.
Webhooks
Verify the raw request body before processing an event. The verifier checks the HMAC-SHA256 signature and timestamp replay window (300 seconds by default):
use Lenorix\BeelSdk\Webhook\WebhookVerifier; use Lenorix\BeelSdk\Generated\Model\WebhookEventDataInvoiceIssued; $verifier = new WebhookVerifier($_ENV['BEEL_WEBHOOK_SECRET']); $event = $verifier->verifyEvent( payload: $rawRequestBody, signatureHeader: $signatureHeader, // BeeL-Signature request header ); if ($event->getType() === 'invoice.issued' && $event->getData() instanceof WebhookEventDataInvoiceIssued) { $invoiceId = $event->getData()->getInvoiceId(); $invoiceNumber = $event->getData()->getInvoiceNumber(); }
verifyEvent() returns Jane's generated WebhookEvent model, with data denormalized to the generated model for its event type. This is useful when dispatching typed framework events, such as Laravel events. verify() remains available when you prefer the decoded payload as an array. Event names are also available as WebhookEventType enum cases, for example WebhookEventType::INVOICE_ISSUED->value.
Errors
API errors are mapped to semantic exception classes. All extend BeelApiError:
| Exception | HTTP status | Useful properties |
|---|---|---|
BeelAuthError |
401, 403 | statusCode, apiCode, requestId |
BeelNotFoundError |
404 | statusCode, apiCode, requestId |
BeelConflictError |
409 | statusCode, apiCode, details, requestId |
BeelValidationError |
422 | statusCode, apiCode, details, requestId |
BeelRateLimitError |
429 | statusCode, retryAfter, retryAfterSeconds |
BeelApiError |
Other API errors | statusCode, apiCode, details, requestId |
use Lenorix\BeelSdk\Exception\BeelNotFoundError; try { $invoice = $company->invoices->get('invoice-uuid'); } catch (BeelNotFoundError $exception) { logger()->warning($exception->getMessage(), ['request_id' => $exception->requestId]); }
Legacy session-focused resources
The SDK retains the deprecated compatibility surface from the API, including $beel->invoices, $beel->customers, $beel->products, $beel->series, and $beel->configuration. New integrations should use $beel->company($companyId) and its resources so the NIF is explicit in every operation. See the Multi-NIF migration guide for route changes and sunset details.