API Specification
API Specification (dashboard-backed)
Section titled “API Specification (dashboard-backed)”This document is a human-readable summary of the API actually used by the dashboard/ frontend and implemented in api/ (Laravel).
For the authoritative machine spec, see api/storage/api-docs/api-docs.json (OpenAPI 3.0).
Base URL and common response shapes
Section titled “Base URL and common response shapes”- Base URL: the frontend is configured via
dashboard/src/configand uses relative paths like/properties. In production this is typically served under/api. - “Resource” responses (most CRUD endpoints): Laravel API Resources return:
{ "data": <T>, "meta": { "...pagination"?: "..." }, "message"?: "..." }- Non-resource responses: some endpoints return a plain DTO (e.g.
GET /ponto-settings,GET /matching-settings) or a bespoke shape (e.g. job status).
Authentication
Section titled “Authentication”Dashboard & mobile (Sanctum)
Section titled “Dashboard & mobile (Sanctum)”- Bearer token:
Authorization: Bearer <token>on all routes underauth:sanctum. - Obtain token:
POST /auth/loginwith{ email, password, device_name? }→{ data: { user, token }, meta, message }.- Allowed roles:
admin,agentonly. device_name:dashboard|mobile-pwa| defaultapi(controls token naming and 90-day cleanup per device).
- Allowed roles:
- Current user:
GET /auth/me - Logout:
POST /auth/logout(revokes current token) - Profile:
PUT /profile— update name, email, phone, password
Passwords are verified with bcrypt; failed login returns 422 with generic auth message.
Server-to-server (bank import / automation)
Section titled “Server-to-server (bank import / automation)”Middleware auth.bank_import accepts:
- Sanctum Bearer token (same as dashboard), or
X-API-Keyheader matchingN8N_API_KEYinapi/.env(binds to first DB user for authorization context)
Used for: bank transaction list/import, payment list/show, auto-match status, billing sync, AI batch job poll, job-status PATCH.
Auth & Profile
Section titled “Auth & Profile”- POST
/auth/login: body{ email, password, device_name? } - POST
/auth/logout(Bearer) - GET
/auth/me(Bearer) - PUT
/profile(Bearer): update authenticated user profile
Users (admin UI)
Section titled “Users (admin UI)”CRUD via Route::apiResource('users', ...) (Bearer). Dashboard shows Users menu for admin role only.
- GET
/users(paginated; filtersearch) - GET
/users/{id} - POST
/users— body includesrole:admin|agent,password - PUT
/users/{id} - DELETE
/users/{id}
Properties (Buildings)
Section titled “Properties (Buildings)”CRUD via Route::apiResource('properties', ...) (Bearer):
- GET
/properties(paginated) - GET
/properties/{id} - POST
/properties - PUT
/properties/{id} - DELETE
/properties/{id}
Property documents (building PDFs for RAG)
Section titled “Property documents (building PDFs for RAG)”Nested under each property (Bearer):
- GET
/properties/{property}/property-documents - POST
/properties/{property}/property-documents(multipart PDF) - GET
/properties/{property}/property-documents/{id} - DELETE
/properties/{property}/property-documents/{id} - GET
/properties/{property}/property-documents/{id}/download - POST
/properties/{property}/property-documents/{id}/index - POST
/properties/{property}/property-documents/{id}/reindex
Categories: assurance | sinistre | entretien | autre. Indexed chunks are injected into AI chat when property_id is sent.
CRUD via Route::apiResource('units', ...) (Bearer):
- GET
/units(paginated; supports filtering byproperty_id) - GET
/units/{id} - POST
/units(JSON ormultipart/form-datawhen uploadingepb_certificate) - PUT
/units/{id}(same) - DELETE
/units/{id}
Signed download (no Bearer; URL from epb_certif_url on unit JSON):
- GET
/units/{unit}/epb-certificate
Tenants
Section titled “Tenants”CRUD via Route::apiResource('tenants', ...) (Bearer):
- GET
/tenants(paginated; supports filtering by name) - GET
/tenants/{id} - POST
/tenants - PUT
/tenants/{id} - DELETE
/tenants/{id}
Notes:
- Tenants store
ibans: string[](not a single iban) for matching.
Leases
Section titled “Leases”CRUD via Route::apiResource('leases', ...) (Bearer):
- GET
/leases(paginated; supports filters liketenant_id,unit_id,status) - GET
/leases/{id} - POST
/leases(supportsmultipart/form-datafor optional attachment) - PUT
/leases/{id}(supportsmultipart/form-datafor optional attachment) - DELETE
/leases/{id}
Attachment download:
- GET
/leases/{lease}/attachment(signed URL middleware)
Lease rent revisions
Section titled “Lease rent revisions”- GET
/leases/{lease}/rent-revisions - POST
/leases/{lease}/rent-revisions→{ data, updated_payments, new_rent_amount } - PUT
/leases/{lease}/rent-revisions/{revision}(latest revision only) - DELETE
/leases/{lease}/rent-revisions/{revision}→{ deleted_revision_id, updated_payments, new_rent_amount }
Repayment plan (arrears amortization)
Section titled “Repayment plan (arrears amortization)”- GET
/leases/{lease}/repayment-plan→{ data: LeaseRepaymentPlan | null } - PUT
/leases/{lease}/repayment-plan— body:{ start_billing_period, extra_amount_per_month, total_debt_amount, note? } - DELETE
/leases/{lease}/repayment-plan— cancels active plan, clearsarrears_extra_amounton pending payments
Payments
Section titled “Payments”CRUD via Route::apiResource('payments', ...) (Bearer) plus additional matching endpoints.
Listing / retrieval
Section titled “Listing / retrieval”- GET
/payments(middlewareauth.bank_import): paginated list; filter bylease_id - GET
/payments/{id}(middlewareauth.bank_import):{id}may be a single id (12) or comma-separated ids (12,34,56)- response is always
{ data: Payment[], missing_ids: number[] }
CRUD (Bearer)
Section titled “CRUD (Bearer)”- POST
/payments - PUT
/payments/{id}(supportsmultipart/form-datavia_method=PUTforcash_receipt) - DELETE
/payments/{id} - POST
/payments/{id}/confirm-cash— record cash received (cash_received_amount) - POST
/payments/{id}/revert-cash— undo cash-only confirmation
Billing period sync
Section titled “Billing period sync”- POST
/payments/sync-billing-periods-for-active-leases(bank import auth)- creates missing monthly payment rows for active leases
- response:
{ created: number }
Auto-match operations
Section titled “Auto-match operations”- POST
/payments/run-auto-match: hybrid (inline for small batches, queued for large)- response:
{ message, mode: "inline"|"queued", unmatched_count, full_matched, partial_linked }
- response:
- GET
/payments/auto-match-status(bank import auth)- response:
{ auto_match_enabled, last_run_at, last_full_matched, last_partial_linked }
- response:
Manual matching (Payment ↔ BankTransaction)
Section titled “Manual matching (Payment ↔ BankTransaction)”- GET
/payments/{payment}/match-candidates - POST
/payments/{payment}/matchbody{ bank_transaction_id, match_type? } - POST
/payments/{payment}/unmatchbody{ bank_transaction_id }
Bank transactions (import + review)
Section titled “Bank transactions (import + review)”Bank transactions represent incoming credits only; debits/negative amounts are intentionally excluded.
CRUD / list
Section titled “CRUD / list”- GET
/bank-transactions(bank import auth): list with filters:bank_account_id,execution_date_from,execution_date_to,search,unmatched_only,exclude_ids[],include_linked_for_payment_id
- GET
/bank-transactions/unmatched: unmatched transactions whose best score is below a threshold - GET
/bank-transactions/{id} - DELETE
/bank-transactions/{id} - DELETE
/bank-transactions: delete all - POST
/bank-transactions/delete-bulkbody{ ids: number[] }
Import methods
Section titled “Import methods”| Endpoint | Auth | Purpose |
|---|---|---|
POST /bank-transactions/refresh | Sanctum | Pull new credits from Ponto; returns job_id for polling |
POST /bank-transactions/import | bank import | Bulk JSON array or { transactions: [...] } |
POST /bank-transactions | Sanctum | Single identity upsert (dedup by fingerprint / provider ids) |
Matching and scoring
Section titled “Matching and scoring”- GET
/bank-transactions/{id}/match-candidates: read-only scored candidates (leases/payments) - POST
/bank-transactions/{id}/match: body{ payment_id? }or{ candidate_index? } - POST
/bank-transactions/run-auto-match(bank import auth): run scoring-based auto-match across unmatched credits
Provider-backed AI match workflow
Section titled “Provider-backed AI match workflow”- POST
/bank-transactions/ai-match: queues an AI batch match run (optionalpayment_idsscope) - GET
/ai-match-batch-jobs/{job_id}(bank import auth): poll AI batch job status - PATCH
/ai-match-batch-jobs/{job_id}(bank import auth): optional external job updates (merge) - POST
/ai-match-batch-jobs/{job_id}/cancel(Sanctum): cancel queued/running batch
Import job status (refresh/import)
Section titled “Import job status (refresh/import)”- GET
/job-status/{job_id} - PATCH
/job-status/{job_id}(bank import auth)
Matching settings (rule-based + AI system message)
Section titled “Matching settings (rule-based + AI system message)”Singleton config (Bearer). Managed in Settings → Matching / Assistant tabs. Global tuning — typically admin responsibility.
- GET
/matching-settings - PUT
/matching-settings
Fields include:
- tolerance & thresholds:
rent_amount_tolerance,min_score_to_auto_match,min_score_partial_link,min_score_modal_list - score weights:
score_iban,score_amount,score_late_window,score_billing_period,score_name_max - rule knobs:
late_window_max_months,infer_month_from_message,tenant_name_min_similarity - IBAN exclusions:
excluded_ibans,transaction_blacklisted_ibans auto_match_enabled, pluslast_auto_match_*telemetryai_chat_system_message— optional system prompt for the dashboard AI assistant (max 10 000 chars)
Ponto settings (bank connectivity)
Section titled “Ponto settings (bank connectivity)”Singleton config (Bearer):
- GET
/ponto-settings - PUT
/ponto-settingsclient_secretcan be cleared by sendingnullor""(API returnsclient_secret_set: boolean)selected_account_ids: string[]is the preferred multi-account selection
- POST
/ponto-settings/refresh-accounts: refresh cached accounts list
AI chat (dashboard assistant)
Section titled “AI chat (dashboard assistant)”(Bearer — all authenticated users)
- POST
/ai/chat— NDJSON stream; body:model(required),provider?(ollama|openai|gemini)messages[]—{ role: user|assistant|system, content }property_id?— when set, injects top-5 RAG chunks from that building’s indexed PDFs
- GET
/ai/models—{ default_provider, providers, availability }
Context assembly (server-side, in order): admin system message → portfolio entity snapshot → Belgian legislation RAG → property document RAG (if property_id).
Knowledge base documents (Belgian legislation RAG)
Section titled “Knowledge base documents (Belgian legislation RAG)”(Bearer)
| Endpoint | Admin only |
|---|---|
GET /knowledge-documents | no |
GET /knowledge-documents/stats | no |
GET /knowledge-documents/{id} | no |
POST /knowledge-documents (multipart files[]) | yes |
DELETE /knowledge-documents/{id} | yes |
POST /knowledge-documents/{id}/index | yes |
POST /knowledge-documents/{id}/reindex | yes |
Document templates (hub)
Section titled “Document templates (hub)”(Bearer — per authenticated user)
Built-in types:
- GET
/document-templates/{documentTypeId}— 404 until first save - PUT
/document-templates/{documentTypeId}— upsertbody_html+logo
Custom templates:
- GET|POST
/custom-templates - GET|PUT|DELETE
/custom-templates/{id}
Types: indexation_letter, residential_lease_contract, notice_of_default (+ user-defined custom templates).
Communication template settings
Section titled “Communication template settings”Singleton (Bearer): GET|PUT /communication-template-settings — plain-text email/WhatsApp bodies for rent revision sharing.