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
bexio-api-client-development
bexio-api-client-development
gigerit/bexio-api-client
claude-code
bexio
api-client
oauth
saloon
dto
authentication
resources

Use when building or maintaining integrations with gigerit/bexio-api-client, including auth, resources, query builders, sales document flows, and package conventions.

Install
php artisan boost:add-skill gigerit/bexio-api-client

Save this content to: .claude/skills/bexio-api-client-development/SKILL.md

# Bexio API Client Development

## When to use this skill
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:
- configuring Bexio authentication
- reading, creating, updating, and deleting Bexio resources
- using resource query builders and search filters
- implementing new resources, requests, DTO fields, or package tests
- working with sales document payloads, item positions, conversions, and query behavior

## Package overview
- Package: `gigerit/bexio-api-client`
- Runtime: PHP `^8.2`, Laravel `^10|^11|^12|^13`
- HTTP layer: `saloonphp/saloon`
- DTO layer: `spatie/laravel-data`
- Main connectors:
  - `Bexio\BexioClient` for `https://api.bexio.com`
  - `Bexio\BexioAuth` for OAuth against `https://auth.bexio.com/realms/bexio/protocol/openid-connect`

## Installation and configuration

Install:

```bash
composer require gigerit/bexio-api-client
php artisan vendor:publish --tag=bexio-config
```

Basic auth with a personal access token:

```env
BEXIO_ACCESS_TOKEN=your-access-token
```

OAuth auth:

```env
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=...
```

Revoke OAuth tokens through `BexioAuth`. A redirect URI is not required for revocation:

```php
use Bexio\BexioAuth;

$auth = new BexioAuth($clientId, $clientSecret);
$auth->revokeToken($refreshToken, 'refresh_token');
```

For disconnect flows, revoke the stored refresh token first and clear persisted access token, refresh token, and expiry values only after the call succeeds. Revoking an access token alone does not end the renewable refresh-token session. The hint is advisory, and this API does not revoke personal access tokens.

Resolve the client from the container or facade:

```php
use Bexio\BexioClient;
use Bexio\Facades\Bexio;

$client = app(BexioClient::class);
$sameClient = Bexio::getFacadeRoot();
```

## Core usage pattern
Resources extend `Bexio\Resources\Resource` and are used through a client-bound instance.

Read:

```php
use Bexio\Resources\Contacts\Contacts\Contact;

$contacts = Contact::useClient($client)->all();
$contact = Contact::useClient($client)->find(1);
```

Create or update:

```php
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 = '[email protected]';
$saved->save();
```

Delete:

```php
$saved->delete();
```

## DTO and payload conventions
- Constructor-promoted public properties are usually create/update payload fields.
- Response-only fields should usually be plain public properties outside the constructor.
- Outgoing payloads are commonly filtered with `$resource->except(...)->toArray()` inside request or resource helpers.
- Do not move response-only API fields into constructor-promoted properties unless the API accepts them on write.
- Preserve package naming even when Bexio docs differ. Example: contacts use `titel_id`, not `title_id`.

## Enum usage
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:
- use `InvoiceStatus`, `OrderStatus`, and `QuoteStatus` with sales document status filters
- use `BillStatus` and `PurchaseOrderStatus` for purchase status fields/actions
- use enums such as `ContactType`, `ItemPositionType`, and `SearchCriteria` instead of memorizing API constants

Raw 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.

## Query builder usage
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:

```php
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.
- Resource-specific builders should stay thin and add only domain helpers or route context.

## Sales document guidance
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\Invoice`
- `Bexio\Resources\Sales\Invoices\InvoiceQueryBuilder`
- `Bexio\Resources\Sales\Orders\Order`
- `Bexio\Resources\Sales\Orders\OrderQueryBuilder`
- `Bexio\Resources\Sales\Quotes\Quote`
- `Bexio\Resources\Sales\Quotes\QuoteQueryBuilder`

Shared 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:

```php
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();
```

