⚡ Internal Developer Reference
Z-Flux POS
API Documentation
Modular SaaS point-of-sale backend built with Laravel. Targets restaurants & cafes with multi-branch, multi-device, offline-first support.
7
Modules
50+
Endpoints
3
User Roles
JWT
Auth
Offline
First
Architecture
Modular Monolith
Loosely coupled modules communicate via service interfaces, not direct calls. Each module can be enabled/disabled at runtime.
Multi-Tenant
Complete data isolation via
company_id on every tenant-scoped table. Super admin can access cross-tenant via X-Company-Id.Device-Owned Branch
Each iPad is permanently bound to a branch via a
device_token. Cashiers never select a branch — the device decides.Transaction Safety
DB row locking on stock deductions prevents race conditions. Idempotency keys prevent duplicate orders on offline sync retries.
Null Object Pattern
When Inventory module is disabled,
NullInventoryService is injected — system continues without crashing or branching logic.Offline-First POS
Flutter app stores orders in SQLite. On reconnect,
POST /api/pos/sync pushes all pending orders. Backend de-dupes via idempotency keys.Module Status
| Module | Status | Key Responsibility |
|---|---|---|
| Product | ✓ Done | Catalog, variants, ingredients, pricing |
| Inventory | ✓ Done | Stock levels, movements, FIFO valuation, row locking |
| Sales | ✓ Done | Orders, payments, tax, discount, stock coordination |
| Purchases | ✓ Done | PO flow, direct invoices, stock on receipt only |
| Suppliers / CRM | ✓ Done | Supplier profiles, basic customer records |
| POS Module | ✓ Done | Devices, shifts, staff auth, orders, sync, bootstrap |
| Settings | ⏳ After POS | Company/branch config, tax rate, currency |
| Reporting | ⏳ After POS | Daily sales, cash summary, shift reports |
| Tables / KDS | ✗ Out of MVP | First client is takeaway-only |
| Loyalty / Full CRM | ✗ Phase 3 | Foundation data already captured |
Authentication
The system uses two separate auth flows — dashboard users and POS staff on devices. Every request must carry a JWT and, for POS endpoints, a device token.
Dashboard Auth (Admin / Manager)
POST /api/login
// Request { "email": "manager@example.com", "password": "password123" } // Response → use token in Authorization header { "token": "eyJ0eXAiOiJKV1Qi...", "user": { "role": "manager", ... } }
POS Staff Auth (Cashier on iPad)
POST /api/pos/auth/login
// Headers — both required Authorization: Bearer {user_jwt} X-Device-Token: {device_token} // branch_id is resolved from device — never from user { "user_id": 5, "role": "cashier", "branch_id": 2, ← from device, not user "device_id": 3 }
No branch_id on users table. Branch is always resolved from the device token. A cashier cannot select or override their branch — the iPad owns it.
Test Credentials
| Role | Password | Access | |
|---|---|---|---|
| Super Admin | admin@admin.com | password | All companies |
| Admin | admin@example.com | password | Own company |
| Manager | manager@example.com | password123 | Branch-level |
| Cashier | cashier@example.com | password123 | POS only |
Device System
The most critical architectural decision. Each iPad is registered once and permanently owns a branch. This eliminates cashier error when selecting branches.
One-Time Setup (Manager)
1
Manager opens Admin Panel → Add New Device
2
Enters device name (e.g. "iPad Counter 1") and selects branch
3
System generates a unique device_token
4
Flutter dev installs token in app's local storage. Never changes unless device is replaced.
Daily Usage (Cashier)
1
Cashier opens app → enters PIN/password
2
App silently sends
X-Device-Token header on every request3
Backend reads token → resolves branch_id + company_id automatically
4
Shift opens for correct branch. Cashier sees nothing about branch config.
devices — schema
-- devices id bigint PK company_id bigint FK → companies branch_id bigint FK → branches ← assigned once, never changed by cashier name varchar "iPad Counter 1" device_token varchar(64) UNIQUE ← generated by system is_active boolean replaced_by bigint NULL FK → devices ← points to replacement device created_at, updated_at
POS Module Endpoints
All POS endpoints require both
Authorization: Bearer {token} and X-Device-Token: {device_token} headers.
Branch is always resolved from the device — never passed in the request body.
📱
Device Management
4 endpoints
GET
/api/pos/devices
List all devices for company
Admin
POST
/api/pos/devices
Register new device → returns device_token
Admin
PATCH
/api/pos/devices/{id}
Deactivate / rename device
Admin
POST
/api/pos/devices/{id}/replace
Replace device → deactivates old, creates new token
Admin
🔐
Staff Authentication
3 endpoints
POST
/api/pos/auth/login
Staff login — requires X-Device-Token, returns branch_id from device
Device
POST
/api/pos/auth/logout
Invalidate staff session
JWT
GET
/api/pos/auth/me
Current staff info with resolved branch
JWT
👥
Staff CRUD
5 endpoints
GET
/api/pos/staff
List staff (cashiers/managers) for company
Admin
POST
/api/pos/staff
Create staff member with role assignment
Admin
GET
/api/pos/staff/{id}
Get staff member details
Admin
PUT
/api/pos/staff/{id}
Update staff info / role / PIN
Admin
DELETE
/api/pos/staff/{id}
Deactivate staff member
Admin
🕐
Shifts
4 endpoints
GET
/api/pos/shifts/current
Get open shift for this device — checked on every login
Device
POST
/api/pos/shifts/open
Open new shift with opening cash amount
Device
POST
/api/pos/shifts/close
Close shift with cash count → calculates variance
Device
GET
/api/pos/shifts
Shift history for branch (manager view)
Admin
🧾
POS Orders
3 endpoints
POST
/api/pos/orders
Create order — branch/shift auto-resolved from device
Device
GET
/api/pos/orders/{id}
Get order details for receipt display
Device
GET
/api/pos/bootstrap
Pull products, prices, tax rate on app start
Device
🔄
Offline Sync
1 endpoint
POST
/api/pos/sync
Push offline orders batch — idempotency keys prevent duplicates
Device
Core & Dashboard Endpoints
🏢
Companies, Branches, Users, Customers
10 endpoints
GET
/api/dashboard/companies
List all companies — Super Admin only, paginated
Super Admin
POST
/api/dashboard/companies
Create company with subscription plan + limits
Super Admin
GET
/api/dashboard/branches
List branches for current company
JWT
POST
/api/dashboard/branches
Create branch
Admin
GET
/api/dashboard/users
List users — filterable by role
JWT
POST
/api/dashboard/users
Create user with role assignment
Admin
GET
/api/dashboard/customers
Customer list — Mini CRM data
JWT
Products Module
📦
Products, Variants, Categories
8 endpoints
GET/api/productsList products with variants and categoriesJWT
POST/api/productsCreate product with variants + ingredient linksAdmin
GET/api/products/{id}Single product with all variantsJWT
PUT/api/products/{id}Update product / toggle activeAdmin
DELETE/api/products/{id}Soft delete productAdmin
GET/api/products/variantsAll variants with pricingJWT
GET/api/products/categoriesCategory tree (hierarchical)JWT
GET/api/products/searchSearch by name / SKUJWT
Inventory Module
📊
Items, Stock, Movements
8 endpoints
GET/api/inventory/itemsList inventory items with min/max thresholdsJWT
GET/api/inventory/stocksCurrent stock per branch (real-time)JWT
GET/api/inventory/movementsFull audit trail — filterable by type/dateJWT
POST/api/inventory/add-stockManual stock addition with movement recordAdmin
POST/api/inventory/transferTransfer stock between branchesAdmin
GET/api/inventory/alertsLow-stock notificationsJWT
GET/api/inventory/adjustmentsPending adjustments — approval workflowJWT
GET/api/inventory/valuationTotal stock value — FIFO methodAdmin
Sales Module
Pricing rule:
subtotal is source of truth.
tax applied to (subtotal - discount).
total = (subtotal - discount) + tax.
Always send idempotency_key from the client.
💰
Orders & Analytics
8 endpoints
GET/api/ordersList orders — filterable by status/branch/dateJWT
POST/api/ordersCreate order — deducts stock, validates paymentsJWT
GET/api/orders/{id}Order detail with items, payments, profitJWT
PATCH/api/orders/{id}Update order statusJWT
GET/api/orders/{id}/paymentsPayment history for orderJWT
POST/api/orders/{id}/refundProcess refund — optionally restocks itemsAdmin
GET/api/orders/analyticsRevenue, top products, hourly/daily breakdownAdmin
GET/api/orders/statisticsQuick stats summary — dashboard widgetJWT
Purchases Module
🛒
Purchase Orders & Suppliers
7 endpoints
GET/api/purchasesList POs — filterable by status/vendor/dateJWT
POST/api/purchasesCreate PO — stock NOT moved until receivedAdmin
GET/api/purchases/{id}PO detail with items and receipt historyJWT
POST/api/purchases/{id}/receiveReceive items → stock added immediatelyAdmin
GET/api/purchases/vendorsSupplier list with spend totalsJWT
POST/api/purchases/direct-invoiceDirect invoice — skips draft, adds stock immediatelyAdmin
GET/api/purchases/analyticsSpend analysis per vendor/periodAdmin
Offline & Sync
Bootstrap
GET /api/pos/bootstrap
// Response { "products": [...], "variants": [...], "categories": [...], "tax_rate": 0.14, "branch": {...}, "last_updated_at": "2026-04-15T..." }
Sync
POST /api/pos/sync
// Request { "last_synced_at": "2026-04-15T10:00:00Z", "orders": [ { "idempotency_key": "uuid", "created_at_device": "...", "items": [...], "payments": [...] } ] }
Conflict rule: If an order syncs but stock is insufficient — accept the order anyway, flag the stock issue, notify manager.
Never reject a completed sale.
Response Format
Success
{
"success": true,
"data": { ... },
"message": "Operation completed"
}
Error
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "The given data was invalid",
"details": { "email": ["required"] }
}
}
HTTP Status Codes
200
OK — Success
201
Created
400
Bad Request
401
Unauthorized
403
Forbidden
404
Not Found
422
Validation Error
500
Server Error
Roadmap
Current — MVP
POS Live
Settings module
Reporting module
Stabilize with 1-2 clients
Collect feedback
Phase 2
Scale
Real-time sync via WebSockets
Kitchen Display (if needed)
Table management (if needed)
Redis caching layer
Phase 3
Platform
Async inventory via message queue
Payment module extraction
Full CRM / loyalty / points
Advanced analytics & BI
Base URL:
https://api.zflux.com ·
Rate limit: 1000 req/hr per user ·
Multi-tenant header: X-Company-Id (Super Admin only) ·
Last updated: April 2026