Eviworx
Docs

Inventory API

The Inventory API drives inventory sessions (stocktake): on start a snapshot of the expected assets is taken, then assets are recorded via QR/tag scan. From expected (snapshot) vs actual (scans) the missing and unexpected assets are derived. Scope is either a location (all assets of a location) or a user (their assigned assets).

📦
Features
✓ Scope: location or user
✓ Expected-vs-actual snapshot on start
✓ Scan by QR/asset tag
✓ Quantities for consumables (quantity/expectedQty)
✓ Missing & unexpected assets
✓ Self-service (own session) + agent review
✓ Rescan behavior: OVERWRITE / ACCUMULATE
✓ Report + scan statistics
✓ Automatic accounting of built-in components
✓ Missing with live status + "report lost"

Lifecycle

Start (snapshot of expected assets)   │  POST /api/inventory-sessions
   ▼
ACTIVE ──scan──▶ POST /:id/scan  (per asset, unique per session)
   │  POST /:id/ready-for-review   (scanner hands off for review)
   ▼
READY FOR REVIEW
   │  POST /:id/complete           (agent completes → endedAt set)
   ▼
COMPLETED   → notification INVENTORY_SESSION_COMPLETED to creator

rescanBehavior: OVERWRITE (default) — re-scanning the same asset overwrites the previous one (one entry per session+asset). ACCUMULATE — multiple scans add up quantities (for consumables).

Authentication & Permissions

Self-service is built in: end users may start a session for THEMSELVES, scan and hand it off for review — but not complete it or see others' sessions. Completion, scan corrections and reports are agent/admin rights. Permissions & RBAC.

PermissionDescription
inventory.startSessionStart session for others/location
inventory.startOwnSessionStart own session (self-service, default on)
inventory.scanScan assets + scan note, ready-for-review
inventory.viewSessions / viewOwnSessionsView all resp. only own sessions
inventory.completeSessionsComplete session
inventory.editScansEdit/delete scans
inventory.viewReports / exportReportsView report / export
inventory.viewDeleted / restoreSee trash / restore a session (both off by default)

Restoring requires two permissions: inventory.restore for the action itself and inventory.viewDeleted to see the trash — whoever may not see the trash cannot pull anything out of it. The same visibility as in the list also applies: you can only restore what you are allowed to see. Both permissions are off by default; only the administrator system role receives them automatically.

Endpoints Overview

Mount: /api/inventory-sessions

Method Endpoint Description Permission
POST/Start session (snapshot of expected assets) → 201, session objectstartSession / startOwnSession
GET/List sessions — { data, pagination, counts }; ?q, ?page/per, ?sort, ?f.<field>, ?status=active|completed, ?deleted=1viewSessions / viewOwnSessions
GET/:idSession detailview…
PATCH/:idUpdate session metadatastartSession
POST/:id/ready-for-reviewHand off for reviewscan
POST/:id/completeComplete session (endedAt)completeSessions
DELETE/:idMove session to trash (soft delete, completed ones too; scans are kept) → 204startSession / startOwnSession (own session only: started by or targeting the caller)
POST/:id/restoreRestore session from trashrestore + viewDeleted
POST/:id/scanScan asset — assetId OR code (scanned QR/barcode value), plus quantity, notescan
PATCH/:id/scans/:scanIdEdit scan (quantity)editScans
PATCH/:id/scans/:scanId/noteSet scan notescan
DELETE/:id/scans/:scanIdDelete scan → 204editScans
GET/:id/scansScans of the session — { data, pagination }view…
GET/:id/scans/statsScan statisticsview…
GET/:id/unexpectedScanned but not expected — { data, pagination }view…
GET/:id/missingExpected but not scanned — { data, pagination }view…
GET/:id/reportInventory report (expected vs actual)viewReports

Fields

InventorySession

