A modern, strongly-typed PHP client for the easybill REST API with built-in rate limiting.
A modern, strongly-typed, dependency-free PHP client for the easybill REST API, built for PHP 8.4+ with first-class rate-limit handling.
Featuresfinal readonly DTOs and native enums for every API model.ext-curl and ext-json PHP extensions are required).HttpClient interface to route
requests through your own HTTP stack (e.g. Guzzle) — without the library depending on it.429 / transient errors, honoring Retry-After
when the API provides it.ext-curl and ext-jsoncomposer require gosuccess/easybill-apiQuick start
use GoSuccess\Easybill\Client; use GoSuccess\Easybill\ClientConfig; $client = new Client(new ClientConfig( token: 'your-api-key', // easybill allows 10 or 60 requests/minute depending on your plan. // Set this to match YOUR limit so the client never hits a 429: requestsPerMinute: 60, )); // Fetch a single customer $customer = $client->customers->get(12345); echo $customer->companyName; // Create a customer use GoSuccess\Easybill\Model\Customer; use GoSuccess\Easybill\Enum\Salutation; $created = $client->customers->create(new Customer( companyName: 'ACME GmbH', salutation: Salutation::Company, emails: ['billing@acme.example'], ));Partial updates and clearing fields
Models serialize only what you actually passed, so an update touches nothing
else. Because of that, a field you leave out and a field you set to null must
mean two different things:
use GoSuccess\Easybill\Model\Customer; // Renames the customer. Every other field keeps its current value. $client->customers->update(12345, new Customer(companyName: 'ACME SE')); // Clears the note: `null` is sent as an explicit JSON null. $client->customers->update(12345, new Customer(note: null)); // Same for lists — `[]` empties them. $client->customers->update(12345, new Customer(emails: []));
To make a field conditional, fall back to the Undefined sentinel — the default
of every writable parameter — instead of null:
use GoSuccess\Easybill\Model\Undefined; $client->customers->update(12345, new Customer( note: $clearNote ? null : Undefined::Value, ));
Reading is unaffected: properties stay plainly typed (?string, list<string>),
never sentinel-valued. A model returned by the API carries no intent to clear
anything, so passing one straight back never nulls out fields.
Each list endpoint offers list() for a single page and all() for a lazy
iterator over every page (each page request is rate-limited automatically).
Filters are strongly typed per endpoint:
use GoSuccess\Easybill\Filter\CustomerFilter; use GoSuccess\Easybill\Filter\DocumentFilter; use GoSuccess\Easybill\Enum\DocumentType; // One page $page = $client->customers->list(page: 1, limit: 100, filter: new CustomerFilter(country: 'DE')); echo $page->total, ' customers in total'; // All customers, transparently across pages foreach ($client->customers->all(new CustomerFilter(country: 'DE')) as $customer) { echo $customer->companyName, PHP_EOL; } // Enum-typed filters, e.g. only draft invoices foreach ($client->documents->all(new DocumentFilter(type: DocumentType::Invoice, isDraft: true)) as $document) { // ... }Rate limiting
The request limit is configured via ClientConfig::$requestsPerMinute. The default
SlidingWindowRateLimiter keeps the client below that threshold so you avoid 429 responses
entirely. You can swap in your own implementation of the RateLimiter interface (for example a
Redis-backed limiter shared across processes):
use GoSuccess\Easybill\Client; use GoSuccess\Easybill\ClientConfig; $client = new Client( config: new ClientConfig(token: 'your-api-key'), rateLimiter: new MyRedisRateLimiter(/* ... */), );Error handling
Every error thrown by the library implements GoSuccess\Easybill\Exception\EasybillException:
use GoSuccess\Easybill\Exception\NotFoundException; use GoSuccess\Easybill\Exception\RateLimitException; use GoSuccess\Easybill\Exception\ValidationException; try { $client->customers->get(999999); } catch (NotFoundException $e) { // 404 } catch (ValidationException $e) { // 422 — inspect $e->responseBody } catch (RateLimitException $e) { // 429 — $e->retryAfter holds the seconds to wait, if provided }Custom transport
Bring your own HTTP client by implementing HttpClient:
use GoSuccess\Easybill\Client; use GoSuccess\Easybill\ClientConfig; use GoSuccess\Easybill\Http\HttpClient; use GoSuccess\Easybill\Http\Request; use GoSuccess\Easybill\Http\Response; final class GuzzleTransport implements HttpClient { public function send(Request $request): Response { /* ... */ } } $client = new Client( config: new ClientConfig(token: 'your-api-key'), httpClient: new GuzzleTransport(), );Documentation & examples
customers, contacts, customerGroups, documents, documentPayments,
positions, positionGroups, discountPositions, discountPositionGroups,
projects, tasks, textTemplates, timeTrackings, attachments, postBoxes,
sepaPayments, serialNumbers, stocks, logins, webhooks, pdfTemplates.
The data models, enums and filter objects are generated from a committed snapshot of the official Swagger specification (resources/swagger.json), and the plain resource classes from a declarative config:
php tools/generate-models.php # enums, DTOs and typed filters (from the snapshot) php tools/generate-resources.php # the plain CRUD/partial resource classes php tools/generate-docs.php # the per-method reference pages under docs/ composer cs-fix # apply code style composer check # php-cs-fixer + phpstan (level max) + phpunit
To refresh against the live API, update the snapshot first and then rerun the generators above:
curl -s https://api.easybill.de/rest/v1/swagger.json -o resources/swagger.json php tools/generate-models.php php tools/generate-resources.php php tools/generate-docs.php composer cs-fix
CI regenerates everything and fails if the committed output is out of date.
LicenseRunning the test suite additionally requires the
dom,xml,xmlwriter,mbstringandtokenizerPHP extensions (PHPUnit dependencies).
| # | Наименование новости | Тональность | Информативность | Дата публикации |
|---|---|---|---|---|
| 1 | shelfwatch/shelfwatch | 0 | 10 | 03-08-2026 |
| 2 | lumnd/platophp | 0 | 17.69 | 03-08-2026 |
| 3 | risetechapps/api-key-for-laravel | 0 | 100 | 03-08-2026 |
| 4 | djeventplannerhub/djep-php-sdk | 0 | 18.33 | 03-08-2026 |
| 5 | paylod 0.10.1 | 0 | 7 | 20-07-2026 |
| 6 | requestguard 0.1.6 | 0 | 5 | 20-07-2026 |
| 7 | anjan-talukdar/laravel-gst-invoice | 0 | 19.93 | 03-08-2026 |
| 8 | phpwind/phpwind | 0 | 10 | 03-08-2026 |
| 9 | Ship code safely at AI speed with Bits Release | 0 | 6.5 | 09-06-2026 |
| 10 | chrisdev/symfony-quality-cockpit (v0.1.2) | 0 | 16.25 | 28-07-2026 |