xavicabot/laravel-holded

PHP client for the Holded API, ready for Laravel

Maintainers

Package info

github.com/xavicabot/laravel-holded

pkg:composer/xavicabot/laravel-holded

Statistics

Installs: 450

Dependents: 0

Suggesters: 0

Stars: 2

Open Issues: 0

v3.0 2026-03-17 17:10 UTC

README

PHP client for the Holded API, designed for Laravel 11 and 12.

Requirements

Laravel PHP
11.x ^8.2
12.x ^8.2

Installation

composer require xavicabot/laravel-holded

Publish the configuration file:

php artisan vendor:publish --tag=config --provider="XaviCabot\Laravel\Holded\HoldedServiceProvider"

Add to your .env:

HOLDED_API=your_api_key

Usage

use XaviCabot\Laravel\Holded\Facades\Holded;

Contacts

$contacts = Holded::contact()->list();
$contact  = Holded::contact()->get('contactId');
$new      = Holded::contact()->create(['name' => 'Acme Inc']);
Holded::contact()->update('contactId', ['name' => 'Acme Corp']);
Holded::contact()->delete('contactId');

You can also use the ContactData DTO for validation and normalization:

use XaviCabot\Laravel\Holded\DTOs\ContactData;

$data = new ContactData(
    name: 'Acme Inc',
    email: 'info@acme.com',
    type: 'client',
    countryCode: 'ES',
);

Holded::contact()->create($data);

Documents

use XaviCabot\Laravel\Holded\Enums\DocumentType;

$invoices = Holded::document()->list(page: 1, docType: DocumentType::INVOICE);
$invoice  = Holded::document()->get('docId', DocumentType::INVOICE);
Holded::document()->delete('docId', DocumentType::INVOICE);

Create a document using the DocumentData DTO:

use XaviCabot\Laravel\Holded\DTOs\DocumentData;

$data = new DocumentData(
    date: new DateTime(),
    contactName: 'Acme Inc',
    items: [
        ['description' => 'Consulting', 'quantity' => 10, 'price' => 75.00],
    ],
);

Holded::document()->create($data, DocumentType::INVOICE);

Available document types: INVOICE, SALES_RECEIPT, CREDIT_NOTE, SALES_ORDER, PROFORM, WAYBILL, ESTIMATE, PURCHASE, PURCHASE_ORDER, PURCHASE_REFUND.

Attach files

Holded::document()->attach('docId', '/path/to/file.pdf', setMain: true, docType: DocumentType::PURCHASE);

Get document PDF

$response = Holded::document()->pdf('docId', DocumentType::INVOICE);
$pdfContent = base64_decode($response['data']);

Multi-account

Define accounts in config/holded.php:

'accounts' => [
    'main' => [
        'api_key' => env('HOLDED_API_MAIN'),
        'base_url' => env('HOLDED_API_URL_MAIN', 'https://api.holded.com/api/invoicing/v1/'),
    ],
],

Then use them:

Holded::account('main')->contact()->list();

Or set credentials at runtime:

Holded::withCredentials('API_KEY', 'https://api.holded.com/api/invoicing/v1/')
    ->contact()
    ->list();

Error handling

All API errors throw a HoldedException with access to status code and response body:

use XaviCabot\Laravel\Holded\Exceptions\HoldedException;

try {
    Holded::contact()->get('invalid-id');
} catch (HoldedException $e) {
    $e->getStatusCode();    // 404
    $e->getResponseBody();  // ['message' => '...']
    $e->isRateLimited();    // true on 429
    $e->isServerError();    // true on 5xx
    $e->isClientError();    // true on 4xx
}

Rate-limited (429) and server error (5xx) requests are automatically retried with backoff. Configure in config/holded.php:

'retry' => [
    'max_retries' => 3,
    'retry_delay' => 1000, // milliseconds
    'retry_on_server_error' => true,
],

Testing

composer test

License

MIT