FieldTypeDescription
nameStringe.g. "Stocktake Q4 2026 – server room"
scopeTypeenumLOCATION, USER
locationIdString?for scope LOCATION (asset location)
userIdString?for scope USER (their assets)
expectedAssetIdsString[]Snapshot of expected asset IDs on start
expectedSnapshotJSONFull expected snapshot (assetTag, name, quantity …)
rescanBehaviorenumOVERWRITE (default), ACCUMULATE
readyForReviewAt / ByDateTime? / String?Hand-off for review
dueAtDateTime?Due date of the stocktake — only inventory.startSession may set or change it (403 INVENTORY_DUE_AT_FORBIDDEN); the target user therefore cannot move their own deadline
deletedAtDateTime?set while the session sits in the trash
startedBy / startedAt / endedAtCreator, start, completion (endedAt = completed)

Due-date monitoring: A built-in cron job ("Inventory Due Monitoring", hourly, enabled by default) reports approaching and missed deadlines: three days and one day ahead, then overdue. Each milestone is reported once. The recipient is the target user resp. the session starter — on overdue additionally the starter. Without a dueAt nothing happens. CronJobs API.

InventoryScan

FieldTypeDescription
assetIdStringScanned asset (unique per session — see rescanBehavior)
quantityIntCounted quantity (default 1)
expectedQtyInt?Expected quantity from snapshot
scanCountIntHow often this asset was scanned in the session
noteString?Note for the scan
scannedBy / scannedAtWho/when scanned

Missing Assets, Live Status & Auto-Account

  • Static missing snapshot: The expected set stays fixed (stocktake record) — an asset marked LOST during the session remains in the missing tab and count.
  • Live status (currentStatus): On read, missing rows are annotated with the current asset status ("now LOST / returned"); undefined = asset since deleted.
  • Auto-account: An unscanned component counts as auto-accounted once its container was scanned in the session (across multiple levels, via the PART_OF/INSTALLED_ON relations). The component is only marked as accounted for; no scan is created. Components without a container relation stay missing. Components out of service (LOST/RETIRED/DISPOSED) are NOT auto-accounted — they stay missing and carry a currentStatus badge.

As a result GET /:id/scans/stats separates real from auto-accounted missing: { scanned, expected, missing, autoAccounted, unexpected }. GET /:id/missing returns missing (REAL missing only) plus autoAccountedAssets; each missing row carries currentStatus and optionally autoAccountedVia.

Asset Movements from Inventory

The inventory routes move NO assets — there is no movement endpoint under /api/inventory-sessions. Actions on scanned, unexpected or missing assets run through the asset flow endpoints (the assets.* rights apply, not inventory.*). Which action is offered depends on the session target, the tracking mode and the asset status; assignments always run via checkout, install or checkin:

ContextActionAPI
Unexpected · USER scope · PERSON · user-lessIssue to {user}POST /assets/:id/checkout
Unexpected · USER scope · PERSON · IN_USE (other user)Change ownercheckin + checkout (+ components via coReturn/coMove)
Unexpected · LOCATION scope · LOCATION AVAILABLEInstall herePOST /assets/:id/install
Unexpected · LOCATION scope · IN_USE elsewhereRelocatePATCH /assets/:id { locationId, coLocateAssetIds? }
Missing (both scopes)Report lostPATCH /assets/:id { status: LOST, note }
PERSON IN_USETake backPOST /assets/:id/checkin
Locked states: with an open handover (PENDING_ACCEPTANCE / RETURN_PENDING) only "open detail" is available. Details on the flows: Asset Lifecycle.

Example Flow

# 1. Start a session for a location
POST /api/inventory-sessions
{ "name": "Stocktake Q4 2026 – Server Room", "scopeType": "LOCATION", "locationId": "clx-loc-id", "rescanBehavior": "OVERWRITE" }

# 2. Scan asset (QR → assetId)
POST /api/inventory-sessions/:id/scan
{ "assetId": "clx-asset-id", "quantity": 1, "note": "Rack 3" }

# 3. Hand off for review
POST /api/inventory-sessions/:id/ready-for-review

# 4. Check expected vs actual
GET /api/inventory-sessions/:id/missing      # expected, not scanned
GET /api/inventory-sessions/:id/unexpected   # scanned, not expected

# 5. Agent completes
POST /api/inventory-sessions/:id/complete
Assets API →
Asset tags/QR, locations, assignment
Permissions & RBAC →
Self-service vs agent rights
Notifications →
INVENTORY_SESSION_COMPLETED