Sales document payload rules:
- `Invoice::createFromApiPayload()` backfills `invoice_date` from `is_valid_from` when needed.
- Invoices, orders, quotes, and item positions build request bodies with
  `toWritePayload(WriteOperation::CREATE|UPDATE)`, which drops response-only fields, so a fetched
  document or position can be written again. `toApi()`, `toUpdateApi()`, `toApiPayload()`, and
  `toCreateApiPayload()` are deprecated.
- `document_nr` is never written; Bexio assigns it while automatic numbering is on.
- Invoice, order, and quote creation preserve explicit `mwst_is_net: false` (gross prices) and
  `true` (net prices). Leave it unset or `null` to use the Bexio default; null is never sent. This
  setting affects totals when `mwst_type` is `MwstType::INCLUDING`.

Item payload rules:
- Bexio item responses may hydrate `article_type_id` as `null`.
- Do not send `article_type_id` or route `id` in `/2.0/article` create/update payloads.

Order and quote query rules:
- Unfiltered orders use `GET /2.0/kb_order`; filtered orders use `POST /2.0/kb_order/search`.
- Unfiltered quotes use `GET /2.0/kb_offer`; filtered quotes use `POST /2.0/kb_offer/search`.
- Quote validity searches use API field `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:
- Orders, quotes, and invoices containing `ItemPositionArticle` or `ItemPositionSubposition` are
  created as an empty document first, then every position is added in order through dedicated
  item-position endpoints. Bexio rejects inline subpositions.
- A document create rejects positions with a `parent_id` before sending. Create the document, then
  `attachTo()` the created parent and `createFor()` the document.
- Updating an existing `ItemPositionArticle` omits `article_id` and `parent_id`; delete and recreate
  the article position to change the linked item.
- Quotes and orders accept `is_optional` on every position type; invoice position writes omit it.
- Sales document update payloads strip `positions`; manage positions through item-position endpoints.

Purchase payload rules:
- Bill and purchase-order create/update requests use `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.
- When callers omit conversion positions, the package derives explicit source-position payloads
  from the source document because live Bexio rejects empty or null `positions` for
  package-created source documents.

## Nested and special-case resources
- `DocumentTemplate::slug` maps the API's `template_slug`; use the public `slug`
  value when selecting a template for a sales document.
- Note/task `project_id` maps `pr_project_id` in both directions. Task `has_reminder`
  normalizes the live string booleans and writes `have_remember`; updates still
  require reminder type/time before sending that flag.
- Leave project `document_nr` unset for automatic numbering. Null is omitted on
  create/update because the live form rejects it; explicit manual numbers are retained.
- Milestone updates use PATCH: the documented POST method returns 404 on the live
  API. Preserve the request method and project context when integrating milestones.
- Bank account postal/clearing identifiers can be strings; preserve leading zeros.
  Accounting response metadata is documented in `docs/resources/accounting/response-metadata.md`.
- For bills, expenses, and purchase outgoing payments, consult
  `docs/resources/purchase/scopes.md`: their OAuth requirements differ from banking
  payments and vary by operation. Use the existing `ApiScope` values.
- Payroll paystubs: use `Payroll\Documents\Requests\DownloadPaystubPdfRequest` and
  read `$response->body()` for PDF bytes. The older `GetPaystubPdfRequest` returns
  a location DTO and is deprecated upstream. Both require `ApiScope::PAYROLL_PAYSTUB_SHOW`.
- `AdditionalAddress` requires contact context; use `->forContact($contactId)` before `get()` or filtered queries.
- `AdditionalAddress::name_addition` is the optional second name line. Search and update
  results retain contact context for subsequent instance operations.
- VAT periods expose API `start`/`end` as `date_from`/`date_to`; their read-only `type`
  supports `quarter`, `semester`, and `annual`, with `closed_at` available when closed.
- `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}`.
- Some resources use different endpoint versions (`/2.0/...` and `/3.0/...`); always match
  neighboring request classes instead of assuming one global API version.
- Resources using `HasOfficeLink` must define a matching `SHOW_URL` constant.

## Adding or changing package resources
When adding a resource feature, mirror the existing package structure:

1. DTO in `src/Resources/...`
2. Request classes in a local `Requests/` directory
3. Optional resource-specific query builder or clause DTO only if needed
4. Tests in the matching `tests/Resources/...` or `tests/Unit/...` path

Prefer:
- base `Resource` helpers for CRUD
- `SearchableQueryBuilder` for resources with separate search endpoints
- `Bexio\Support\Requests\SearchRequest` for search requests that only need the shared POST JSON
  `searchClauses` body behavior
- resource-specific builder sugar only for context-specific helpers like `forContact()` or invoice status/date methods
- README `## Available Resources` updates whenever endpoint implementation status changes

## Additional verified wire behavior

- Contact reads accept `title_id` as public `titel_id`; create/update/bulk omit legacy `address` and response metadata.
- Timesheet writes use `tracking`; computed date, duration, running and travel fields are omitted.
- Quote manual addresses use `contact_address_manual` / `delivery_address_manual`; write project context as `pr_project_id` and read `project_id`.
- Position DTOs retain calculated totals and quantities. Dedicated creates retain article/parent IDs where supported; updates omit immutable parent IDs.
- V4 banking payments use UUIDs for instance operations, JSON numbers for amounts, and `type` on update. QR writes omit `allowance`; reference and additional-information fields remain writable.
- Bill payloads preserve line/discount IDs on update and omit them, calculated line taxes, and create-incompatible metadata on create. Use request classes or `toApiPayload()`.
- Outgoing purchase-payment lists require `query()->forBill($billId)`. Only IBAN/QR payments can be updated remotely.
- Payroll absence DTOs retain employee route context. Successful employee/absence HTTP 204 updates return the submitted DTO.

## Testing guidance
- Test stack: Pest + Orchestra Testbench
- Main commands:

```bash
composer test
composer test:types
```

- Use real API coverage for API-facing package features through `testClient()` from `tests/Pest.php`.
- Prefer creating disposable remote records inside tests for write-capable endpoints.
- For read-only endpoints, fetch a small live dataset and skip only when the remote account
  genuinely has no compatible data.
- Use mock responses only for narrow unit tests like DTO normalization or request-construction behavior.
- Live tests may run in parallel against one Bexio account. When testing a specific record, scope
  queries by unique fixture data instead of assuming unfiltered `first()` or `orderBy('id', 'desc')`
  returns the record created by the current test.

## Best practices for AI-generated code
- Bind a client with `useClient()` for static reads and `attachClient()` for instance writes.
- Reuse existing request and DTO patterns from the nearest resource instead of inventing new ones.
- Keep query-builder APIs fluent and Laravel-like; collection retrieval should end with `get()` or `first()`.
- If adding response-only fields, update outgoing payload filtering and relevant tests together.
- When changing package architecture or conventions, also update package guidance such as
  `AGENTS.md`, README usage, and any Boost skill content.

Bexio API Client Development

When to use this skill

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:

  • configuring Bexio authentication
  • reading, creating, updating, and deleting Bexio resources
  • using resource query builders and search filters
  • implementing new resources, requests, DTO fields, or package tests
  • working with sales document payloads, item positions, conversions, and query behavior

Package overview

  • Package: gigerit/bexio-api-client
  • Runtime: PHP ^8.2, Laravel ^10|^11|^12|^13
  • HTTP layer: saloonphp/saloon
  • DTO layer: spatie/laravel-data
  • Main connectors:
    • Bexio\BexioClient for https://api.bexio.com
    • Bexio\BexioAuth for OAuth against https://auth.bexio.com/realms/bexio/protocol/openid-connect

Installation and configuration

Install:

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=...

Revoke OAuth tokens through BexioAuth. A redirect URI is not required for revocation:

use Bexio\BexioAuth;

$auth = new BexioAuth($clientId, $clientSecret);
$auth->revokeToken($refreshToken, 'refresh_token');

For disconnect flows, revoke the stored refresh token first and clear persisted access token, refresh token, and expiry values only after the call succeeds. Revoking an access token alone does not end the renewable refresh-token session. The hint is advisory, and this API does not revoke personal access tokens.

