Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Moneybird Php Api Laravel Package

bluetools/moneybird-php-api

A PHP client for the Moneybird accounting API with a Laravel service provider and facade. Manage contacts, invoices, payments and other resources through a clean, fluent interface, with typed resources, pagination and authentication handled for you.

View on GitHub
Deep Wiki
Context7

Getting Started

Install with Composer (check the composer.json on the repo — the package is archived, so you may need to pin a version that matches your Guzzle constraint):

composer require bluetools/moneybird-php-api

Grab a personal API token from MoneyBird: Settings → API tokens, and note the administration ID you want to work against (visible in the MoneyBird URL: .../administrations/<id>/...).

Wire the client up — the package is a thin wrapper around Guzzle, so instantiation is just "token in, client out":

use Moneybird\Api\Api;

$api = new Api\Api($token);
// or, depending on version:
// $api = new Api('token');

Look here first, in this order:

  1. README.md — the only trustworthy source of method names, because the archived package predates several MoneyBird API changes.
  2. src/Api/ — the entry point class and how it wires services.
  3. src/Service/ (or src/Service/*Service.php) — the per-resource classes (Contacts, SalesInvoices, Invoices, Payments, Products, TimeEntries, Documents). These tell you exactly which endpoints are wrapped.
  4. src/Object/ / src/Entity/ — the typed wrappers around JSON responses.

First use case: pull contacts and create a sales invoice. This is the 80% case for a Dutch SMB integration:

$contacts = $api->contacts()->all();
$invoices = $api->salesInvoices()->all();

If that works, you're past the hard part. If all() returns an entity collection rather than plain arrays, iterate and inspect the first item before writing any mapping code.

Implementation Patterns

Bind the client as a singleton in the container. The token and administration context are per-request state; do not new it up in every controller.

// AppServiceProvider
$this->app->singleton(\Moneybird\Api\Api::class, fn () => new \Moneybird\Api\Api(
    config('services.moneybird.token')
));

Wrap the library behind your own repository. Every call site should go through MoneybirdContactRepository, MoneybirdInvoiceRepository, etc. The reason is not abstraction purity — it is that this package is archived and you will eventually need to swap it for the first-party SDK or raw Guzzle calls. Confining the blast radius to three classes makes that a one-day job instead of a three-week one.

class MoneybirdInvoiceRepository
{
    public function __construct(private \Moneybird\Api\Api $api) {}

    public function find(string $id): array
    {
        return $this->api->salesInvoices()->get($id);
    }

    public function create(array $payload): array
    {
        return $this->api->salesInvoices()->create($payload);
    }
}

Treat responses as DTOs, not arrays. The entity objects returned by the package expose attributes via magic getters or a toArray()-style method. Convert once at the boundary into your own DTOs so nothing downstream depends on the package's shape.

Use the package for the write path, raw HTTP for the read-heavy paths. Listing thousands of invoices via the typed wrapper is slow and memory-hungry; paginate with small per_page values, or fall back to a direct Guzzle call for reporting endpoints the wrapper does not cover (there are several — the package is incomplete by modern standards).

Synchronisation pattern for invoices → payments:

  1. On a schedule, fetch sales invoices with a filter on state:open or updated_at where supported.
  2. For each, fetch $api->salesInvoices()->payments($id) (or the equivalent nested service) and reconcile against your local ledger.
  3. Persist a moneybird_id + updated_at pair locally as your idempotency key — MoneyBird will give you duplicate webhook/retry traffic.

Queue every write. MoneyBird enforces rate limits and returns 429 under load. Wrap creates/updates in a job with ->backoff([10, 60, 300]) and ->tries(5). Do not let a customer-facing HTTP request block on a MoneyBird call.

Testing: point the underlying Guzzle handler at a MockHandler (or use Http::fake() if you have abstracted to Laravel's client). Do not hit the real sandbox in CI — MoneyBird sandboxes are shared and mutable, so tests will interfere with each other.

Gotchas and Tips

  • It is archived. Last commit is years old, there are zero dependents, and MoneyBird's API has moved on. Pin the version, vendor the package into packages/ if you want to patch it, and budget for replacement. Treat it as scaffolding for a working integration, not a long-term dependency.
  • Administration scoping is the #1 source of 404s. Most endpoints require an administration context. Depending on the version, this is either a constructor argument, a setAdministration() call, or a path segment the wrapper assembles for you. Read src/Api/ before debugging a mysterious 404 Not Found.
  • invoices vs salesInvoices vs purchaseInvoices. MoneyBird distinguishes these hard. Sending a purchase invoice payload to the sales endpoint produces a validation error that reads like a schema problem; it is not, it is the wrong endpoint.
  • Nested payloads must match field-for-field. Invoice lines (details_attributes), tax rates, and ledger accounts all need exact MoneyBird field names and, frequently, the ID of the tax rate/ledger, not the human-readable name. Cache these lookup lists locally — they rarely change and fetching them on every invoice creation is wasteful.
  • Money is a string, not a float. MoneyBird returns "123.45". Never (float) cast before arithmetic; use bcmath or an integer-cents value object.
  • Timestamps are ISO 8601 with offset (2024-01-15T10:30:00+01:00). Parse with Carbon::parse(), store as UTC, and be aware MoneyBird reports in the administration's timezone.
  • Exceptions are \Exception-based, not granular. Catch broadly around every call and log the response body — the package does not expose response bodies cleanly, so you may need to catch the Guzzle exception underneath it.
  • Pagination state is on the wrapper, not the response. If the service exposes nextPage() / hasMore(), check the README for the exact idiom; guessing here costs an afternoon.
  • Extension point: if you must extend, subclass the service or decorate the Api instance and add your own method that builds the Guzzle request. Do not fork the package in-place unless you are prepared to maintain a vendored copy.
  • Rate limits: MoneyBird is generous but not infinite (~150 req/min per token in practice). Batch your sync jobs and back off on 429 rather than hammering through all().
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource