⚠️⚠️⚠️ This release contains breaking changes for API users
The API list endpoints (GET /api/portfolio, /api/transaction, /api/holding) now use spatie/laravel-query-builder in place of hackeresq/filter-models. Each endpoint now accepts only a fixed set of columns for filtering and sorting, so the API no longer passes request input straight through as column names. The updated spec is in openapi.json. See more in #218 (or below)
✨ What's new in API v2.0.0
- Sort by several columns:
?sort=-date,symbol - Comma-separated filters match any value:
?filter[symbol]=AAPL,MSFTnow returns both. Before, it returned nothing. - Filtering by holding works:
?filter[holdings.symbol]=AAPLon portfolios. - Search ignores case on every database, including Postgres.
- Stable pagination: results no longer shift between pages when the sorted column has duplicate values.
- Pagination links keep your filters: the
links.nextandlinks.prevURLs include your active filters and sorts.
Breaking changes
| Before | After |
|---|---|
?sortBy=date&sortDesc=true
| ?sort=-date (prefix - for descending)
|
| Filter or sort on any column | Only the listed columns are accepted (see below) |
| Unknown filter or sort column → error, or ignored | 400 Bad Request, with the allowed columns in the message |
?itemsPerPage=-1 returned every row
| itemsPerPage must be between 1 and 100 (default 15), otherwise 422
|
filter[col]=null matched empty values
| No longer supported |
page,itemsPerPage, and searchkeep their names. Boolean filters accept true orfalse.
Supported parameters
| Endpoint | filter[...]
| sort
| search matches
|
|---|---|---|---|
/portfolio
| title, wishlist, holdings.symbol
| title, wishlist, created_at, updated_at
| title, notes
|
/transaction
| symbol, portfolio_id, transaction_type, currency, split, reinvested_dividend
| symbol, transaction_type, quantity, cost_basis, sale_price, date, created_at, updated_at
| symbol
|
/holding
| symbol, portfolio_id, reinvest_dividends
| symbol, quantity, average_cost_basis, total_cost_basis, realized_gain_dollars, dividends_earned, created_at, updated_at
| symbol
|
Migrating
- GET /api/transaction?sortBy=date&sortDesc=true&itemsPerPage=-1
+ GET /api/transaction?sort=-date&itemsPerPage=100Other Changes
- Record market data provider health and user activity by @hackeresq in #215
- Fix portfolio authorization gaps and dividends SQL injection by @hackeresq in #216
- Count same-day purchases when validating sell quantity by @hackeresq in #219
- Add MCP_SERVER_ENABLED env to toggle the built-in MCP server by @hackeresq in #220
- Fix performance percentage hidden by long holding names by @shanAliKhos in #221
- Fix date-dependent failures in daily change tests by @hackeresq in #225
- Validate imports up front, report every problem, and show progress by @hackeresq in #226
- Build exports in the background, reduce export memory use and keep annotations by @hackeresq in #227
- Speed up exports and prevent overlapping exports by @hackeresq in #228
- Restore export performance columns by calculating them per portfolio in a batch by @hackeresq in #229
- Keep spreadsheet cells in memory instead of the cache store by @hackeresq in #230
- Add user, API, MCP, and self-hosting documentation by @hackeresq in #232
- Simplify import sheet reading and export job dispatch by @hackeresq in #231
- docs: explain how to add custom currencies by @shanAliKhos in #222
Security Fixes
- Harden toast actions and currency SQL; fix currency filter and empty import sheets by @hackeresq in #233
- Delete owned data with the account, fix dividend reinvestment, rate-limit the API by @hackeresq in #234
- Resync the holding a transaction moves out of; guard portfolio owner removal by membership by @hackeresq in #235
- Validate imported locales, restrict import reinvestment to full access, enforce the AI chat rate limit by @hackeresq in #236
- Only answer to the APP_URL host by @hackeresq in #237
- Re-check invite links on submit and bind connect-account links to their user by @hackeresq in #238
- Ask for the password before changing the account email by @hackeresq in #239
- Update laravel/mcp to 1.1.2 by @hackeresq in #241
- Lock server-owned Livewire properties by @hackeresq in #240
- Rate-limit MCP OAuth client registration by @hackeresq in #242
- Sign out other sessions when the password changes by @hackeresq in #243
- Show HTML in AI chat answers as text by @hackeresq in #244
- Keep database error details out of import failure messages by @hackeresq in #245
- Stop user text becoming links in emails, and rate-limit invitations by @hackeresq in #246
- Hardening: email case, social sign-up verification, safer templates, Portfolio cleanups by @hackeresq in #247
- Share one symbol format rule between the API, the form and the import by @hackeresq in #251
- Check access before validating transactions and looking up holdings by @hackeresq in #252
- Cancel reset links on password change, and revoke an app's unexchanged auth codes by @hackeresq in #253
- Bound transaction dates and currency-rate ranges by @hackeresq in #249
- Run daily change syncs one at a time per portfolio by @hackeresq in #250
- Stop publishing PostgreSQL to the host in docker-compose.yml by @hackeresq in #254
New Contributors
- @shanAliKhos made their first contribution in #221
Full Changelog: v1.3.2...v1.4.0