Resolve the client from the container or facade:

use Bexio\BexioClient;
use Bexio\Facades\Bexio;

$client = app(BexioClient::class);
$sameClient = Bexio::getFacadeRoot();

Core usage pattern

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 = '[email protected]';
$saved->save();

Delete:

$saved->delete();

DTO and payload conventions

  • Constructor-promoted public properties are usually create/update payload fields.
  • Response-only fields should usually be plain public properties outside the constructor.
  • Outgoing payloads are commonly filtered with $resource->except(...)->toArray() inside request or resource helpers.
  • Do not move response-only API fields into constructor-promoted properties unless the API accepts them on write.
  • Preserve package naming even when Bexio docs differ. Example: contacts use titel_id, not title_id.

Enum usage

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:

  • use InvoiceStatus, OrderStatus, and QuoteStatus with sales document status filters
  • use BillStatus and PurchaseOrderStatus for purchase status fields/actions
  • use enums such as ContactType, ItemPositionType, and SearchCriteria instead of memorizing API constants

Raw 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.

Query builder usage

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.
  • Resource-specific builders should stay thin and add only domain helpers or route context.

Sales document guidance

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\Invoice
  • Bexio\Resources\Sales\Invoices\InvoiceQueryBuilder
  • Bexio\Resources\Sales\Orders\Order
  • Bexio\Resources\Sales\Orders\OrderQueryBuilder
  • Bexio\Resources\Sales\Quotes\Quote
  • Bexio\Resources\Sales\Quotes\QuoteQueryBuilder

Shared 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();

Sales document payload rules:

  • Invoice::createFromApiPayload() backfills invoice_date from is_valid_from when needed.
  • Invoices, orders, quotes, and item positions build request bodies with toWritePayload(WriteOperation::CREATE|UPDATE), which drops response-only fields, so a fetched document or position can be written again. toApi(), toUpdateApi(), toApiPayload(), and toCreateApiPayload() are deprecated.
  • document_nr is never written; Bexio assigns it while automatic numbering is on.
  • Invoice, order, and quote creation preserve explicit mwst_is_net: false (gross prices) and true (net prices). Leave it unset or null to use the Bexio default; null is never sent. This setting affects totals when mwst_type is MwstType::INCLUDING.

Item payload rules:

  • Bexio item responses may hydrate article_type_id as null.
  • Do not send article_type_id or route id in /2.0/article create/update payloads.

Order and quote query rules:

  • Unfiltered orders use GET /2.0/kb_order; filtered orders use POST /2.0/kb_order/search.
  • Unfiltered quotes use GET /2.0/kb_offer; filtered quotes use POST /2.0/kb_offer/search.
  • Quote validity searches use API field 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:

  • Orders, quotes, and invoices containing ItemPositionArticle or ItemPositionSubposition are created as an empty document first, then every position is added in order through dedicated item-position endpoints. Bexio rejects inline subpositions.
  • A document create rejects positions with a parent_id before sending. Create the document, then attachTo() the created parent and createFor() the document.
  • Updating an existing ItemPositionArticle omits article_id and parent_id; delete and recreate the article position to change the linked item.
  • Quotes and orders accept is_optional on every position type; invoice position writes omit it.
  • Sales document update payloads strip positions; manage positions through item-position endpoints.

Purchase payload rules:

  • Bill and purchase-order create/update requests use 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.
  • When callers omit conversion positions, the package derives explicit source-position payloads from the source document because live Bexio rejects empty or null positions for package-created source documents.

