A Laravel package for the Bexio API, built with saloonphp/saloon as API connector and spatie/laravel-data for DTOs.
- PHP 8.2+
- Laravel 10.x, 11.x, 12.x, or 13.x
composer require gigerit/bexio-api-clientThe package will automatically register its service provider.
php artisan vendor:publish --tag=bexio-configThis will create a config/bexio.php configuration file.
Add your Bexio API credentials to your .env file:
# For Personal Access Token (simplest method)
BEXIO_ACCESS_TOKEN=your-access-token
# For OAuth2 (user-based authentication)
BEXIO_CLIENT_ID=your-client-id
BEXIO_CLIENT_SECRET=your-client-secret
BEXIO_REDIRECT_URI=https://your-app.com/bexio/callback
# Optional: persisted OAuth tokens
BEXIO_OAUTH_ACCESS_TOKEN=your-oauth-access-token
BEXIO_OAUTH_REFRESH_TOKEN=your-oauth-refresh-tokenuse Bexio\BexioClient;
use Bexio\Resources\Contacts\Contacts\Contact;
class ContactController extends Controller
{
public function index(BexioClient $client)
{
$contacts = Contact::useClient($client)->all();
return view('contacts.index', compact('contacts'));
}
public function show(BexioClient $client, int $id)
{
$contact = Contact::useClient($client)->find($id);
return view('contacts.show', compact('contact'));
}
}use Bexio\Facades\Bexio;
use Bexio\Resources\Contacts\Contacts\Contact;
// Get all contacts
$contacts = Contact::useClient(Bexio::getFacadeRoot())->all();
// Or resolve the client directly
$client = app('bexio');
$contacts = Contact::useClient($client)->all();Get a Contact by ID:
use Bexio\BexioClient;
use Bexio\Resources\Contacts\Contacts\Contact;
$client = app(BexioClient::class);
// Get the Contact with ID 1
$contact = Contact::useClient($client)->find(1);
// Access the Contact properties
echo $contact->id;
echo $contact->name_1;
echo $contact->mail;Get all Contacts:
use Bexio\BexioClient;
use Bexio\Resources\Contacts\Contacts\Contact;
$client = app(BexioClient::class);
// Get all Contacts
$contacts = Contact::useClient($client)->all();
// Access the Contacts
foreach ($contacts as $contact) {
echo $contact->id;
echo $contact->name_1;
echo $contact->mail;
}Create a Contact:
use Bexio\BexioClient;
use Bexio\Resources\Contacts\Contacts\Contact;
use Bexio\Resources\Contacts\Contacts\Enums\ContactType;
$client = app(BexioClient::class);
// Create a new Person Contact
$contact = new Contact(
contact_type_id: ContactType::PERSON,
name_1: 'Doe', // Last name
name_2: 'John', // First name
street_name: 'Main Street',
house_number: '123',
postcode: '8000',
city: 'Zurich',
country_id: 1,
mail: 'john.doe@example.com',
user_id: 1,
owner_id: 1,
);
// Save the Contact
$contact->attachClient($client)->save();Note: if you need to assign a contact title, use the titel_id field name. That matches the payload shape used by this package even though some Bexio docs refer to title_id.
Update a Contact:
use Bexio\BexioClient;
use Bexio\Resources\Contacts\Contacts\Contact;
$client = app(BexioClient::class);
// Get the Contact with ID 1
$contact = Contact::useClient($client)->find(1);
// Update the Contact properties
$contact->name_2 = 'Jane';
$contact->mail = 'jane.doe@example.com';
// Send the changes back to bexio
$contact->save();Search Contacts:
use Bexio\BexioClient;
use Bexio\Resources\Contacts\Contacts\Contact;
use Bexio\Support\Data\SearchCriteria;
$client = app(BexioClient::class);
// Search contacts with criteria
$contacts = Contact::useClient($client)
->query()
->where('name_1', SearchCriteria::LIKE, 'John')
->where('city', SearchCriteria::EQUAL, 'Zurich')
->get();For user-based authentication where users authenticate with their own Bexio account:
use Bexio\BexioAuth;
use Illuminate\Support\Str;
class BexioAuthController extends Controller
{
public function redirect()
{
$auth = new BexioAuth(
config('bexio.oauth.client_id'),
config('bexio.oauth.client_secret'),
config('bexio.oauth.redirect_uri')
);
$state = Str::random(40);
session()->put('bexio_state', $state);
$url = $auth->getAuthorizationUrl(
scopes: config('bexio.scopes'),
state: $state
);
return redirect($url);
}
}use Bexio\BexioAuth;
public function callback(Request $request)
{
$code = $request->get('code');
$state = $request->get('state');
if ($state !== session('bexio_state')) {
abort(403, 'Invalid state');
}
$auth = new BexioAuth(
config('bexio.oauth.client_id'),
config('bexio.oauth.client_secret'),
config('bexio.oauth.redirect_uri')
);
$authenticator = $auth->getAccessToken($code, $state, session('bexio_state'));
// Store the tokens (serialize the $authenticator or store individual values)
auth()->user()->update([
'bexio_access_token' => $authenticator->getAccessToken(),
'bexio_refresh_token' => $authenticator->getRefreshToken(),
'bexio_expires_at' => $authenticator->getExpiresAt(),
]);
return redirect()->route('dashboard');
}use Bexio\BexioAuth;
use DateTimeImmutable;
use Bexio\Resources\Contacts\Contacts\Contact;
use Bexio\BexioClient;
use Saloon\Http\Auth\AccessTokenAuthenticator;
public function getContacts()
{
$user = auth()->user();
$authService = new BexioAuth(
config('bexio.oauth.client_id'),
config('bexio.oauth.client_secret'),
config('bexio.oauth.redirect_uri')
);
$auth = new AccessTokenAuthenticator(
$user->bexio_access_token,
$user->bexio_refresh_token,
new DateTimeImmutable($user->bexio_expires_at)
);
if ($auth->hasExpired()) {
$auth = $authService->refreshAccessToken($auth);
$user->update([
'bexio_access_token' => $auth->getAccessToken(),
'bexio_refresh_token' => $auth->getRefreshToken(),
'bexio_expires_at' => $auth->getExpiresAt(),
]);
}
$client = new BexioClient($auth->getAccessToken());
return Contact::useClient($client)->all();
}For detailed documentation and advanced usage examples, see:
- Contacts Documentation - Comprehensive guide for Contacts, Contact Relations, Contact Groups, Contact Sectors, Additional Addresses, Salutations, and Titles
- Tests - Unit tests with practical examples
DTOs provide type hinting and autocompletion in the IDE, for a better development experience.

