MVP · In Development
⚡ 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✓ DoneCatalog, variants, ingredients, pricing
Inventory✓ DoneStock levels, movements, FIFO valuation, row locking
Sales✓ DoneOrders, payments, tax, discount, stock coordination
Purchases✓ DonePO flow, direct invoices, stock on receipt only
Suppliers / CRM✓ DoneSupplier profiles, basic customer records
POS Module✓ DoneDevices, shifts, staff auth, orders, sync, bootstrap
Settings⏳ After POSCompany/branch config, tax rate, currency
Reporting⏳ After POSDaily sales, cash summary, shift reports
Tables / KDS✗ Out of MVPFirst client is takeaway-only
Loyalty / Full CRM✗ Phase 3Foundation 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

RoleEmailPasswordAccess
Super Adminadmin@admin.compasswordAll companies
Adminadmin@example.compasswordOwn company
Managermanager@example.compassword123Branch-level
Cashiercashier@example.compassword123POS 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 request
3
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