with one click
bexio-api-client-development
// Use when building or maintaining integrations with gigerit/bexio-api-client, including auth, resources, query builders, sales document flows, and package conventions.
// Use when building or maintaining integrations with gigerit/bexio-api-client, including auth, resources, query builders, sales document flows, and package conventions.
[HINT] Download the complete skill directory including SKILL.md and all related files
| name | bexio-api-client-development |
| description | Use when building or maintaining integrations with gigerit/bexio-api-client, including auth, resources, query builders, sales document flows, and package conventions. |
Use this skill when working with the gigerit/bexio-api-client package itself or when
generating code that consumes the package in a Laravel app.
Use it for:
gigerit/bexio-api-client^8.2, Laravel ^10|^11|^12|^13saloonphp/saloonspatie/laravel-dataBexio\BexioClient for https://api.bexio.comBexio\BexioAuth for OAuth against https://auth.bexio.com/realms/bexio/protocol/openid-connectInstall:
composer require gigerit/bexio-api-client
php artisan vendor:publish --tag=bexio-config
Basic auth with a personal access token:
BEXIO_ACCESS_TOKEN=your-access-token
OAuth auth:
BEXIO_CLIENT_ID=your-client-id
BEXIO_CLIENT_SECRET=your-client-secret
BEXIO_REDIRECT_URI=https://your-app.com/bexio/callback
# optional persisted tokens
BEXIO_OAUTH_ACCESS_TOKEN=...
BEXIO_OAUTH_REFRESH_TOKEN=...
Resolve the client from the container or facade:
use Bexio\BexioClient;
use Bexio\Facades\Bexio;
$client = app(BexioClient::class);
$sameClient = Bexio::getFacadeRoot();
Resources extend Bexio\Resources\Resource and are used through a client-bound instance.
Read:
use Bexio\Resources\Contacts\Contacts\Contact;
$contacts = Contact::useClient($client)->all();
$contact = Contact::useClient($client)->find(1);
Create or update:
use Bexio\Resources\Contacts\Contacts\Contact;
use Bexio\Resources\Contacts\Contacts\Enums\ContactType;
$contact = new Contact(
contact_type_id: ContactType::PERSON,
name_1: 'Doe',
name_2: 'Jane',
city: 'Zurich',
country_id: 1,
user_id: 1,
owner_id: 1,
);
$saved = $contact->attachClient($client)->save();
$saved->mail = 'jane@example.com';
$saved->save();
Delete:
$saved->delete();
$resource->except(...)->toArray() inside request or resource helpers.titel_id, not title_id.Prefer package enums over raw API IDs or strings whenever an enum exists for the field. This is especially important for statuses and typed values in consuming Laravel apps.
Examples:
InvoiceStatus, OrderStatus, and QuoteStatus with sales document status filtersBillStatus and PurchaseOrderStatus for purchase status fields/actionsContactType, ItemPositionType, and SearchCriteria instead of memorizing API constantsRaw IDs or strings are acceptable only when the package has no enum for that API value yet or a method explicitly documents support for custom values.
Use ->query() for fluent reads.
Shared methods:
limit()offset()forPage()orderBy()when()get()first()Searchable resources also support:
where()whereIn()whereNull()whereNotNull()whereBetween()Use get() for both index and filtered queries. Do not use search(); that API was
removed in the current major version.
Example:
use Bexio\Resources\Contacts\Contacts\Contact;
use Bexio\Support\Data\SearchCriteria;
$contacts = Contact::useClient($client)
->query()
->where('name_1', SearchCriteria::LIKE, 'John')
->where('city', SearchCriteria::EQUAL, 'Zurich')
->orderBy('updated_at', 'desc')
->forPage(1, 50)
->get();
Implementation notes:
Bexio\Support\QueryBuilder is the base for index-style requests.Bexio\Support\SearchableQueryBuilder automatically switches to the resource search request
once any where* clause is added.Invoices, orders, and quotes share sales document query patterns, but each document type has a few API-specific payload rules.
Use:
Bexio\Resources\Sales\Invoices\InvoiceBexio\Resources\Sales\Invoices\InvoiceQueryBuilderBexio\Resources\Sales\Orders\OrderBexio\Resources\Sales\Orders\OrderQueryBuilderBexio\Resources\Sales\Quotes\QuoteBexio\Resources\Sales\Quotes\QuoteQueryBuilderShared sales document query helpers:
status()statusIn()validFrom()validTo()validBetween()Pass the matching status enum to status helpers instead of numeric kb_item_status_id values.
Example:
use Bexio\Resources\Sales\Invoices\Invoice;
use Bexio\Resources\Sales\Invoices\Enums\InvoiceStatus;
$invoices = Invoice::useClient($client)
->query()
->status(InvoiceStatus::DRAFT)
->validBetween('2026-01-01', '2026-01-31')
->orderBy('updated_at', 'desc')
->forPage(1, 25)
->get();
Invoice payload rules:
Invoice::createFromApiPayload() backfills invoice_date from is_valid_from when needed.Invoice::toApi() removes reporting and response-only fields before writes.document_nr and mwst_is_net out of create/update payloads.Item payload rules:
article_type_id as null.article_type_id or route id in /2.0/article create/update payloads.Order and quote query rules:
GET /2.0/kb_order; filtered orders use POST /2.0/kb_order/search.GET /2.0/kb_offer; filtered quotes use POST /2.0/kb_offer/search.is_valid_until; do not use is_valid_to for quote helpers.OrderStatus maps pending 5, done 6, partial 15, and canceled 21.Sales document item-position rules:
ItemPositionArticle are created as an empty document
first, then positions are added through dedicated item-position endpoints. Some Bexio widget
schemas reject inline article_id on sales document create endpoints.ItemPositionArticle omits article_id and parent_id; delete and recreate
the article position to change the linked item.positions; manage positions through item-position endpoints.Purchase payload rules:
toApi() helpers so hydrated DTOs do not
resend response-only IDs, document numbers, statuses, totals, timestamps, or embedded response
objects.Conversion rules:
Quote::createOrder(), Quote::createInvoice(), Order::createDelivery(), and
Order::createInvoice() call Bexio conversion endpoints.positions for
package-created source documents.AdditionalAddress requires contact context; use ->forContact($contactId) before get() or filtered queries.InvoiceReminder requires invoice context; use ->forInvoice($invoiceId) before get() or
filtered queries, and keep kb_invoice_id available for find() and delete().ManualEntry has no documented/live show endpoint; use index queries for reads. find() and
refresh() intentionally throw instead of calling /3.0/accounting/manual_entries/{id}./2.0/... and /3.0/...); always match
neighboring request classes instead of assuming one global API version.HasOfficeLink must define a matching SHOW_URL constant.When adding a resource feature, mirror the existing package structure:
src/Resources/...Requests/ directorytests/Resources/... or tests/Unit/... pathPrefer:
Resource helpers for CRUDSearchableQueryBuilder for resources with separate search endpointsBexio\Support\Requests\SearchRequest for search requests that only need the shared POST JSON
searchClauses body behaviorforContact() or invoice status/date methods## Available Resources updates whenever endpoint implementation status changescomposer test
composer test:types
testClient() from tests/Pest.php.first() or orderBy('id', 'desc')
returns the record created by the current test.useClient() for static reads and attachClient() for instance writes.get() or first().AGENTS.md, README usage, and any Boost skill content.