| Resource | Implemented |
|---|---|
| Contacts | ✅ |
| Contact Relations | ✅ |
| Contact Groups | ✅ |
| Contact Sectors | ✅ |
| Additional Addresses | ✅ |
| Salutations | ✅ |
| Titles | ✅ |
| Resource | Implemented |
|---|---|
| Quotes | ✅ |
| Orders | ✅ |
| Deliveries | ✅ |
| Invoices | ✅ |
| Document Settings | ✅ |
| Comments | ✅ |
| Default positions | ✅ |
| Item positions | ✅ |
| Text positions | ✅ |
| Subtotal positions | ✅ |
| Discount positions | ✅ |
| Pagebreak positions | ✅ |
| Sub positions | ✅ |
| Document templates | ✅ |
| Resource | Implemented |
|---|---|
| Bills | ✅ |
| Expenses | ✅ |
| Purchase Orders | ✅ |
| Outgoing Payment | ✅ |
| Resource | Implemented |
|---|---|
| Accounts | ✅ |
| Account Groups | ✅ |
| Calendar Years | ✅ |
| Business Years | ✅ |
| Currencies | ✅ |
| Manual Entries | ✅ |
| Reports | ✅ |
| Taxes | ✅ |
| Vat Periods | ✅ |
| Resource | Implemented |
|---|---|
| Bank Accounts | ✅ |
| IBAN Payments | ✅ |
| QR Payments | ✅ |
| Payments | ✅ |
| Resource | Implemented |
|---|---|
| Items | ✅ |
| Stock locations | ✅ |
| Stock Areas | ✅ |
| Resource | Implemented |
|---|---|
| Projects | ✅ |
| Timesheets | ✅ |
| Business Activities | ✅ |
| Communication Types | ✅ |
| Resource | Implemented |
|---|---|
| Files | ✅ |
| Resource | Implemented |
|---|---|
| Company Profile | ✅ |
| Countries | ✅ |
| Languages | ✅ |
| Notes | ✅ |
| Payment Types | ✅ |
| Permissions | ✅ |
| Tasks | ✅ |
| Units | ✅ |
| User Management | ✅ |
composer testMIT License - see the LICENSE file for details.