Nested and special-case resources

  • DocumentTemplate::slug maps the API's template_slug; use the public slug value when selecting a template for a sales document.
  • Note/task project_id maps pr_project_id in both directions. Task has_reminder normalizes the live string booleans and writes have_remember; updates still require reminder type/time before sending that flag.
  • Leave project document_nr unset for automatic numbering. Null is omitted on create/update because the live form rejects it; explicit manual numbers are retained.
  • Milestone updates use PATCH: the documented POST method returns 404 on the live API. Preserve the request method and project context when integrating milestones.
  • Bank account postal/clearing identifiers can be strings; preserve leading zeros. Accounting response metadata is documented in docs/resources/accounting/response-metadata.md.
  • For bills, expenses, and purchase outgoing payments, consult docs/resources/purchase/scopes.md: their OAuth requirements differ from banking payments and vary by operation. Use the existing ApiScope values.
  • Payroll paystubs: use Payroll\Documents\Requests\DownloadPaystubPdfRequest and read $response->body() for PDF bytes. The older GetPaystubPdfRequest returns a location DTO and is deprecated upstream. Both require ApiScope::PAYROLL_PAYSTUB_SHOW.
  • AdditionalAddress requires contact context; use ->forContact($contactId) before get() or filtered queries.
  • AdditionalAddress::name_addition is the optional second name line. Search and update results retain contact context for subsequent instance operations.
  • VAT periods expose API start/end as date_from/date_to; their read-only type supports quarter, semester, and annual, with closed_at available when closed.
  • 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}.
  • Some resources use different endpoint versions (/2.0/... and /3.0/...); always match neighboring request classes instead of assuming one global API version.
  • Resources using HasOfficeLink must define a matching SHOW_URL constant.

Adding or changing package resources

When adding a resource feature, mirror the existing package structure:

  1. DTO in src/Resources/...
  2. Request classes in a local Requests/ directory
  3. Optional resource-specific query builder or clause DTO only if needed
  4. Tests in the matching tests/Resources/... or tests/Unit/... path

Prefer:

  • base Resource helpers for CRUD
  • SearchableQueryBuilder for resources with separate search endpoints
  • Bexio\Support\Requests\SearchRequest for search requests that only need the shared POST JSON searchClauses body behavior
  • resource-specific builder sugar only for context-specific helpers like forContact() or invoice status/date methods
  • README ## Available Resources updates whenever endpoint implementation status changes

Additional verified wire behavior

  • Contact reads accept title_id as public titel_id; create/update/bulk omit legacy address and response metadata.
  • Timesheet writes use tracking; computed date, duration, running and travel fields are omitted.
  • Quote manual addresses use contact_address_manual / delivery_address_manual; write project context as pr_project_id and read project_id.
  • Position DTOs retain calculated totals and quantities. Dedicated creates retain article/parent IDs where supported; updates omit immutable parent IDs.
  • V4 banking payments use UUIDs for instance operations, JSON numbers for amounts, and type on update. QR writes omit allowance; reference and additional-information fields remain writable.
  • Bill payloads preserve line/discount IDs on update and omit them, calculated line taxes, and create-incompatible metadata on create. Use request classes or toApiPayload().
  • Outgoing purchase-payment lists require query()->forBill($billId). Only IBAN/QR payments can be updated remotely.
  • Payroll absence DTOs retain employee route context. Successful employee/absence HTTP 204 updates return the submitted DTO.

Testing guidance

  • Test stack: Pest + Orchestra Testbench
  • Main commands:
composer test
composer test:types
  • Use real API coverage for API-facing package features through testClient() from tests/Pest.php.
  • Prefer creating disposable remote records inside tests for write-capable endpoints.
  • For read-only endpoints, fetch a small live dataset and skip only when the remote account genuinely has no compatible data.
  • Use mock responses only for narrow unit tests like DTO normalization or request-construction behavior.
  • Live tests may run in parallel against one Bexio account. When testing a specific record, scope queries by unique fixture data instead of assuming unfiltered first() or orderBy('id', 'desc') returns the record created by the current test.

Best practices for AI-generated code

  • Bind a client with useClient() for static reads and attachClient() for instance writes.
  • Reuse existing request and DTO patterns from the nearest resource instead of inventing new ones.
  • Keep query-builder APIs fluent and Laravel-like; collection retrieval should end with get() or first().
  • If adding response-only fields, update outgoing payload filtering and relevant tests together.
  • When changing package architecture or conventions, also update package guidance such as AGENTS.md, README usage, and any Boost skill content.
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.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib