Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .github/workflows/composer-audit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Composer Audit
permissions:
contents: read

on:
push:
branches: [ main ]
pull_request:
schedule:
# Weekly, so new advisories surface without waiting for a push
- cron: '30 6 * * 1'

jobs:
audit:
runs-on: ubuntu-latest
timeout-minutes: 5

steps:
- name: Checkout code
uses: actions/checkout@v7

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.5'
tools: composer:latest
coverage: none

- name: Install dependencies (prefer-stable)
run: composer update --prefer-stable --prefer-dist --no-interaction

- name: Audit dependencies
run: composer audit
2 changes: 1 addition & 1 deletion .github/workflows/deploy-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout your repository using git
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Install, build, and upload your site
uses: withastro/action@v5
with:
Expand Down
33 changes: 33 additions & 0 deletions .github/workflows/docs-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Docs Build
permissions:
contents: read

on:
pull_request:
paths:
- 'docs/**'
- '.github/workflows/docs-build.yml'

jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- name: Checkout code
uses: actions/checkout@v7

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
cache-dependency-path: docs/package-lock.json

- name: Install dependencies
run: npm ci
working-directory: docs

- name: Build docs site
run: npm run build
working-directory: docs
2 changes: 1 addition & 1 deletion .github/workflows/fix-php-code-style-issues.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:

steps:
- name: Checkout code
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
ref: ${{ github.head_ref }}

Expand Down
29 changes: 29 additions & 0 deletions .github/workflows/phpstan.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: PHPStan
permissions:
contents: read

on:
push:
branches: [ main ]
pull_request:

jobs:
phpstan:
runs-on: ubuntu-latest
timeout-minutes: 5

steps:
- name: Checkout code
uses: actions/checkout@v7

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.5'
coverage: none

- name: Install dependencies
run: composer update --prefer-dist --no-interaction

- name: Run PHPStan
run: composer analyse -- --error-format=github
12 changes: 4 additions & 8 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,8 @@ permissions:

on:
push:
paths:
- '**.php'
- '.github/workflows/run-tests.yml'
- 'phpunit.xml.dist'
- 'composer.json'
- 'composer.lock'
branches: [ main ]
pull_request:

jobs:
test:
Expand All @@ -19,14 +15,14 @@ jobs:
fail-fast: true
matrix:
os: [ubuntu-latest, windows-latest]
php: [8.3, 8.2]
php: [8.5, 8.4, 8.3, 8.2]
stability: [prefer-lowest, prefer-stable]

name: P${{ matrix.php }} - ${{ matrix.stability }} - ${{ matrix.os }}

steps:
- name: Checkout code
uses: actions/checkout@v6
uses: actions/checkout@v7

- name: Setup PHP
uses: shivammathur/setup-php@v2
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/update-changelog.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:

steps:
- name: Checkout code
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
ref: main

Expand Down
45 changes: 38 additions & 7 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ This is a PHP wrapper for the Moneybird API built on the Saloon PHP HTTP client

## Architecture

- **Base API Client**: `src/Api/MoneybirdApiClient.php` - Main entry point
- **Endpoints**: Each resource has its own folder in `src/Api/` with an `Endpoint` class and request classes
- **Base API Client**: `src/Api/MoneybirdApiClient.php` - Main entry point. Every resource needs an accessor method here (e.g. `contacts()`, `salesInvoices()`).
- **Endpoints**: Each resource has its own folder in `src/Api/` with an `Endpoint` class and request classes. Endpoint classes extend `BaseEndpoint` (which provides `paginate()` and `create()`).
- **Document types** are nested under `src/Api/Documents/` (GeneralDocuments, GeneralJournalDocuments, PurchaseInvoices, Receipts, TypelessDocuments).
- **Sub-endpoints** (e.g. sales invoice payments, external sales invoice attachments/payments) have no top-level client accessor — they are reached via the parent endpoint: `$client->externalSalesInvoices()->attachments()`.
- **Request Classes**: Inherit from base classes in `src/Api/Support/` (BaseJsonGetRequest, BaseJsonPostRequest, etc.)
- **DTOs**: Data transfer objects for type-safe responses in `src/Dto/`

Expand All @@ -31,11 +33,11 @@ src/Api/
# Run tests
composer test

# Run static analysis
# Run static analysis (PHPStan level 5, baseline in phpstan-baseline.neon)
composer analyse

# Fix code style
composer fix-style
composer format

# Run all checks
composer test && composer analyse
Expand All @@ -58,10 +60,16 @@ curl -s https://raw.githubusercontent.com/moneybird/openapi/refs/heads/main/open

The `openapi-specs/` directory contains dated versions of the spec for reference and comparison.

After pulling a new spec:
1. Diff against the previous dated spec at the path/operation level (extract `METHOD /path` pairs from `paths:` in both files and diff those) — a raw line diff is too noisy.
2. Update the **Current Spec Version** section below.
3. Update `OPENAPI-BACKLOG.md`: add newly introduced endpoints/resources, and re-verify its "not in spec" claims — endpoints this library implements as undocumented occasionally become official (e.g. external sales invoices synchronization in v2-20260710).
4. Check whether any spec change affects the docs site (see Documentation Site below).

### Current Spec Version

- **File**: `openapi-specs/openapi-2025-01-07.yml`
- **API Version**: v2-20251223-d1277b6a05
- **File**: `openapi-specs/openapi-2026-07-12.yml`
- **API Version**: v2-20260710-57729b61b9
- **Base URL**: https://moneybird.com/api/v2

## Implementation Status
Expand All @@ -71,10 +79,33 @@ See `OPENAPI-BACKLOG.md` for a detailed comparison between this library and the
- Missing methods on existing resources
- Potential improvements

Note: `specs/` (endpoint implementation checklist and endpoint-generation templates) is a separate directory from `openapi-specs/` (dated copies of the official OpenAPI spec).

## Documentation Site

User-facing docs live in `docs/` (Astro + Starlight):

- One reference page per resource in `docs/src/content/docs/reference/*.md`; the sidebar is autogenerated from that directory, so a new resource only needs a new page there.
- Guides in `docs/src/content/docs/guides/`.

**Keep docs in sync with the code.** When adding, renaming, or removing endpoint methods, update the matching reference page. Every method call in a code sample must exist on the endpoint class it's called on — docs have drifted before (renamed methods, nonexistent client accessors). Sub-endpoint examples must use the parent-accessor style (`$client->externalSalesInvoices()->payments()`), since there are no top-level accessors for sub-endpoints.

Known deliberate omission: `TypelessDocumentsEndpoint::update()` exists but is intentionally undocumented — the Moneybird API does not support updating typeless documents.

## Rate Limiting

The API has a rate limit of 150 requests per 5 minutes. This is handled automatically by the Saloon rate limit plugin.

## Testing

Tests are in `tests/` using Pest PHP. Run with `composer test`.
Tests are in `tests/` using PHPUnit. Run with `composer test`.

## CI

- **Tests** (`run-tests.yml`): PHPUnit matrix (PHP 8.2–8.5 × prefer-lowest/stable × ubuntu/windows) on pushes to `main` and all pull requests.
- **PHPStan** (`phpstan.yml`): `composer analyse` on pushes to `main` and all pull requests. New errors fail CI; pre-existing ones are pinned in `phpstan-baseline.neon` — shrink the baseline over time, don't add to it.
- **Docs Build** (`docs-build.yml`): builds the Astro docs site on pull requests touching `docs/**` (deploy still happens only on `main` via `deploy-docs.yml`).
- **Composer Audit** (`composer-audit.yml`): `composer audit` (latest Composer) against a prefer-stable resolution, on pushes to `main`, all pull requests, and a weekly cron. Must stay prefer-stable: prefer-lowest would resolve Saloon v3, which has known advisories accepted for BC.
- Workflows use `actions/checkout@v7` (blocks pwn-request patterns by default in `pull_request_target` workflows). The only `pull_request_target` workflow (`dependabot-auto-merge.yml`) deliberately never checks out PR code.

**PHP support policy:** support exactly the PHP versions officially supported by the PHP project (php.net/supported-versions). As of 2026-07: 8.2, 8.3 (security), 8.4, 8.5 — hence `"php": "^8.2"` in composer.json and the 8.2–8.5 CI matrix. When a version reaches EOL (8.2 on 2026-12-31), drop it from the matrix and bump the composer constraint in the next major/minor release.
62 changes: 51 additions & 11 deletions OPENAPI-BACKLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,23 @@

This document tracks the differences between this PHP library and the official Moneybird OpenAPI specification.

**Last Updated**: 2025-01-07
**OpenAPI Spec Version**: v2-20251223-d1277b6a05
**Spec File**: `openapi-specs/openapi-2025-01-07.yml`
**Last Updated**: 2026-07-12
**OpenAPI Spec Version**: v2-20260710-57729b61b9
**Spec File**: `openapi-specs/openapi-2026-07-12.yml`

---

## 🆕 Spec Changes Since 2025-01-07 (v2-20251223 → v2-20260710)

New in the official spec, compared to the previous stored version:

- **Task Lists** (entirely new resource family, not implemented): `task_lists`, `task_list_groups`, `task_list_tasks` (incl. assignment, completion, notes), `task_list_templates`
- **External Sales Invoices synchronization** (`GET`/`POST /external_sales_invoices/synchronization`) is now officially in the spec — this library already implemented it (`getSynchronization()` / `synchronize()`), so it is no longer "undocumented"
- **Reports**: new `GET /reports/creditors_aging` and `GET /reports/debtors_aging`
- **Assets**: new `POST`/`DELETE /assets/{id}/reinvestment_reserve_purchase` and `.../reinvestment_reserve_sale`; the sources delete path parameter was renamed `detail_id` → `source_id`
- **Time Entries**: new `PATCH /time_entries/{id}/resume` and `PATCH /time_entries/{id}/stop` (not implemented)
- **Webhooks**: new `PATCH /webhooks/{id}/activate` and `PATCH /webhooks/{id}/deactivate` (not implemented)
- **Workflows**: new `GET /workflows/{id}` (resource still not implemented)

---

Expand Down Expand Up @@ -58,7 +72,8 @@ These resources have synchronization methods implemented, but the OpenAPI spec d
|----------|---------------------|----------|
| Products | `synchronization()`, `synchronize()` | ❌ No |
| Financial Statements | `synchronization()`, `synchronize()` | ❌ No |
| External Sales Invoices | `getSynchronization()`, `synchronize()` | ❌ No |

**Update 2026-07-12:** External Sales Invoices synchronization was added to the official spec and is no longer in this list — the existing `getSynchronization()` / `synchronize()` implementation is now spec-compliant.

---

Expand Down Expand Up @@ -97,7 +112,9 @@ Full asset management including depreciation tracking.
- `GET/POST /{administration_id}/assets` - List/Create
- `GET/PATCH/DELETE /{administration_id}/assets/{id}` - CRUD
- `POST /{administration_id}/assets/{id}/disposals` - Create disposal
- `POST/DELETE /{administration_id}/assets/{id}/sources` - Manage sources
- `POST/DELETE /{administration_id}/assets/{id}/sources` - Manage sources (delete uses `source_id`)
- `POST/DELETE /{administration_id}/assets/{id}/reinvestment_reserve_purchase` - *(new in v2-20260710)*
- `POST/DELETE /{administration_id}/assets/{id}/reinvestment_reserve_sale` - *(new in v2-20260710)*
- `POST /{administration_id}/assets/{id}/value_changes/*` - Value changes (arbitrary, divestment, full_depreciation, manual, retroactive)

---
Expand All @@ -108,9 +125,10 @@ Analytical reports for the administration.
**Endpoints needed:**
- Balance sheet, Cash flow, Profit & loss
- Creditors, Debtors
- Creditors aging, Debtors aging *(new in v2-20260710)*
- Revenue/Expenses by contact/project
- General ledger, Journal entries
- Tax report, Subscriptions report
- Tax report, Subscriptions report, Assets report
- Export endpoints (auditfile, brugstaat, ledger_accounts)

---
Expand All @@ -135,6 +153,23 @@ Analytical reports for the administration.

### Workflows
- `GET /{administration_id}/workflows` - List workflows
- `GET /{administration_id}/workflows/{id}` - Get single workflow *(new in v2-20260710)*

---

### Task Lists *(new in v2-20260710)*
Task management resource family, entirely new in the spec:

- `GET/POST /{administration_id}/task_lists` + `GET/PATCH/DELETE /task_lists/{id}`
- `POST /task_lists/{task_list_id}/groups`
- `GET/PATCH/DELETE /task_list_groups/{id}` + `POST /task_list_groups/{task_list_group_id}/tasks`
- `GET/PATCH/DELETE /task_list_tasks/{id}`
- `POST/DELETE /task_list_tasks/{task_list_task_id}/assignment`
- `POST/DELETE /task_list_tasks/{task_list_task_id}/completion`
- `POST /task_list_tasks/{task_list_task_id}/notes`
- `GET/POST /task_list_templates` + `GET/PATCH/DELETE /task_list_templates/{id}`
- `POST /task_list_templates/{task_list_template_id}/groups`
- `POST /task_list_templates/{task_list_template_id}/task_lists`

---

Expand All @@ -160,7 +195,7 @@ Analytical reports for the administration.
- `PATCH /{id}/mark_as_uncollectible` - Mark as uncollectible
- Notes sub-resource

**Note:** Synchronization methods exist but are NOT in spec.
**Note:** Synchronization endpoints were added to the spec in v2-20260710; the existing implementation is now spec-compliant.

---

Expand Down Expand Up @@ -204,7 +239,10 @@ Analytical reports for the administration.
---

### Time Entries
**Missing:** Notes sub-resource (CRUD)
**Missing:**
- Notes sub-resource (CRUD)
- `PATCH /{id}/resume` - Resume a running time entry *(new in v2-20260710)*
- `PATCH /{id}/stop` - Stop a running time entry *(new in v2-20260710)*

---

Expand All @@ -224,6 +262,8 @@ Analytical reports for the administration.
- `GET /webhooks` - List all webhooks
- `GET /webhooks/{id}` - Get single webhook
- `DELETE /webhooks/{id}` - Delete webhook
- `PATCH /webhooks/{id}/activate` - Activate webhook *(new in v2-20260710)*
- `PATCH /webhooks/{id}/deactivate` - Deactivate webhook *(new in v2-20260710)*

---

Expand Down Expand Up @@ -259,14 +299,14 @@ All critical bugs have been fixed:

### Undocumented (May or May Not Work)
1. 5 sales invoice mark_as_* endpoints not in spec
2. 3 resources have sync methods not in spec (Products, Financial Statements, External Sales Invoices)
2. 2 resources have sync methods not in spec (Products, Financial Statements) — External Sales Invoices sync is now official
3. Financial Statements GET methods not in spec
4. Ledger Accounts create() not in spec
5. Entire Import Mappings resource not in spec

### Missing from Library
1. 6 entire resources (Assets, Reports, Customer Portal, Downloads, SEPA, Workflows)
2. Various sub-methods on existing resources
1. 7 entire resources (Assets, Reports, Customer Portal, Downloads, SEPA, Workflows, Task Lists)
2. Various sub-methods on existing resources (incl. new time entry resume/stop and webhook activate/deactivate)

---

Expand Down
2 changes: 2 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
},
"require-dev": {
"laravel/pint": "^1.17",
"phpstan/phpstan": "^2.2",
"phpunit/phpunit": "^11.3",
"symfony/var-dumper": "^7.1"
},
Expand All @@ -47,6 +48,7 @@
"scripts": {
"test": "vendor/bin/phpunit",
"test-coverage": "vendor/bin/phpunit --coverage",
"analyse": "vendor/bin/phpstan analyse --memory-limit=1G",
"format": "vendor/bin/pint"
},
"config": {
Expand Down
Loading
Loading