# API Design — `/api/v1` Contract between the Vue frontend (`erpvue`) and the Go backend. The machine-readable version is `docs/openapi.yaml` (Phase 3). This file fixes the conventions the OpenAPI spec and every handler must follow. ## 1. Base URL & versioning - All endpoints under `/api/v1`. Never version per-module. Future: `/api/v2` without breaking v1. - Content-Type `application/json` (except file upload / CSV export endpoints). ## 2. Authentication - `Authorization: Bearer ` — opaque bearer token (Sanctum-parity), SHA-256 hash stored in `personal_access_tokens`. - Only `POST /api/v1/auth/login` and `GET /health`, `GET /ready` are public. - 401 → `AUTHENTICATION_REQUIRED`; 403 → `FORBIDDEN` (role/permission denied, or inactive account `ACCOUNT_DISABLED`). ## 3. Response envelope — success ```json { "data": { ... }, "meta": { "pagination": { "current_page": 1, "per_page": 20, "total": 137, "last_page": 7 }, "request_id": "..." } } ``` - Single resource: `data` = object. Collections: `data` = array + pagination in `meta`. - No pagination for small static lists (units, warehouses, roles) — `data` array only, no `meta.pagination`. - List endpoints accept: `page`, `per_page` (default 20, max 100), `search`, and module filters (e.g. `status`, `customer_id`, `date_from`, `date_to`). ## 4. Response envelope — error ```json { "error": { "code": "VALIDATION_ERROR", "message": "Validation failed", "details": { "items.0.quantity": ["The quantity must be greater than 0."] } } } ``` ### Error codes ↔ HTTP status | HTTP | code | When | |---|---|---| | 400 | `VALIDATION_ERROR` | request shape/rules fail | | 401 | `AUTHENTICATION_REQUIRED` | missing/invalid/expired token | | 403 | `FORBIDDEN` | role/permission denied | | 403 | `ACCOUNT_DISABLED` | `users.is_active = false` | | 404 | `NOT_FOUND` | resource missing | | 409 | `CONFLICT` | duplicate code/number, stock insufficient, status-transition invalid, duplicate payment | | 422 | `BUSINESS_RULE_VIOLATION` | domain rule (e.g. amount > remaining) | | 429 | `RATE_LIMITED` | rate limit exceeded | | 500 | `INTERNAL_ERROR` | unexpected; generic message, details logged (never exposed) | ## 5. Money & numbers - Money fields serialized as **JSON numbers** (e.g. `3996000.00` → `3996000`), internally `shopspring/decimal`. This matches Vue's `formatRupiah(Number(value))`. - Quantities serialized as JSON numbers (up to 4dp, e.g. `0.001`). - Dates: `YYYY-MM-DD`; datetimes: ISO-8601 with timezone offset. All document-number "today" logic uses `Asia/Jakarta`. ## 6. Pagination / filtering / sorting - Query: `page`, `per_page`, `search`, `status`, plus per-resource filters listed in openapi.yaml. - Sort: `sort=` + `order=asc|desc` (default `desc` on `created_at`/document date where Laravel used `latest()`). - Response `meta.pagination` mirrors Laravel paginator so the Vue team can reuse patterns. ## 7. Validation - Validation lives at the handler boundary (mirrors Laravel FormRequests — exact rules in business-rules.md §2–§8). - Business validation re-checked inside the service layer (e.g. stock availability, `amount <= remaining`) — never trusts the client. ## 8. Idempotency - `POST /api/v1/payments`, `POST /api/v1/invoices`, `POST /api/v1/pos` accept `Idempotency-Key` header (≤64 chars). Duplicate key + same payload within TTL returns the original response (Redis), preventing duplicate financial transactions. Optional but recommended for POS. ## 9. CSV exports - `Accept: text/csv` on report endpoints (receivables, supplier debt, cashflow, opname) returns RFC-4180 CSV with BOM for Excel (`UTF-8 BOM`), same columns as Laravel exports. ## 10. Endpoint catalogue (top level) ### Auth | Method | Path | |---|---| | POST | `/auth/login` | | POST | `/auth/logout` | | GET | `/auth/me` | | PUT | `/auth/me` (name/email/password) | ### Master data | Resource | Base | |---|---| | products (+/search, /categories) | `/products` | | categories | `/categories` | | units | `/units` | | suppliers | `/suppliers` | | customers (+/search) | `/customers` | | warehouses | `/warehouses` | ### Inventory | Resource | Base | |---|---| | stocks | `/inventory/stock` | | stock movements | `/inventory/movements` | | stock in | `POST /inventory/stock-in` | | stock out | `POST /inventory/stock-out` | | transfers | `/inventory/transfers` | | opnames (+ `{id}/finalize`, `{id}/export`) | `/inventory/opnames` | ### Sales / POS | Resource | Base | |---|---| | sales orders (+ `{id}/confirm`, `{id}/cancel`) | `/sales-orders` | | POS | `POST /pos` | | receivables | `/invoices/receivables` | ### Purchasing | Resource | Base | |---|---| | purchase orders (+ `{id}/approve`, `{id}/cancel`, `{id}/pay`) | `/purchase-orders` | | supplier debt | `/purchase-orders/debts` | | goods receipts | `/goods-receipts` | | vouchers | `/vouchers` | ### Fakturisasi / Finance | Resource | Base | |---|---| | invoices (+ `{id}/print`) | `/invoices` | | payments | `/payments` | | cashflows | `/cashflows` | ### Reports / Dashboard | Resource | Base | |---|---| | KPIs | `GET /dashboard/kpis` | | exports | `GET /reports/{receivables,debts,cashflow}.csv` | ### HR | Resource | Base | |---|---| | employees | `/employees` | | attendance (admin) | `/hr/attendance` | | attendance (self) | `/hr/attendance/me`, `POST /hr/attendance/clock-in`, `POST /hr/attendance/clock-out` | | leaves (+ `{id}/approve`, `{id}/reject`) | `/hr/leaves` | | payrolls (+ `{id}/approve`) | `/hr/payrolls` | | performances | `/hr/performances` | ### Sales Tracking | Resource | Base | |---|---| | visits (+ `{id}/checkin`, `{id}/checkout`) | `/sales-tracking/visits` | | activities | `/sales-tracking/activities` | | routes | `/sales-tracking/routes` | | locations (ping) | `POST /sales-tracking/locations/ping` | ### System | Resource | Base | |---|---| | users (+ `{id}/toggle`) | `/users` | | roles | `/roles` | | activity logs | `/activity-logs` | ## 11. Authorization matrix on API (enforced server-side) Same as Laravel role permissions (`config/erp.php`) + admin bypass + per-user `permissions` JSON override. Table in business-rules.md §7.3. ## 12. Security - CORS restricted to configured origins. Security headers: `X-Content-Type-Options`, `Referrer-Policy`, `X-Frame-Options` (web UI), CSP for HTML/PDF only. - Request body size limit (e.g. 10MB; image uploads handled separately with 2MB limit per file). - Rate limits: login (e.g. 10/min/IP), location ping (e.g. 1/sec/user), generic (e.g. 300/min/user). - Structured logging with request ID; no sensitive values.