# ShopDB Flask - API Reference Auto-inventoried across core + plugins (362 endpoints). Auth legend: none = public, jwt = login required, jwt-optional = works either way, admin = admin role, permission:x = named RBAC permission, api-key = X-API-Key managed token. ## core-identity ### POST /api/auth/login **Auth:** none **Params:** body: `username`, `password` (both required) **Purpose:** Authenticate and issue JWT access+refresh tokens; per-IP fixed-window rate limit (429) and 5-strike/15-min account lockout. ```bash curl -X POST http://localhost:5001/api/auth/login -H 'Content-Type: application/json' -d '{"username":"admin","password":"secret123"}' ``` ### POST /api/auth/refresh **Auth:** JWT (refresh token) **Params:** none; refresh token in Authorization header **Purpose:** Exchange a refresh token for a new access token (rejects inactive/deleted users). ```bash curl -X POST http://localhost:5001/api/auth/refresh -H "Authorization: Bearer $REFRESH_TOKEN" ``` ### GET /api/auth/me **Auth:** JWT **Params:** none **Purpose:** Return the authenticated user's profile, roles, permissions, `mustchangepassword` flag. ```bash curl http://localhost:5001/api/auth/me -H "Authorization: Bearer $TOK" ``` ### POST /api/auth/change-password **Auth:** JWT **Params:** body: `new_password` (min 8, required), `current_password` (required unless `mustchangepassword` is set) **Purpose:** Self-service password change; forced first-login change skips `current_password`; clears lockout state. ```bash curl -X POST http://localhost:5001/api/auth/change-password -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"current_password":"old","new_password":"newpass123"}' ``` ### POST /api/auth/logout **Auth:** JWT **Params:** none **Purpose:** Logout stub for frontend token cleanup (no server-side blacklist yet). ```bash curl -X POST http://localhost:5001/api/auth/logout -H "Authorization: Bearer $TOK" ``` ### GET /api/users **Auth:** JWT + role `admin` **Params:** none **Purpose:** List all users ordered by username. ```bash curl http://localhost:5001/api/users -H "Authorization: Bearer $TOK" ``` ### GET /api/users/<userid> **Auth:** JWT (admin or self, checked inline) **Params:** path: `userid` (int) **Purpose:** Get one user; non-admins may only fetch their own record (403 otherwise). ```bash curl http://localhost:5001/api/users/7 -H "Authorization: Bearer $TOK" ``` ### POST /api/users **Auth:** JWT + role `admin` **Params:** body: `username`, `email`, `password` (required); `firstname`, `lastname`, `isactive`, `roles` [roleids], `mustchangepassword` (default true), `sendwelcome` (default true) **Purpose:** Create a user, assign roles, audit-log, and best-effort send a welcome email with the temp password. ```bash curl -X POST http://localhost:5001/api/users -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"username":"jdoe","email":"jdoe@example.com","password":"Temp1234","roles":[2]}' ``` ### PUT /api/users/<userid> **Auth:** JWT (admin or self; `isactive`/`roles`/`unlock` fields admin-only) **Params:** path: `userid`; body: `email`, `firstname`, `lastname`, `password`; admin-only: `isactive`, `roles` [roleids], `unlock` (bool) **Purpose:** Update a user; email uniqueness enforced; admins can also change active state, roles, and unlock the account; changes audit-logged. ```bash curl -X PUT http://localhost:5001/api/users/7 -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"firstname":"Jane","unlock":true}' ``` ### DELETE /api/users/<userid> **Auth:** JWT + role `admin` **Params:** path: `userid` (cannot be your own account) **Purpose:** Delete a user; revokes their API tokens and detaches (nulls `userid` on) their audit-log rows first. ```bash curl -X DELETE http://localhost:5001/api/users/7 -H "Authorization: Bearer $TOK" ``` ### GET /api/users/permissions **Auth:** JWT **Params:** none **Purpose:** List assignable permissions (core + enabled plugins) both flat and grouped by category, for the role grid. ```bash curl http://localhost:5001/api/users/permissions -H "Authorization: Bearer $TOK" ``` ### GET /api/users/roles **Auth:** JWT **Params:** none **Purpose:** List all roles with description, color, user count, permission names, and `isadmin` flag. ```bash curl http://localhost:5001/api/users/roles -H "Authorization: Bearer $TOK" ``` ### POST /api/users/roles **Auth:** JWT + role `admin` **Params:** body: `rolename` (required), `description`, `color`, `permissions` [names] **Purpose:** Create a role and assign permissions by name; 409 if `rolename` exists. ```bash curl -X POST http://localhost:5001/api/users/roles -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"rolename":"viewer","permissions":["assets.view"]}' ``` ### PUT /api/users/roles/<roleid> **Auth:** JWT + role `admin` **Params:** path: `roleid`; body: `description`, `color`, `permissions` [names] (permissions immutable on the admin role) **Purpose:** Update a role's description/color/permissions; admin role's permission set cannot be modified; audit-logged. ```bash curl -X PUT http://localhost:5001/api/users/roles/3 -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"color":"#0057b8","permissions":["assets.view","assets.edit"]}' ``` ### DELETE /api/users/roles/<roleid> **Auth:** JWT + role `admin` **Params:** path: `roleid` **Purpose:** Delete a role; refuses for the admin role or any role still assigned to users. ```bash curl -X DELETE http://localhost:5001/api/users/roles/3 -H "Authorization: Bearer $TOK" ``` ### GET /api/apitokens **Auth:** JWT **Params:** query: `all=true` (admin only, lists everyone's tokens with owner info) **Purpose:** List the caller's own API tokens (never returns hashes or secrets); admins may list all. ```bash curl 'http://localhost:5001/api/apitokens?all=true' -H "Authorization: Bearer $TOK" ``` ### POST /api/apitokens **Auth:** JWT + permission `apitokens.create` **Params:** body: `name` (required), `expiresat` (date/datetime), `scopes` [permission names, ceiling = owner's permissions], `resourcescopes` [resource names, plugin-defined] **Purpose:** Create a personal API token for the caller; the full secret is returned once in this response and never again. ```bash curl -X POST http://localhost:5001/api/apitokens -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"name":"import-script","scopes":["assets.edit"],"expiresat":"2026-12-31"}' ``` ### PUT /api/apitokens/<tokenid> **Auth:** JWT + permission `apitokens.create` (own token, or any if admin) **Params:** path: `tokenid`; body: `name`, `isactive` (bool), `scopes` [names, validated against the token owner's permissions], `resourcescopes` [names] **Purpose:** Rename, rescope, or (de)activate a token; scope ceiling is always the owner, even when an admin edits. ```bash curl -X PUT http://localhost:5001/api/apitokens/4 -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"isactive":false}' ``` ### DELETE /api/apitokens/<tokenid> **Auth:** JWT + permission `apitokens.create` (own token, or any if admin) **Params:** path: `tokenid` **Purpose:** Revoke (deactivate, not delete) a token; audit-logged. ```bash curl -X DELETE http://localhost:5001/api/apitokens/4 -H "Authorization: Bearer $TOK" ``` ### GET /api/setup/needs-admin **Auth:** none **Params:** none **Purpose:** Return `{needsadmin: true}` when zero users exist, so the login screen can offer first-run setup. ```bash curl http://localhost:5001/api/setup/needs-admin ``` ### POST /api/setup/create-admin **Auth:** none (only functions while zero users exist; 403 afterwards) **Params:** body: `username`, `email`, `password` (all required) **Purpose:** Bootstrap the very first admin account (creates the admin role if missing). ```bash curl -X POST http://localhost:5001/api/setup/create-admin -H 'Content-Type: application/json' -d '{"username":"admin","email":"admin@example.com","password":"ChangeMe123"}' ``` ### POST /api/setup/seed-reference **Auth:** JWT + role `admin` **Params:** none **Purpose:** Idempotently seed core reference data, permissions, and default settings (runs the flask seed CLI routines). ```bash curl -X POST http://localhost:5001/api/setup/seed-reference -H "Authorization: Bearer $TOK" ``` ### POST /api/setup/seed-starter **Auth:** JWT + role `admin` **Params:** none **Purpose:** Idempotently add a starter list of common hardware vendors (Dell, HP, Lenovo, ...). ```bash curl -X POST http://localhost:5001/api/setup/seed-starter -H "Authorization: Bearer $TOK" ``` ### POST /api/setup/complete **Auth:** JWT + role `admin` **Params:** none **Purpose:** Set the `setup_complete` setting to true, marking the first-run wizard finished. ```bash curl -X POST http://localhost:5001/api/setup/complete -H "Authorization: Bearer $TOK" ``` ### GET /api/settings **Auth:** JWT optional **Params:** query: `category` (filter) **Purpose:** List settings; unauthenticated callers see only the public allowlist (branding+map categories plus named bootstrap keys); secrets always masked as `********`. ```bash curl 'http://localhost:5001/api/settings?category=branding' ``` ### POST /api/settings **Auth:** JWT + permission `settings.edit` **Params:** body: `key` (required), `value`, `valuetype` (default string), `category` (default general), `description` **Purpose:** Create a new setting; 409 if the key exists. ```bash curl -X POST http://localhost:5001/api/settings -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"key":"facility_name","value":"West Jefferson","category":"site"}' ``` ### GET /api/settings/<key> **Auth:** JWT optional **Params:** path: `key` **Purpose:** Get one setting; non-public keys return 404 (not 403) to unauthenticated callers; secret values masked. ```bash curl http://localhost:5001/api/settings/facility_name ``` ### PUT /api/settings/<key> **Auth:** JWT + permission `settings.edit` **Params:** path: `key`; body: `value` (required; bool coerced to 'true'/'false'; secret mask `********` means leave unchanged) **Purpose:** Update a setting (upserts a plugin-scoped string row if the key is new); audit-logged with secrets masked; invalidates the 5-min settings cache. ```bash curl -X PUT http://localhost:5001/api/settings/facility_name -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"value":"West Jefferson"}' ``` ### POST /api/settings/seed **Auth:** JWT + permission `settings.edit` **Params:** none **Purpose:** Idempotently create any missing default settings (identifier toggles, search toggles, map, SMTP, SAML, etc.). ```bash curl -X POST http://localhost:5001/api/settings/seed -H "Authorization: Bearer $TOK" ``` ### POST /api/settings/test-email **Auth:** JWT + permission `settings.edit` **Params:** body: `to` (optional; falls back to `alert_recipients` setting) **Purpose:** Send a test email to verify SMTP config; always 200 with a `sent` flag, SMTP errors returned with credentials scrubbed. ```bash curl -X POST http://localhost:5001/api/settings/test-email -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"to":"me@example.com"}' ``` ### POST /api/settings/map-blueprint **Auth:** JWT + role `admin` **Params:** multipart/form-data: `file` (png/jpg/jpeg/gif/webp/svg), `theme=light|dark` **Purpose:** Upload a floor-map blueprint image to the instance maps dir and point `map_blueprint_` at its served URL. ```bash curl -X POST http://localhost:5001/api/settings/map-blueprint -H "Authorization: Bearer $TOK" -F 'file=@floor.png' -F 'theme=light' ``` ### GET /api/settings/map-blueprint/<path:filename> **Auth:** none **Params:** path: `filename` **Purpose:** Serve an uploaded floor-map blueprint image (public so kiosk dashboards can load it). ```bash curl -O http://localhost:5001/api/settings/map-blueprint/blueprint-light.png ``` ### POST /api/settings/branding-logo **Auth:** JWT + role `admin` **Params:** multipart/form-data: `file` (map image types plus .ico), `kind=site|qr|badge|favicon` **Purpose:** Upload a branding logo/favicon to the instance branding dir and set the matching branding setting (`site_logo`/`qr_logo`/`badge_logo`/`site_favicon`). ```bash curl -X POST http://localhost:5001/api/settings/branding-logo -H "Authorization: Bearer $TOK" -F 'file=@logo.svg' -F 'kind=site' ``` ### GET /api/settings/branding/<path:filename> **Auth:** none **Params:** path: `filename` **Purpose:** Serve an uploaded branding logo (public - kiosks and print pages read it). ```bash curl -O http://localhost:5001/api/settings/branding/logo-site.svg ``` ## core-catalog ### GET /api/assets/types **Auth:** JWT optional **Params:** `page`, `per_page`, `active=false` to include inactive **Purpose:** List asset types (paginated, active-only by default). ```bash curl http://localhost:5001/api/assets/types?active=false ``` ### GET /api/assets/types/<type_id> **Auth:** JWT optional **Params:** path: `type_id` **Purpose:** Get one asset type. ```bash curl http://localhost:5001/api/assets/types/1 ``` ### POST /api/assets/types **Auth:** permission `assets.create` **Params:** body: `assettype` (req), `pluginname`, `tablename`, `description`, `icon`, `color` **Purpose:** Create asset type (409 on duplicate name). ```bash curl -X POST http://localhost:5001/api/assets/types -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"assettype":"Robot","icon":"mdi-robot"}' ``` ### PUT /api/assets/types/<type_id> **Auth:** permission `assets.edit` **Params:** body: `description`, `icon`, `color`, `isactive` **Purpose:** Update asset type display fields only (name/plugin/table are structural, not editable). ```bash curl -X PUT http://localhost:5001/api/assets/types/1 -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"color":"#ff0000"}' ``` ### GET /api/assets/statuses **Auth:** JWT optional **Params:** `page`, `per_page`, `active=false` **Purpose:** List asset statuses (paginated, active-only by default). ```bash curl http://localhost:5001/api/assets/statuses ``` ### GET /api/assets/statuses/<status_id> **Auth:** JWT optional **Params:** path: `status_id` **Purpose:** Get one asset status. ```bash curl http://localhost:5001/api/assets/statuses/1 ``` ### POST /api/assets/statuses **Auth:** permission `assets.create` **Params:** body: `status` (req), `description`, `color` **Purpose:** Create asset status (409 on duplicate). ```bash curl -X POST http://localhost:5001/api/assets/statuses -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"status":"In Repair","color":"#f90"}' ``` ### PUT /api/assets/statuses/<status_id> **Auth:** permission `assets.edit` **Params:** body: `status`, `description`, `color`, `isactive` **Purpose:** Update asset status (rename conflict-checked). ```bash curl -X PUT http://localhost:5001/api/assets/statuses/2 -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"isactive":false}' ``` ### DELETE /api/assets/statuses/<status_id> **Auth:** permission `assets.delete` **Params:** path: `status_id` **Purpose:** Hard-delete asset status; 409 if any asset still uses it. ```bash curl -X DELETE http://localhost:5001/api/assets/statuses/9 -H "Authorization: Bearer $TOK" ``` ### GET /api/assets/relationshiptypes **Auth:** JWT optional **Params:** none **Purpose:** List relationship types incl. read-only `propagatesthrough` rails. ```bash curl http://localhost:5001/api/assets/relationshiptypes ``` ### POST /api/assets/relationshiptypes **Auth:** permission `assets.create` **Params:** body: `relationshiptype` (req), `description`, `color`, `isdirectional` (default true) **Purpose:** Create relationship type (409 on duplicate). ```bash curl -X POST http://localhost:5001/api/assets/relationshiptypes -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"relationshiptype":"controls","isdirectional":true}' ``` ### PUT /api/assets/relationshiptypes/<type_id> **Auth:** permission `assets.edit` **Params:** body: `relationshiptype`, `description`, `color`, `isdirectional` **Purpose:** Update relationship type (rename conflict-checked). ```bash curl -X PUT http://localhost:5001/api/assets/relationshiptypes/3 -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"color":"#00f"}' ``` ### DELETE /api/assets/relationshiptypes/<type_id> **Auth:** permission `assets.delete` **Params:** path: `type_id` **Purpose:** Hard-delete relationship type; 409 while relationships use it. ```bash curl -X DELETE http://localhost:5001/api/assets/relationshiptypes/3 -H "Authorization: Bearer $TOK" ``` ### GET /api/assets **Auth:** JWT optional **Params:** `page`, `per_page`, `active`, `search` (assetnumber/name/serialnumber ilike), `type` (name), `typeid|type_id`, `statusid|status_id`, `locationid|location_id`, `businessunitid|businessunit_id`, `sort` (assetnumber|name|createddate|modifieddate), `dir` (asc|desc), `include_type_data` **Purpose:** List assets with filtering, search, sorting, pagination. ```bash curl 'http://localhost:5001/api/assets?type=machine&search=205&include_type_data=true' ``` ### GET /api/assets/<asset_id> **Auth:** JWT optional **Params:** `include_type_data` (default true) **Purpose:** Get one asset with full details. ```bash curl http://localhost:5001/api/assets/42 ``` ### POST /api/assets **Auth:** permission `assets.create` **Params:** body: `assetnumber` (req), `assettypeid` (req), `name`, `serialnumber`, `statusid` (default 1), `locationid`, `businessunitid`, `mapx`, `mapy`, `notes` **Purpose:** Create asset (duplicate assetnumber 409, assettypeid validated); honors `X-Import-Mode` timestamps. ```bash curl -X POST http://localhost:5001/api/assets -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"assetnumber":"0205","assettypeid":1,"name":"Grinder 5"}' ``` ### PUT /api/assets/<asset_id> **Auth:** permission `assets.edit` **Params:** body: `assetnumber`, `name`, `serialnumber`, `assettypeid`, `statusid`, `locationid`, `businessunitid`, `mapx`, `mapy`, `notes`, `isactive` **Purpose:** Update asset (allowed fields incl. isactive); honors `X-Import-Mode`. ```bash curl -X PUT http://localhost:5001/api/assets/42 -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"locationid":3}' ``` ### DELETE /api/assets/<asset_id> **Auth:** permission `assets.delete` **Params:** path: `asset_id` **Purpose:** Soft-delete asset (`isactive=false`). ```bash curl -X DELETE http://localhost:5001/api/assets/42 -H "Authorization: Bearer $TOK" ``` ### GET /api/assets/lookup/<assetnumber> **Auth:** JWT optional **Params:** path: `assetnumber` (string) **Purpose:** Look up an active asset by asset number (returns full type data). ```bash curl http://localhost:5001/api/assets/lookup/0205 ``` ### GET /api/assets/<asset_id>/relationships **Auth:** JWT optional **Params:** path: `asset_id` **Purpose:** Get outgoing + incoming active relationships for an asset with partner asset dicts. ```bash curl http://localhost:5001/api/assets/42/relationships ``` ### POST /api/assets/relationships **Auth:** permission `assets.create` **Params:** body: `sourceassetid` (req), `targetassetid` (req), `relationshiptypeid` (req), `notes`; `X-Import-Mode` honored **Purpose:** Create relationship, then fan out across symmetric propagation rails (Dualpath); response carries `propagated` + `propagatedcount`. ```bash curl -X POST http://localhost:5001/api/assets/relationships -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"sourceassetid":10,"targetassetid":42,"relationshiptypeid":1}' ``` ### DELETE /api/assets/relationships/<rel_id> **Auth:** permission `assets.delete` **Params:** path: `rel_id` **Purpose:** Soft-delete one relationship row (no cascade to propagated partner rows). ```bash curl -X DELETE http://localhost:5001/api/assets/relationships/7 -H "Authorization: Bearer $TOK" ``` ### GET /api/assets/map **Auth:** JWT optional **Params:** `assettype` (name), `subtype` (id, per-type), `businessunitid`, `statusid`, `locationid`, `search` **Purpose:** Unified floor-map payload: all mapped assets (with type data, primary IP, dualpath collapse) plus filter option lists. ```bash curl 'http://localhost:5001/api/assets/map?assettype=machine&statusid=1' ``` ### GET /api/assets/<asset_id>/communications **Auth:** JWT optional **Params:** path: `asset_id` **Purpose:** List active communications (IPs etc.) for an asset with `comtype_name`. ```bash curl http://localhost:5001/api/assets/42/communications ``` ### GET /api/locations/types **Auth:** JWT optional **Params:** `active=false` includes inactive **Purpose:** List location types. ```bash curl http://localhost:5001/api/locations/types ``` ### POST /api/locations/types **Auth:** admin **Params:** body: `locationtype` (req), `description`, `color` **Purpose:** Create location type; reactivates a soft-deleted same-name type instead of 409. ```bash curl -X POST http://localhost:5001/api/locations/types -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"locationtype":"Operation"}' ``` ### PUT /api/locations/types/<type_id> **Auth:** admin **Params:** body: `locationtype`, `description`, `color`, `isactive` **Purpose:** Update location type (rename conflict-checked). ```bash curl -X PUT http://localhost:5001/api/locations/types/2 -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"color":"#0a0"}' ``` ### DELETE /api/locations/types/<type_id> **Auth:** admin **Params:** path: `type_id` **Purpose:** Hard-delete location type; 409 while locations use it. ```bash curl -X DELETE http://localhost:5001/api/locations/types/2 -H "Authorization: Bearer $TOK" ``` ### GET /api/locations **Auth:** JWT optional **Params:** `page`, `per_page`, `active`, `locationname` (exact), `search` (name/building ilike) **Purpose:** List locations (paginated); exact locationname lookup for idempotent import. ```bash curl 'http://localhost:5001/api/locations?locationname=Building%201' ``` ### GET /api/locations/<location_id> **Auth:** JWT optional **Params:** path: `location_id` **Purpose:** Get one location. ```bash curl http://localhost:5001/api/locations/3 ``` ### POST /api/locations **Auth:** admin **Params:** body: `locationname` (req), `building`, `floor`, `room`, `description`, `locationtypeid`, `parentlocationid`, `mapimage`, `mapwidth`, `mapheight` **Purpose:** Create location (409 on duplicate name); honors `X-Import-Mode`. ```bash curl -X POST http://localhost:5001/api/locations -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"locationname":"Cell 12","building":"B1"}' ``` ### PUT /api/locations/<location_id> **Auth:** admin **Params:** body: `locationname`, `building`, `floor`, `room`, `description`, `locationtypeid`, `parentlocationid`, `mapimage`, `mapwidth`, `mapheight`, `isactive` **Purpose:** Update location (rename conflict-checked); honors `X-Import-Mode`. ```bash curl -X PUT http://localhost:5001/api/locations/3 -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"room":"104"}' ``` ### DELETE /api/locations/<location_id> **Auth:** admin **Params:** path: `location_id` **Purpose:** Soft-delete location. ```bash curl -X DELETE http://localhost:5001/api/locations/3 -H "Authorization: Bearer $TOK" ``` ### GET /api/vendors **Auth:** JWT optional **Params:** `page`, `per_page`, `active`, `vendor` (exact), `search` (ilike) **Purpose:** List vendors (paginated); exact vendor lookup for idempotent import. ```bash curl 'http://localhost:5001/api/vendors?search=fanuc' ``` ### GET /api/vendors/<vendor_id> **Auth:** JWT optional **Params:** path: `vendor_id` **Purpose:** Get one vendor. ```bash curl http://localhost:5001/api/vendors/5 ``` ### POST /api/vendors **Auth:** admin **Params:** body: `vendor` (req), `description`, `website`, `supportphone`, `supportemail`, `notes` **Purpose:** Create vendor (409 on duplicate); honors `X-Import-Mode`. ```bash curl -X POST http://localhost:5001/api/vendors -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"vendor":"Fanuc"}' ``` ### PUT /api/vendors/<vendor_id> **Auth:** admin **Params:** body: `vendor`, `description`, `website`, `supportphone`, `supportemail`, `notes`, `isactive` **Purpose:** Update vendor (rename conflict-checked); honors `X-Import-Mode`. ```bash curl -X PUT http://localhost:5001/api/vendors/5 -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"website":"https://fanuc.com"}' ``` ### DELETE /api/vendors/<vendor_id> **Auth:** admin **Params:** path: `vendor_id` **Purpose:** Soft-delete vendor. ```bash curl -X DELETE http://localhost:5001/api/vendors/5 -H "Authorization: Bearer $TOK" ``` ### GET /api/models **Auth:** JWT optional **Params:** `page`, `per_page`, `active`, `vendor` (id), `modeltype` (id), `modelnumber` (exact), `search` (ilike) **Purpose:** List vendor catalog models (paginated) with flattened vendor/modeltype names; exact modelnumber+vendor lookup for import. ```bash curl 'http://localhost:5001/api/models?vendor=5&search=30i' ``` ### GET /api/models/<model_id> **Auth:** JWT optional **Params:** path: `model_id` **Purpose:** Get one model with nested vendor + modeltype dicts. ```bash curl http://localhost:5001/api/models/12 ``` ### POST /api/models **Auth:** admin **Params:** body: `modelnumber` (req), `vendorid`, `modeltypeid`, `description`, `imageurl`, `documentationurl`, `notes` **Purpose:** Create model (409 on duplicate modelnumber+vendorid); honors `X-Import-Mode`. ```bash curl -X POST http://localhost:5001/api/models -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"modelnumber":"R-30iB","vendorid":5}' ``` ### PUT /api/models/<model_id> **Auth:** admin **Params:** body: `modelnumber`, `vendorid`, `modeltypeid`, `description`, `imageurl`, `documentationurl`, `notes`, `isactive` **Purpose:** Update model; honors `X-Import-Mode`. ```bash curl -X PUT http://localhost:5001/api/models/12 -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"notes":"EOL 2027"}' ``` ### DELETE /api/models/<model_id> **Auth:** admin **Params:** path: `model_id` **Purpose:** Soft-delete model. ```bash curl -X DELETE http://localhost:5001/api/models/12 -H "Authorization: Bearer $TOK" ``` ### POST /api/models/<model_id>/image **Auth:** admin **Params:** multipart/form-data: `file=` (.png/.jpg/.jpeg/.gif/.webp/.svg) **Purpose:** Upload/replace model photo (saved as `instance/modelimages/model-`, one per model); sets `imageurl`. ```bash curl -X POST http://localhost:5001/api/models/12/image -H "Authorization: Bearer $TOK" -F file=@robot.jpg ``` ### GET /api/models/image/<filename> **Auth:** none **Params:** path: `filename` **Purpose:** Serve an uploaded model image (deliberately public - asset detail pages read it without auth). ```bash curl http://localhost:5001/api/models/image/model-12.jpg ``` ### DELETE /api/models/<model_id>/image **Auth:** admin **Params:** path: `model_id` **Purpose:** Clear `imageurl` and delete the uploaded file only if it lives under `/api/models/image/` (external URLs untouched). ```bash curl -X DELETE http://localhost:5001/api/models/12/image -H "Authorization: Bearer $TOK" ``` ### GET /api/modeltypes **Auth:** JWT optional **Params:** `page`, `per_page`, `active`, `category`, `modeltype` (exact), `search` (ilike) **Purpose:** List model types (types the vendor MODELS catalog, not machines); exact modeltype lookup for import. ```bash curl 'http://localhost:5001/api/modeltypes?category=Equipment' ``` ### GET /api/modeltypes/<type_id> **Auth:** JWT optional **Params:** path: `type_id` **Purpose:** Get one model type. ```bash curl http://localhost:5001/api/modeltypes/2 ``` ### POST /api/modeltypes **Auth:** admin **Params:** body: `modeltype` (req), `category`, `description`, `icon` **Purpose:** Create model type (409 on duplicate); category defaults to Equipment; honors `X-Import-Mode`. ```bash curl -X POST http://localhost:5001/api/modeltypes -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"modeltype":"Controller"}' ``` ### PUT /api/modeltypes/<type_id> **Auth:** admin **Params:** body: `modeltype`, `category`, `description`, `icon`, `isactive` **Purpose:** Update model type (rename conflict-checked); honors `X-Import-Mode`. ```bash curl -X PUT http://localhost:5001/api/modeltypes/2 -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"icon":"mdi-chip"}' ``` ### DELETE /api/modeltypes/<type_id> **Auth:** admin **Params:** path: `type_id` **Purpose:** Soft-delete model type; 409 while models use it. ```bash curl -X DELETE http://localhost:5001/api/modeltypes/2 -H "Authorization: Bearer $TOK" ``` ### GET /api/businessunits **Auth:** JWT optional **Params:** `page`, `per_page`, `active`, `businessunit` (exact), `search` (name/code ilike) **Purpose:** List business units (paginated); exact businessunit lookup for import. ```bash curl http://localhost:5001/api/businessunits ``` ### GET /api/businessunits/<bu_id> **Auth:** JWT optional **Params:** path: `bu_id` **Purpose:** Get one business unit with parent + children. ```bash curl http://localhost:5001/api/businessunits/1 ``` ### POST /api/businessunits **Auth:** admin **Params:** body: `businessunit` (req), `code`, `description`, `parentid` **Purpose:** Create business unit (409 on duplicate); honors `X-Import-Mode`. ```bash curl -X POST http://localhost:5001/api/businessunits -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"businessunit":"Blades","code":"BLD"}' ``` ### PUT /api/businessunits/<bu_id> **Auth:** admin **Params:** body: `businessunit`, `code`, `description`, `parentid`, `isactive` **Purpose:** Update business unit (rename conflict-checked); honors `X-Import-Mode`. ```bash curl -X PUT http://localhost:5001/api/businessunits/1 -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"code":"BL"}' ``` ### DELETE /api/businessunits/<bu_id> **Auth:** admin **Params:** path: `bu_id` **Purpose:** Soft-delete business unit. ```bash curl -X DELETE http://localhost:5001/api/businessunits/1 -H "Authorization: Bearer $TOK" ``` ### GET /api/operatingsystems **Auth:** JWT optional **Params:** `page`, `per_page`, `active`, `osname` (exact), `osversion` (exact), `search` (osname ilike) **Purpose:** List operating systems (paginated); exact osname/osversion lookup for import. ```bash curl 'http://localhost:5001/api/operatingsystems?osname=Windows%2011' ``` ### GET /api/operatingsystems/<os_id> **Auth:** JWT optional **Params:** path: `os_id` **Purpose:** Get one operating system. ```bash curl http://localhost:5001/api/operatingsystems/4 ``` ### POST /api/operatingsystems **Auth:** admin **Params:** body: `osname` (req), `osversion`, `architecture`, `endoflife` **Purpose:** Create OS (409 on duplicate osname+osversion); honors `X-Import-Mode`. ```bash curl -X POST http://localhost:5001/api/operatingsystems -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"osname":"Windows 11","osversion":"24H2"}' ``` ### PUT /api/operatingsystems/<os_id> **Auth:** admin **Params:** body: `osname`, `osversion`, `architecture`, `endoflife`, `isactive` **Purpose:** Update OS; honors `X-Import-Mode`. ```bash curl -X PUT http://localhost:5001/api/operatingsystems/4 -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"endoflife":"2031-10-14"}' ``` ### DELETE /api/operatingsystems/<os_id> **Auth:** admin **Params:** path: `os_id` **Purpose:** Soft-delete OS. ```bash curl -X DELETE http://localhost:5001/api/operatingsystems/4 -H "Authorization: Bearer $TOK" ``` ### GET /api/customfields **Auth:** JWT optional **Params:** `assettypeid` (int), `active=false` includes inactive **Purpose:** List custom-field definitions, ordered by sortorder. ```bash curl 'http://localhost:5001/api/customfields?assettypeid=1' ``` ### POST /api/customfields **Auth:** admin **Params:** body: `assettypeid` (req), `label` (req), `datatype` (one of CUSTOM_FIELD_DATATYPES, default text), `fieldkey`, `options` (list or newline/comma text), `showondetail`, `showonform`, `searchable`, `sortorder` **Purpose:** Create field definition; fieldkey auto-slugged from label; reactivates a soft-deleted same-key field instead of 409. ```bash curl -X POST http://localhost:5001/api/customfields -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"assettypeid":1,"label":"Coolant Type","datatype":"select","options":["Oil","Water"]}' ``` ### PUT /api/customfields/<fieldid> **Auth:** admin **Params:** body: `label`, `datatype`, `options`, `showondetail`, `showonform`, `isactive`, `searchable`, `sortorder` **Purpose:** Update field definition (label/datatype/options/flags/sortorder). ```bash curl -X PUT http://localhost:5001/api/customfields/7 -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"searchable":true}' ``` ### DELETE /api/customfields/<fieldid> **Auth:** admin **Params:** path: `fieldid` **Purpose:** Hard-delete field definition AND all stored values for it. ```bash curl -X DELETE http://localhost:5001/api/customfields/7 -H "Authorization: Bearer $TOK" ``` ### GET /api/customfields/asset/<assetid> **Auth:** JWT optional **Params:** path: `assetid` **Purpose:** Active field defs for the asset's type merged with the asset's stored values. ```bash curl http://localhost:5001/api/customfields/asset/42 ``` ### PUT /api/customfields/asset/<assetid> **Auth:** admin **Params:** body: `{values: {fieldid: value, ...}}` **Purpose:** Upsert per-asset values; empty string clears a value; only fields of the asset's type accepted. ```bash curl -X PUT http://localhost:5001/api/customfields/asset/42 -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"values":{"7":"Oil"}}' ``` ### GET /api/supportteams **Auth:** JWT optional **Params:** `active`, `teamname` (exact) **Purpose:** List support teams with contacts; exact teamname lookup for import (not paginated). ```bash curl http://localhost:5001/api/supportteams ``` ### GET /api/supportteams/<team_id> **Auth:** JWT optional **Params:** path: `team_id` **Purpose:** Get one support team with contacts. ```bash curl http://localhost:5001/api/supportteams/2 ``` ### POST /api/supportteams **Auth:** admin **Params:** body: `teamname` (req), `teamurl`, `webhookurl`, `isactive` **Purpose:** Create support team (409 on duplicate); audit-logged; honors `X-Import-Mode`. ```bash curl -X POST http://localhost:5001/api/supportteams -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"teamname":"CNC Support"}' ``` ### PUT /api/supportteams/<team_id> **Auth:** admin **Params:** body: `teamname`, `teamurl`, `webhookurl`, `isactive` **Purpose:** Update support team (rename conflict-checked); audit-logged; honors `X-Import-Mode`. ```bash curl -X PUT http://localhost:5001/api/supportteams/2 -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"webhookurl":"https://hooks/x"}' ``` ### DELETE /api/supportteams/<team_id> **Auth:** admin **Params:** path: `team_id` **Purpose:** Hard-delete team (cascade removes contacts); 409 while applications reference it; audit-logged. ```bash curl -X DELETE http://localhost:5001/api/supportteams/2 -H "Authorization: Bearer $TOK" ``` ### POST /api/supportteams/<team_id>/contacts **Auth:** admin **Params:** body: `name` (req), `sso`, `sortorder`, `isactive` **Purpose:** Add contact to a team; audit-logged; honors `X-Import-Mode`. ```bash curl -X POST http://localhost:5001/api/supportteams/2/contacts -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"name":"Jane Doe","sso":"212345678"}' ``` ### PUT /api/supportteams/<team_id>/contacts/<contact_id> **Auth:** admin **Params:** body: `name`, `sso`, `sortorder`, `isactive` **Purpose:** Update a team contact; audit-logged; honors `X-Import-Mode`. ```bash curl -X PUT http://localhost:5001/api/supportteams/2/contacts/9 -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"sortorder":1}' ``` ### DELETE /api/supportteams/<team_id>/contacts/<contact_id> **Auth:** admin **Params:** path: `team_id`, `contact_id` **Purpose:** Hard-delete a team contact; audit-logged. ```bash curl -X DELETE http://localhost:5001/api/supportteams/2/contacts/9 -H "Authorization: Bearer $TOK" ``` ### GET /api/applications **Auth:** JWT optional **Params:** `page`, `per_page`, `active`, `showhidden`, `installable` (true/false), `appname` (exact), `search` (name/description ilike) **Purpose:** List applications (paginated) with installedcount; hidden apps excluded unless `showhidden=true`; exact appname lookup for import. ```bash curl 'http://localhost:5001/api/applications?installable=true' ``` ### GET /api/applications/<app_id> **Auth:** JWT optional **Params:** path: `app_id` **Purpose:** Get one application with active versions, installedcount, and linked KB articles. ```bash curl http://localhost:5001/api/applications/15 ``` ### POST /api/applications **Auth:** permission `applications.create` **Params:** body: `appname` (req), `appdescription`, `supportteamid`, `isinstallable`, `applicationnotes`, `installpath`, `applicationlink`, `documentationpath`, `ishidden`, `isprinter`, `islicenced`, `isrequired`, `image` **Purpose:** Create application (409 on duplicate name); audit-logged; honors `X-Import-Mode`. ```bash curl -X POST http://localhost:5001/api/applications -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"appname":"PC-DMIS","isinstallable":true}' ``` ### PUT /api/applications/<app_id> **Auth:** permission `applications.edit` **Params:** body: any of `appname`, `appdescription`, `supportteamid`, `isinstallable`, `applicationnotes`, `installpath`, `applicationlink`, `documentationpath`, `ishidden`, `isprinter`, `islicenced`, `isrequired`, `image`, `isactive` **Purpose:** Update application (rename conflict-checked); field-level change diff audit-logged; honors `X-Import-Mode`. ```bash curl -X PUT http://localhost:5001/api/applications/15 -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"supportteamid":2}' ``` ### DELETE /api/applications/<app_id> **Auth:** permission `applications.delete` **Params:** path: `app_id` **Purpose:** Soft-delete application; audit-logged. ```bash curl -X DELETE http://localhost:5001/api/applications/15 -H "Authorization: Bearer $TOK" ``` ### GET /api/applications/<app_id>/versions **Auth:** JWT optional **Params:** path: `app_id` **Purpose:** List active versions of an application (desc). ```bash curl http://localhost:5001/api/applications/15/versions ``` ### POST /api/applications/<app_id>/versions **Auth:** permission `applications.create` **Params:** body: `version` (req), `releasedate`, `notes` **Purpose:** Create app version (409 on duplicate version per app); honors `X-Import-Mode` for legacy dates. ```bash curl -X POST http://localhost:5001/api/applications/15/versions -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"version":"2024.2"}' ``` ### GET /api/applications/<app_id>/installed **Auth:** JWT optional **Params:** path: `app_id` **Purpose:** List computers with this app installed (503 if computers plugin absent). ```bash curl http://localhost:5001/api/applications/15/installed ``` ### GET /api/applications/machines/<machine_id> **Auth:** JWT optional **Params:** path: `machine_id` **Purpose:** List apps installed on a computer (machine_id is a computerid; 503 without computers plugin). ```bash curl http://localhost:5001/api/applications/machines/8 ``` ### POST /api/applications/machines/<machine_id> **Auth:** permission `applications.create` **Params:** body: `appid` (req), `appversionid` **Purpose:** Install an app on a computer; reactivates a prior soft-deleted install; 409 if already installed. ```bash curl -X POST http://localhost:5001/api/applications/machines/8 -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"appid":15}' ``` ### DELETE /api/applications/machines/<machine_id>/<app_id> **Auth:** permission `applications.delete` **Params:** path: `machine_id`, `app_id` **Purpose:** Uninstall (soft-delete install row) an app from a computer. ```bash curl -X DELETE http://localhost:5001/api/applications/machines/8/15 -H "Authorization: Bearer $TOK" ``` ### PUT /api/applications/machines/<machine_id>/<app_id> **Auth:** permission `applications.edit` **Params:** body: `appversionid` **Purpose:** Update an installed-app row (change appversionid). ```bash curl -X PUT http://localhost:5001/api/applications/machines/8/15 -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"appversionid":3}' ``` ## core-platform ### GET /api/reports **Auth:** jwt-optional **Params:** none **Purpose:** List all available reports (6 core cards plus cards contributed by enabled plugins via the get_reports hook). ```bash curl http://localhost:5001/api/reports ``` ### GET /api/reports/machines-by-type **Auth:** jwt-optional **Params:** `businessunitid` (int filter), `format=json|csv` **Purpose:** Machine count grouped by machine type (dualpath secondary bays collapsed when the site setting is on); 404 if machines plugin absent. ```bash curl 'http://localhost:5001/api/reports/machines-by-type?businessunitid=2&format=csv' ``` ### GET /api/reports/assets-by-status **Auth:** jwt-optional **Params:** `assettypeid`, `businessunitid`, `format=json|csv` **Purpose:** Asset count grouped by status with status colors. ```bash curl 'http://localhost:5001/api/reports/assets-by-status?assettypeid=1' ``` ### GET /api/reports/kb-popularity **Auth:** jwt-optional **Params:** `limit` (default 20, max 100), `format=json|csv` **Purpose:** Most-clicked knowledge base articles; 503 if knowledgebase plugin absent. ```bash curl 'http://localhost:5001/api/reports/kb-popularity?limit=10' ``` ### GET /api/reports/software-compliance **Auth:** jwt-optional **Params:** `appid` (filter to one app), `format=json|csv` **Purpose:** Required applications vs installed per PC with compliance rate and up to 100 non-compliant PCs per app; 503 if computers plugin absent. ```bash curl 'http://localhost:5001/api/reports/software-compliance?appid=5' ``` ### GET /api/reports/asset-inventory **Auth:** jwt-optional **Params:** `businessunitid`, `locationid`, `format=json|csv` **Purpose:** Complete asset inventory summary broken down by type, status, and location. ```bash curl 'http://localhost:5001/api/reports/asset-inventory?locationid=3&format=csv' ``` ### GET /api/reports/pc-relationships **Auth:** jwt-optional **Params:** `format=json|csv` **Purpose:** PC-to-shop-floor-machine relationships (machine number, vendor, model, hostname, IP) matched in both edge directions via raw SQL UNION. ```bash curl 'http://localhost:5001/api/reports/pc-relationships?format=csv' ``` ### POST /api/reports/email **Auth:** jwt + permission:reports.export **Params:** body: `subject`, `columns` `[{key,label}]`, `rows` `[{..}]`, `intro` (optional), `to` (optional email) **Purpose:** Email a report's rows as an HTML table on demand; recipient defaults to the site Alert Recipients setting; intended cron target via a PAT scoped to reports.export. ```bash curl -X POST http://localhost:5001/api/reports/email -H 'Authorization: Bearer $PAT' -H 'Content-Type: application/json' -d '{"subject":"Warranty Report","columns":[{"key":"vendor","label":"Vendor"}],"rows":[{"vendor":"Haas"}],"to":"ops@example.com"}' ``` ### GET /api/search **Auth:** jwt-optional **Params:** `q` (required, 2-200 chars) **Purpose:** Global search across assets, applications, KB, employees, notifications, custom fields, hostnames, IPs/subnets, vendor/model/type; ServiceNOW ticket prefixes and smart redirects; results capped at 50, types filterable via `search__enabled` settings. ```bash curl 'http://localhost:5001/api/search?q=tsgwp00525' ``` ### GET /api/dashboard **Auth:** jwt-optional **Params:** none **Purpose:** Dashboard summary: asset counts by type (machines/PCs/network/printers/measuring tools, dualpath-collapsed), counts by status, 10 most recent assets. ```bash curl http://localhost:5001/api/dashboard ``` ### GET /api/dashboard/summary **Auth:** jwt-optional **Params:** none **Purpose:** Alias route for the same dashboard summary handler as GET /api/dashboard. ```bash curl http://localhost:5001/api/dashboard/summary ``` ### GET /api/dashboard/stats **Auth:** jwt-optional **Params:** none **Purpose:** Asset counts grouped by every asset type with display category labels. ```bash curl http://localhost:5001/api/dashboard/stats ``` ### GET /api/dashboard/navigation **Auth:** none **Params:** none **Purpose:** Sidebar navigation items: core entries merged with get_navigation_items from every enabled plugin, sorted by position. ```bash curl http://localhost:5001/api/dashboard/navigation ``` ### GET /api/dashboard/widgets **Auth:** jwt-optional **Params:** none **Purpose:** Dashboard widget definitions aggregated from enabled plugins (get_dashboard_widgets hook), sorted by position. ```bash curl http://localhost:5001/api/dashboard/widgets ``` ### GET /api/dashboard/health **Auth:** none **Params:** none **Purpose:** Health check: runs SELECT 1 against the DB, returns ok/degraded plus app version. ```bash curl http://localhost:5001/api/dashboard/health ``` ### GET /api/dashboarddefaults/visitor-location **Auth:** none **Params:** `fqdn` (optional), `ipaddress` (optional, defaults to client IP) **Purpose:** Resolve the business unit for a kiosk/lobby display by FQDN first then IP (caller IP used when ipaddress omitted); null businessunitid when unmapped. ```bash curl 'http://localhost:5001/api/dashboarddefaults/visitor-location?fqdn=display01.wjs.geaerospace.net' ``` ### GET /api/dashboarddefaults/display-role **Auth:** none **Params:** `fqdn` (optional), `ipaddress` (optional, defaults to client IP) **Purpose:** Resolve what a display PC should show at boot: role (dashboard/lobby/partskiosk), frontend path, and business unit; null role when unmapped. ```bash curl 'http://localhost:5001/api/dashboarddefaults/display-role?ipaddress=10.1.2.3' ``` ### GET /api/dashboarddefaults **Auth:** jwt-optional **Params:** none **Purpose:** List all active display-to-business-unit mappings. ```bash curl http://localhost:5001/api/dashboarddefaults ``` ### POST /api/dashboarddefaults **Auth:** jwt + role:admin **Params:** body: `fqdn`, `ipaddress`, `displayrole` (dashboard|lobby|partskiosk, default dashboard), `businessunitid`, `description` **Purpose:** Create a display mapping; requires fqdn or ipaddress, dashboard role requires businessunitid, 409 on duplicate fqdn/IP; audit-logged. ```bash curl -X POST http://localhost:5001/api/dashboarddefaults -H 'Authorization: Bearer $JWT' -H 'Content-Type: application/json' -d '{"ipaddress":"10.1.2.3","displayrole":"dashboard","businessunitid":2}' ``` ### PUT /api/dashboarddefaults/<int:default_id> **Auth:** jwt + role:admin **Params:** body: any of `fqdn`, `ipaddress`, `displayrole`, `businessunitid`, `description` **Purpose:** Update a display mapping; non-dashboard roles get businessunitid nulled, dashboard role must keep one. ```bash curl -X PUT http://localhost:5001/api/dashboarddefaults/7 -H 'Authorization: Bearer $JWT' -H 'Content-Type: application/json' -d '{"displayrole":"lobby"}' ``` ### DELETE /api/dashboarddefaults/<int:default_id> **Auth:** jwt + role:admin **Params:** none **Purpose:** Soft-delete (deactivate) a display mapping. ```bash curl -X DELETE http://localhost:5001/api/dashboarddefaults/7 -H 'Authorization: Bearer $JWT' ``` ### POST /api/collector/<pluginname> **Auth:** api-key (X-API-Key: per-plugin `COLLECTOR_API_KEY_` or shared `COLLECTOR_API_KEY`, or a collector.ingest-scoped managed PAT via Bearer/X-API-Key) **Params:** body: JSON payload whose schema identityfield (e.g. hostname) is required; rest is plugin-defined **Purpose:** Generic collector ingest (ADR-006): schema-validated identity field, idempotent upsert via the plugin's apply_collector_payload; audit-logged; 404 when no collector registered for the plugin. ```bash curl -X POST http://localhost:5001/api/collector/computers -H 'X-API-Key: $KEY' -H 'Content-Type: application/json' -d '{"hostname":"tsgwp00525","serialnumber":"ABC123"}' ``` ### GET /api/collector/_schemas **Auth:** jwt **Params:** none **Purpose:** List collector schemas for all enabled plugins that accept collector input. ```bash curl http://localhost:5001/api/collector/_schemas -H 'Authorization: Bearer $JWT' ``` ### POST /api/collector/pc **Auth:** api-key **Params:** body: `hostname` (required), `lastboottime` (ISO), `currentuser`, `serialnumber` **Purpose:** Legacy computers-specific ingest: update one PC matched by hostname (or asset number) - lastreporteddate, lastboottime, loggedinuser, serialnumber. ```bash curl -X POST http://localhost:5001/api/collector/pc -H 'X-API-Key: $KEY' -H 'Content-Type: application/json' -d '{"hostname":"tsgwp00525","currentuser":"212345678"}' ``` ### POST /api/collector/apps **Auth:** api-key **Params:** body: `hostname` (required), `apps` `[{appname, version}]` (required) **Purpose:** Update installed applications for one PC; only apps already in the Application table are tracked, others skipped; returns created/updated/skipped counts. ```bash curl -X POST http://localhost:5001/api/collector/apps -H 'X-API-Key: $KEY' -H 'Content-Type: application/json' -d '{"hostname":"tsgwp00525","apps":[{"appname":"PC-DMIS","version":"2023.2"}]}' ``` ### POST /api/collector/heartbeat **Auth:** api-key **Params:** body: `hostname` (string) or `hostnames` (array) **Purpose:** Record PC online heartbeat (single hostname or batch); stamps lastreporteddate, returns updated count and notfound list. ```bash curl -X POST http://localhost:5001/api/collector/heartbeat -H 'X-API-Key: $KEY' -H 'Content-Type: application/json' -d '{"hostnames":["pc1","pc2"]}' ``` ### POST /api/collector/bulk **Auth:** api-key **Params:** body: `pcs` `[{hostname (required), currentuser, lastboottime}]` **Purpose:** Bulk update many PCs in one call (lastreporteddate, currentuser, lastboottime per entry); returns updated/notfound/errors. ```bash curl -X POST http://localhost:5001/api/collector/bulk -H 'X-API-Key: $KEY' -H 'Content-Type: application/json' -d '{"pcs":[{"hostname":"pc1","currentuser":"212345678"}]}' ``` ### GET /api/collector/status **Auth:** api-key **Params:** none **Purpose:** Collector API liveness/credential check; returns timestamp and the collector endpoint list. ```bash curl http://localhost:5001/api/collector/status -H 'X-API-Key: $KEY' ``` ### GET /api/auditlogs **Auth:** jwt + permission:audit.view **Params:** `page` (default 1), `perpage` (default 50, max 200), `action` (created|updated|deleted), `entitytype`, `userid` (int), `search` (entityname/username ilike), `from_date`, `to_date` (ISO) **Purpose:** List audit logs with filtering and pagination, newest first; rows enriched with best-effort SSO-to-full-name resolution. ```bash curl 'http://localhost:5001/api/auditlogs?action=deleted&perpage=100' -H 'Authorization: Bearer $JWT' ``` ### GET /api/auditlogs/entity/<entitytype>/<int:entityid> **Auth:** jwt + permission:audit.view **Params:** path only **Purpose:** Full audit history for one entity, newest first. ```bash curl http://localhost:5001/api/auditlogs/entity/Asset/42 -H 'Authorization: Bearer $JWT' ``` ### GET /api/auditlogs/stats **Auth:** jwt + permission:audit.view **Params:** none **Purpose:** Audit statistics: counts by action and entity type, last-7-days activity count, top 5 most active users. ```bash curl http://localhost:5001/api/auditlogs/stats -H 'Authorization: Bearer $JWT' ``` ### GET /api/plugins **Auth:** jwt-optional **Params:** none **Purpose:** List all discovered plugins (enabled or not) with metadata plus the framework contract version. ```bash curl http://localhost:5001/api/plugins ``` ### GET /api/plugins/enabled **Auth:** none (jwt-optional decorator, no claims used) **Params:** none **Purpose:** Flat sorted array of enabled plugin names (registry read, no DB); deliberately anonymous so kiosk routes can gate plugin-owned frontend routes. ```bash curl http://localhost:5001/api/plugins/enabled ``` ### PUT /api/plugins/<name> **Auth:** jwt + role:admin **Params:** body: `enabled` (bool, required) **Purpose:** Enable or disable a plugin (route changes need an app restart); 409 when unknown or a dependency conflict refuses the change. ```bash curl -X PUT http://localhost:5001/api/plugins/warranty -H 'Authorization: Bearer $JWT' -H 'Content-Type: application/json' -d '{"enabled":false}' ``` ### GET /api/pluginui/settings-cards **Auth:** jwt-optional **Params:** none **Purpose:** Merge enabled plugins' settings-catalog cards (get_settings_cards hook) sorted by position for the settings rail/overview. ```bash curl http://localhost:5001/api/pluginui/settings-cards ``` ### GET /api/pluginui/asset-panels **Auth:** jwt-optional **Params:** `assetid` (int, required); 400 without it, 404 if asset missing **Purpose:** Asset-detail panels from enabled plugins (get_asset_panels hook) matching one asset's type ('*' wildcard supported), sorted by position. ```bash curl 'http://localhost:5001/api/pluginui/asset-panels?assetid=42' ``` ### GET /api/pluginui/map-overlays **Auth:** jwt-optional **Params:** none **Purpose:** Merge enabled plugins' map overlay declarations (get_map_overlays hook), sorted by position. ```bash curl http://localhost:5001/api/pluginui/map-overlays ``` ### GET /api/pluginui/asset-presentation **Auth:** jwt-optional **Params:** none **Purpose:** Merge enabled plugins' asset-type presentation entries (icon + detail route per type) used by search rows and cross-links. ```bash curl http://localhost:5001/api/pluginui/asset-presentation ``` ## plugin-computers ### GET /api/computers/types **Auth:** jwt-optional **Params:** `page`, `per_page`, `active` (default true; `'false'` includes inactive), `search` (ilike on computertype) **Purpose:** List computer types, paginated, active-only by default. ```bash curl -H 'Authorization: Bearer $TOKEN' 'http://localhost:5001/api/computers/types?search=kiosk&per_page=50' ``` ### GET /api/computers/types/<int:type_id> **Auth:** jwt-optional **Params:** `type_id` in path **Purpose:** Get a single computer type by ID. ```bash curl 'http://localhost:5001/api/computers/types/3' ``` ### POST /api/computers/types **Auth:** permission `computers.create` (jwt_required) **Params:** body: `computertype` (required), `description`, `icon`, `color`. Matching a deactivated type revives it instead of 409. **Purpose:** Create (or reactivate) a computer type. ```bash curl -X POST -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{"computertype":"Shopfloor","color":"#0066cc"}' 'http://localhost:5001/api/computers/types' ``` ### PUT /api/computers/types/<int:type_id> **Auth:** permission `computers.edit` (jwt_required) **Params:** body: `computertype`, `description`, `icon`, `color`, `isactive`. 409 on duplicate name. **Purpose:** Update a computer type. ```bash curl -X PUT -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{"isactive":false}' 'http://localhost:5001/api/computers/types/3' ``` ### DELETE /api/computers/types/<int:type_id> **Auth:** permission `computers.delete` (jwt_required) **Params:** `type_id` in path. 409 if any Computer still uses the type. **Purpose:** Hard-delete a computer type when unused. ```bash curl -X DELETE -H 'Authorization: Bearer $TOKEN' 'http://localhost:5001/api/computers/types/3' ``` ### GET /api/computers/protocols **Auth:** jwt-optional **Params:** `active` (default true; `'false'` includes disabled) **Purpose:** List remote-access protocols (VNC/WinRM/RDP catalog), unpaginated. ```bash curl 'http://localhost:5001/api/computers/protocols?active=false' ``` ### POST /api/computers/protocols **Auth:** permission `computers.edit` (jwt_required) **Params:** body: `name`, `scheme`, `linktemplate` (all required), `defaultport`, `isactive`. 409 on duplicate name. **Purpose:** Create an access protocol. ```bash curl -X POST -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{"name":"VNC","scheme":"vnc","defaultport":5900,"linktemplate":"vnc://{host}:{port}"}' 'http://localhost:5001/api/computers/protocols' ``` ### PUT|PATCH /api/computers/protocols/<int:protocol_id> **Auth:** permission `computers.edit` (jwt_required) **Params:** body: `name`, `scheme`, `linktemplate`, `defaultport`, `isactive` (all optional) **Purpose:** Update an access protocol. ```bash curl -X PATCH -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{"defaultport":5901}' 'http://localhost:5001/api/computers/protocols/2' ``` ### DELETE /api/computers/protocols/<int:protocol_id> **Auth:** permission `computers.edit` (jwt_required) **Params:** `protocol_id` in path. If referenced by any ComputerAccess it deactivates instead of deleting. **Purpose:** Delete an access protocol (soft-deactivate when in use). ```bash curl -X DELETE -H 'Authorization: Bearer $TOKEN' 'http://localhost:5001/api/computers/protocols/2' ``` ### GET /api/computers/display-kiosks **Auth:** jwt-optional **Params:** none. Uses pctype mapping for `gea-shopfloor-display` (default "Kiosk") and the `display_fqdn_domain` setting. **Purpose:** List display-kiosk computers with derived `F.` FQDN for the Dashboard Defaults picker. ```bash curl -H 'Authorization: Bearer $TOKEN' 'http://localhost:5001/api/computers/display-kiosks' ``` ### GET /api/computers **Auth:** jwt-optional **Params:** `page`, `per_page`, `active`, `search` (assetnumber/name/serialnumber/hostname ilike), `assetnumber` (exact, for idempotent import), `typeid`|`type_id`, `osid`|`os_id`, `locationid`|`location_id`, `businessunitid`|`businessunit_id`, `shopfloor` (true/false), `sort` (hostname|assetnumber|name|lastreporteddate), `dir` (asc|desc) **Purpose:** List computers (asset + computer + resolved access links), filtered and paginated. ```bash curl 'http://localhost:5001/api/computers?shopfloor=true&sort=lastreporteddate&dir=desc&per_page=25' ``` ### GET /api/computers/<int:computer_id> **Auth:** jwt-optional **Params:** `computer_id` in path **Purpose:** Get one computer with full detail: asset fields, computer extension, communications, resolved access links. ```bash curl 'http://localhost:5001/api/computers/42' ``` ### GET /api/computers/by-asset/<int:asset_id> **Auth:** jwt-optional **Params:** `asset_id` in path **Purpose:** Get computer record by its core asset ID. ```bash curl 'http://localhost:5001/api/computers/by-asset/1234' ``` ### GET /api/computers/by-hostname/<hostname> **Auth:** jwt-optional **Params:** `hostname` in path (exact match) **Purpose:** Get computer record by hostname. ```bash curl 'http://localhost:5001/api/computers/by-hostname/tsgwp00525' ``` ### POST /api/computers **Auth:** permission `computers.create` (jwt_required) **Params:** body: `assetnumber` (required); `name`, `serialnumber`, `gaugelabreference`, `maintenancereference`, `statusid`, `locationid`, `businessunitid`, `mapx`, `mapy`, `notes`, `computertypeid`, `hostname`, `osid`, `vendorid`, `modelnumberid`, `loggedinuser`, `lastreporteddate`, `lastboottime`, `ipaddress` (creates primary IP communication), `accessmethods` `[{protocolid, portoverride?}]`. `X-Import-Mode` header preserves legacy timestamps. 409 on duplicate assetnumber or hostname. **Purpose:** Create a computer (Asset + Computer records, optional primary IP and access methods), audit-logged. ```bash curl -X POST -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{"assetnumber":"PC-0042","hostname":"tsgwp00042","computertypeid":1,"osid":2,"ipaddress":"10.1.2.3","accessmethods":[{"protocolid":1}]}' 'http://localhost:5001/api/computers' ``` ### PUT /api/computers/<int:computer_id> **Auth:** permission `computers.edit` (jwt_required) **Params:** body: any asset field (`assetnumber`, `name`, `serialnumber`, `gaugelabreference`, `maintenancereference`, `statusid`, `locationid`, `businessunitid`, `mapx`, `mapy`, `notes`, `isactive`) or computer field (`computertypeid`, `hostname`, `osid`, `vendorid`, `modelnumberid`, `loggedinuser`, `lastreporteddate`, `lastboottime`). `ipaddress` upserts/clears the primary IP communication; `accessmethods` replaces the protocol list. 409 on assetnumber/hostname conflict. Changes audit-logged. **Purpose:** Update a computer's asset and extension fields. ```bash curl -X PUT -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{"locationid":7,"ipaddress":"10.1.2.4"}' 'http://localhost:5001/api/computers/42' ``` ### DELETE /api/computers/<int:computer_id> **Auth:** permission `computers.delete` (jwt_required) **Params:** `computer_id` in path **Purpose:** Soft-delete a computer (sets asset `isactive=false`), audit-logged. ```bash curl -X DELETE -H 'Authorization: Bearer $TOKEN' 'http://localhost:5001/api/computers/42' ``` ### GET /api/computers/<int:computer_id>/apps **Auth:** jwt-optional **Params:** `computer_id` in path. Returns active installs only. **Purpose:** List installed applications on a computer. ```bash curl 'http://localhost:5001/api/computers/42/apps' ``` ### POST /api/computers/<int:computer_id>/apps **Auth:** permission `computers.create` (jwt_required) **Params:** body: `appid` (required, must exist in Applications), `appversionid`. Reactivates a soft-deleted install; 409 if already installed. **Purpose:** Record an application install on a computer. ```bash curl -X POST -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{"appid":17,"appversionid":3}' 'http://localhost:5001/api/computers/42/apps' ``` ### DELETE /api/computers/<int:computer_id>/apps/<int:app_id> **Auth:** permission `computers.delete` (jwt_required) **Params:** `computer_id` and `app_id` in path **Purpose:** Soft-remove an installed application (`isactive=false`). ```bash curl -X DELETE -H 'Authorization: Bearer $TOKEN' 'http://localhost:5001/api/computers/42/apps/17' ``` ### POST /api/computers/<int:computer_id>/report **Auth:** permission `computers.create` (jwt_required) **Params:** body (all optional): `loggedinuser`, `lastboottime`. Server sets `lastreporteddate` to now (UTC). **Purpose:** Agent status check-in: refresh last-reported timestamp plus logged-in user and boot time. ```bash curl -X POST -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{"loggedinuser":"cproudlock","lastboottime":"2026-07-30T06:00:00"}' 'http://localhost:5001/api/computers/42/report' ``` ### GET /api/computers/dashboard/summary **Auth:** jwt-optional **Params:** none **Purpose:** Dashboard counts: total active computers, breakdown by type and OS, shopfloor vs non-shopfloor. ```bash curl 'http://localhost:5001/api/computers/dashboard/summary' ``` ## plugin-employees ### GET /api/employees/search **Auth:** none **Params:** `q` (query string, min 2 chars, required), `limit` (max results, default 10, capped 50) **Purpose:** Search employees by first name, last name, or SSO substring (self-hosted table or external HR DB depending on `employee_directory_mode` setting). ```bash curl 'http://localhost:5001/api/employees/search?q=smith&limit=5' ``` ### GET /api/employees/lookup/<sso> **Auth:** none **Params:** `sso` (path, numeric) **Purpose:** Look up a single employee by numeric SSO; returns directory fields plus resolved `photourl`. ```bash curl 'http://localhost:5001/api/employees/lookup/210009518' ``` ### GET /api/employees/lookup **Auth:** none **Params:** `sso` (query, comma-separated numeric SSOs, at least one required) **Purpose:** Bulk lookup of multiple employees by SSO list; returns `employees` array plus a joined names string. ```bash curl 'http://localhost:5001/api/employees/lookup?sso=210009518,210001234' ``` ### GET /api/employees/directory **Auth:** jwt-optional **Params:** none **Purpose:** List the full self-hosted directory for the management page; 400 when directory mode is external. ```bash curl -H 'Authorization: Bearer $TOKEN' 'http://localhost:5001/api/employees/directory' ``` ### POST /api/employees/directory **Auth:** jwt + require_role admin **Params:** JSON body: `sso` (numeric, required), `firstname`, `lastname` (required), `team`, `role`, `picture` (also accepts external-style keys `SSO`/`First_Name`/`Last_Name`/`Team`/`Role`/`Picture`) **Purpose:** Create a self-hosted directory employee; 409 if SSO exists, 400 in external mode. ```bash curl -X POST -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{"sso":210009518,"firstname":"Jane","lastname":"Doe","team":"CNC","role":"Machinist"}' 'http://localhost:5001/api/employees/directory' ``` ### PUT /api/employees/directory/<int:sso> **Auth:** jwt + require_role admin **Params:** `sso` (path); JSON body: `firstname`, `lastname`, `team`, `role`, `picture` (external-style keys also accepted; `team`/`role`/`picture` can be cleared) **Purpose:** Update a self-hosted directory employee's name/team/role/picture; 404 if missing, 400 in external mode. ```bash curl -X PUT -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{"team":"Quality"}' 'http://localhost:5001/api/employees/directory/210009518' ``` ### DELETE /api/employees/directory/<int:sso> **Auth:** jwt + require_role admin **Params:** `sso` (path) **Purpose:** Delete a self-hosted directory employee; 404 if missing, 400 in external mode. ```bash curl -X DELETE -H 'Authorization: Bearer $TOKEN' 'http://localhost:5001/api/employees/directory/210009518' ``` ### POST /api/employees/directory/import **Auth:** jwt + require_role admin **Params:** multipart `file=` OR JSON body `{"csv": "..."}`; rows missing numeric sso or names are skipped **Purpose:** Bulk upsert the self-hosted directory from CSV (headers `SSO,First_Name,Last_Name,Team,Role,Picture` case-insensitive; plain `firstname`/`lastname` also accepted); returns added/updated/skipped counts. ```bash curl -X POST -H 'Authorization: Bearer $TOKEN' -F 'file=@employees.csv' 'http://localhost:5001/api/employees/directory/import' ``` ### POST /api/employees/<int:sso>/photo **Auth:** jwt + require_role admin **Params:** `sso` (path); multipart/form-data `file=`, extensions .png/.jpg/.jpeg/.gif/.webp only **Purpose:** Upload or replace a self-hosted employee's photo (saved as `photo-` in instance employeephotos dir; old file wiped even on extension change); 409 in external mode, 404 if employee missing. ```bash curl -X POST -H 'Authorization: Bearer $TOKEN' -F 'file=@jane.jpg' 'http://localhost:5001/api/employees/210009518/photo' ``` ### GET /api/employees/photo/<path:filename> **Auth:** none **Params:** `filename` (path, e.g. `photo-210009518.jpg`) **Purpose:** Serve an uploaded employee photo file from the instance employeephotos dir (public so kiosk recognition cards can read it). ```bash curl 'http://localhost:5001/api/employees/photo/photo-210009518.jpg' ``` ### DELETE /api/employees/<int:sso>/photo **Auth:** jwt + require_role admin **Params:** `sso` (path) **Purpose:** Clear a self-hosted employee's photo record and delete the uploaded file; 409 in external mode, 404 if employee missing. ```bash curl -X DELETE -H 'Authorization: Bearer $TOKEN' 'http://localhost:5001/api/employees/210009518/photo' ``` ## plugin-geenforce ### GET /api/geenforce/manifest **Auth:** api-key (managed service token with `geenforce.fetch` scope via `X-API-Key` or Bearer PAT) OR source IP in `geenforce_allowed_cidrs`; resource-bound tokens restricted to their listed scopes (403 otherwise) **Params:** query: `pctype` (required, = scopename), `phase` (default `runtime`); header: `If-None-Match` for 304 **Purpose:** Serve the current PUBLISHED manifest JSON snapshot for a scope (never the live draft) to the GE-Enforce client, with ETag/304 support and `X-Manifest-Version` header. ```bash curl -H 'X-API-Key: $TOKEN' 'http://localhost:5001/api/geenforce/manifest?pctype=cmm&phase=runtime' ``` ### GET /api/geenforce/payload/<sha256> **Auth:** api-key (`geenforce.fetch` service token) OR IP allowlist; resource-bound tokens get 404 for blobs not referenced by their scopes **Params:** path: `sha256` (64 lowercase hex chars, 400 otherwise); header: `If-None-Match` (ETag = the hash) **Purpose:** Download a payload blob by content hash (blob store first, then inline DB payload) so share-less PCs can pull installers over HTTPS; per-IP rate limited (120/60s default) and size-capped (512MB default, 413 above). ```bash curl -H 'X-API-Key: $TOKEN' -o installer.exe 'http://localhost:5001/api/geenforce/payload/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' ``` ### POST /api/geenforce/report **Auth:** api-key (managed service token with `geenforce.report` scope) OR IP allowlist **Params:** JSON body: `hostname` (required); remainder parsed by `service.record_enforcement_report` (`scopename`, `phase`, `appliedversion`, `enforcerversion`, `status`, per-entry results, counts); 400 on ValueError **Purpose:** Record one PC's enforcement cycle: applied manifest version plus per-entry self-heal outcomes (installed/skipped/failed); returns `reportid` + `status`. ```bash curl -X POST -H 'X-API-Key: $TOKEN' -H 'Content-Type: application/json' -d '{"hostname":"tsgwp00525","scopename":"cmm","appliedversion":4,"results":[{"entryname":"7zip","action":"installed"}]}' http://localhost:5001/api/geenforce/report ``` ### GET /api/geenforce/scopes **Auth:** jwt + permission `geenforce.manage` **Params:** none **Purpose:** List all imaging PC-type scopes with entry counts and current published version numbers, ordered by phase then scopename. ```bash curl -H 'Authorization: Bearer $JWT' http://localhost:5001/api/geenforce/scopes ``` ### POST /api/geenforce/scopes **Auth:** jwt + permission `geenforce.manage` **Params:** JSON body: `scopename` (required), `phase` (default `runtime`, must be in PHASES), `manifestversion` (default `1.0`), `description`, `computertypeid`, `measuringtooltypeid`, `iscommon` (defaults true when scopename == `common`) **Purpose:** Create a new manifest scope; 400 if scopename missing, phase invalid, or scope already exists for that scopename+phase; returns 201 with scope summary. ```bash curl -X POST -H 'Authorization: Bearer $JWT' -H 'Content-Type: application/json' -d '{"scopename":"cmm","phase":"runtime","description":"CMM bays"}' http://localhost:5001/api/geenforce/scopes ``` ### GET /api/geenforce/scopes/<int:scopeid> **Auth:** jwt + permission `geenforce.manage` **Params:** path: `scopeid` **Purpose:** Get one scope's summary plus its full draft entry list (each entry includes entryid, sortorder, curated appid/appname link, and inline-payload metadata). ```bash curl -H 'Authorization: Bearer $JWT' http://localhost:5001/api/geenforce/scopes/3 ``` ### PUT /api/geenforce/scopes/<int:scopeid> **Auth:** jwt + permission `geenforce.manage` **Params:** JSON body (all optional): `description`, `computertypeid`, `measuringtooltypeid`, `manifestversion` (stringified), `iscommon` (bool-coerced) **Purpose:** Update scope metadata fields (only keys present in the body are changed); scopename and phase are immutable here. ```bash curl -X PUT -H 'Authorization: Bearer $JWT' -H 'Content-Type: application/json' -d '{"description":"updated","manifestversion":"1.1"}' http://localhost:5001/api/geenforce/scopes/3 ``` ### DELETE /api/geenforce/scopes/<int:scopeid> **Auth:** jwt + permission `geenforce.manage` **Params:** path: `scopeid` **Purpose:** Delete a scope (and via cascade its entries); returns `{deleted: scopeid}`. ```bash curl -X DELETE -H 'Authorization: Bearer $JWT' http://localhost:5001/api/geenforce/scopes/3 ``` ### GET /api/geenforce/scopes/<int:scopeid>/preview **Auth:** jwt + permission `geenforce.manage` **Params:** path: `scopeid` **Purpose:** Render the DRAFT manifest JSON that a publish would freeze, for admin review before shipping. ```bash curl -H 'Authorization: Bearer $JWT' http://localhost:5001/api/geenforce/scopes/3/preview ``` ### GET /api/geenforce/applications **Auth:** jwt + permission `geenforce.manage` **Params:** none **Purpose:** List the core active Applications catalog (appid + appname) for the curated entry-to-app link picker in the entry editor. ```bash curl -H 'Authorization: Bearer $JWT' http://localhost:5001/api/geenforce/applications ``` ### POST /api/geenforce/scopes/<int:scopeid>/entries **Auth:** jwt + permission `geenforce.manage` **Params:** path: `scopeid`; JSON body: `Name` (required), `Type` (required, one of ENTRY_TYPES), optional `appid` (curated app link, unknown/non-numeric ids ignored), plus manifest fields consumed by `build_entry` (PCTypes, TargetHostnames, DetectionValue, etc.) **Purpose:** Create a manifest entry in a scope at the next sortorder; validates Name (required) and Type (must be in ENTRY_TYPES); 400 on duplicate Name in scope; returns 201 with entry payload. ```bash curl -X POST -H 'Authorization: Bearer $JWT' -H 'Content-Type: application/json' -d '{"Name":"7-Zip","Type":"App","appid":12}' http://localhost:5001/api/geenforce/scopes/3/entries ``` ### PUT /api/geenforce/entries/<int:entryid> **Auth:** jwt + permission `geenforce.manage` **Params:** path: `entryid`; JSON body: `Name` (required), `Type` (required), optional `appid`, plus manifest fields **Purpose:** Replace an entry's fields from the payload (re-populates via `populate_entry`, re-creating the one-to-one InUseCheck); same Name/Type validation and duplicate-Name 400 as create. ```bash curl -X PUT -H 'Authorization: Bearer $JWT' -H 'Content-Type: application/json' -d '{"Name":"7-Zip","Type":"App","DetectionValue":"24.08"}' http://localhost:5001/api/geenforce/entries/17 ``` ### DELETE /api/geenforce/entries/<int:entryid> **Auth:** jwt + permission `geenforce.manage` **Params:** path: `entryid` **Purpose:** Delete a manifest entry; returns `{deleted: entryid}`. ```bash curl -X DELETE -H 'Authorization: Bearer $JWT' http://localhost:5001/api/geenforce/entries/17 ``` ### PUT /api/geenforce/scopes/<int:scopeid>/entries/reorder **Auth:** jwt + permission `geenforce.manage` **Params:** path: `scopeid`; JSON body: `order` = [entryid, ...] (must match the scope's entry ids exactly, 400 otherwise) **Purpose:** Set entry ordering from an entryid list. ```bash curl -X PUT -H 'Authorization: Bearer $JWT' -H 'Content-Type: application/json' -d '{"order":[17,15,16]}' http://localhost:5001/api/geenforce/scopes/3/entries/reorder ``` ### GET /api/geenforce/scopes/<int:scopeid>/simulate **Auth:** jwt + permission `geenforce.manage` **Params:** path: `scopeid`; query (all optional): `pctype` (defaults to scopename), `subtype`, `hostname`, `machinenumber`, `cmmversion`; phase comes from the scope **Purpose:** Simulate which draft entries would apply to a given machine profile and why the rest are filtered out (PCTypes/TargetHostnames/TargetMachineNumbers/_CmmVersion), using the engine-mirror filters. ```bash curl -H 'Authorization: Bearer $JWT' 'http://localhost:5001/api/geenforce/scopes/3/simulate?hostname=tsgwp00525&cmmversion=2023.2' ``` ### GET /api/geenforce/scopes/<int:scopeid>/compliance **Auth:** jwt + permission `geenforce.manage` **Params:** path: `scopeid` **Purpose:** Fleet install coverage per app-linked entry (installed/version-match counts from the computers plugin's ComputerInstalledApp; null counts with `computersplugin:false` when that plugin is absent). ```bash curl -H 'Authorization: Bearer $JWT' http://localhost:5001/api/geenforce/scopes/3/compliance ``` ### POST /api/geenforce/entries/<int:entryid>/payload **Auth:** jwt + permission `geenforce.publish` **Params:** path: `entryid`; multipart/form-data: `file` (required) **Purpose:** Upload an inline payload file (max 1 MB, rejects empty) for an entry and point the entry at it (stores sha256, filename, mimetype); returns 201 with entry payload. ```bash curl -X POST -H 'Authorization: Bearer $JWT' -F 'file=@fix.ps1' http://localhost:5001/api/geenforce/entries/17/payload ``` ### GET /api/geenforce/entries/<int:entryid>/payload **Auth:** jwt + permission `geenforce.manage` **Params:** path: `entryid` **Purpose:** Download the stored inline payload bytes for an entry as an attachment (404 if the entry has no payload). ```bash curl -H 'Authorization: Bearer $JWT' -o fix.ps1 http://localhost:5001/api/geenforce/entries/17/payload ``` ### POST /api/geenforce/scopes/<int:scopeid>/publish **Auth:** jwt + permission `geenforce.publish` **Params:** path: `scopeid`; JSON body (optional): `notes` **Purpose:** Freeze the scope's draft into a new published version (`service.publish_scope`), recording the publishing user from the JWT identity and optional notes; returns 201 with the new versionnumber. ```bash curl -X POST -H 'Authorization: Bearer $JWT' -H 'Content-Type: application/json' -d '{"notes":"add 7zip 24.08"}' http://localhost:5001/api/geenforce/scopes/3/publish ``` ### GET /api/geenforce/scopes/<int:scopeid>/versions **Auth:** jwt + permission `geenforce.manage` **Params:** path: `scopeid` **Purpose:** List published versions for a scope, newest first (versionnumber, iscurrent, publishedat, publishedby, notes). ```bash curl -H 'Authorization: Bearer $JWT' http://localhost:5001/api/geenforce/scopes/3/versions ``` ### GET /api/geenforce/scopes/<int:scopeid>/versions/<int:versionnumber> **Auth:** jwt + permission `geenforce.manage` **Params:** path: `scopeid`, `versionnumber` **Purpose:** Fetch one published version's frozen manifest JSON (parsed and returned in the success envelope). ```bash curl -H 'Authorization: Bearer $JWT' http://localhost:5001/api/geenforce/scopes/3/versions/4 ``` ### POST /api/geenforce/scopes/<int:scopeid>/rollback **Auth:** jwt + permission `geenforce.publish` **Params:** path: `scopeid`; JSON body: `versionnumber` (required, int) **Purpose:** Roll the scope's current published pointer back to an earlier versionnumber (`service.rollback_scope`); 400 with the error message if the version is invalid. ```bash curl -X POST -H 'Authorization: Bearer $JWT' -H 'Content-Type: application/json' -d '{"versionnumber":3}' http://localhost:5001/api/geenforce/scopes/3/rollback ``` ### POST /api/geenforce/scopes/<int:scopeid>/export-share **Auth:** jwt + permission `geenforce.publish` **Params:** path: `scopeid`; no body **Purpose:** Write the scope's current published JSON to the configured share root (`geenforce_share_root` setting), backing up the old file to `_meta/history`; 400 if the share root is unconfigured or the export fails. ```bash curl -X POST -H 'Authorization: Bearer $JWT' http://localhost:5001/api/geenforce/scopes/3/export-share ``` ### GET /api/geenforce/config **Auth:** jwt + permission `geenforce.manage` **Params:** none **Purpose:** Read plugin config: the on-share export root (`geenforce_share_root`) and the client IP allowlist CIDRs (`geenforce_allowed_cidrs`). ```bash curl -H 'Authorization: Bearer $JWT' http://localhost:5001/api/geenforce/config ``` ### PUT /api/geenforce/config **Auth:** jwt + permission `geenforce.publish` **Params:** JSON body (both optional): `shareroot` (string path), `allowedcidrs` (CSV/newline CIDR list); allowedcidrs is validated/normalized (comma/newline-separated CIDRs or bare IPs, 400 listing any bad entries); only keys present in the body are written **Purpose:** Update plugin config settings. ```bash curl -X PUT -H 'Authorization: Bearer $JWT' -H 'Content-Type: application/json' -d '{"allowedcidrs":"10.20.30.0/24, 192.168.5.7"}' http://localhost:5001/api/geenforce/config ``` ### GET /api/geenforce/reports **Auth:** jwt + permission `geenforce.manage` **Params:** query (optional): `hostname` (ILIKE match), `scopename` (exact) **Purpose:** Latest enforcement report per PC (iscurrent rows) for the fleet compliance view: applied vs latest published version (`receivedlatest` flag), install/skip/fail/filtered counts, status, check-in times. ```bash curl -H 'Authorization: Bearer $JWT' 'http://localhost:5001/api/geenforce/reports?scopename=cmm' ``` ### GET /api/geenforce/reports/<int:reportid> **Auth:** jwt + permission `geenforce.manage` **Params:** path: `reportid` **Purpose:** One enforcement report in detail with per-entry outcomes (entryname, action, selfhealed, exitcode, message) plus applied-vs-latest version comparison. ```bash curl -H 'Authorization: Bearer $JWT' http://localhost:5001/api/geenforce/reports/42 ``` ## plugin-knowledgebase ### GET /api/knowledgebase **Auth:** jwt-optional **Params:** query: `page`, `per_page`, `search` (ILIKE on shortdescription/keywords/application name), `appid` (int filter), `linkurl` (exact natural-key match), `shortdescription` (exact match), `sort` (`clicks|topic|description|lastupdated`, default `clicks`), `order` (`asc|desc`, default `desc`) **Purpose:** List active KB articles with search, topic filter, natural-key lookup, sorting, and pagination; each item includes its linked application (appid/appname) or null. ```bash curl 'http://localhost:5001/api/knowledgebase?search=printer&sort=clicks&order=desc&page=1&per_page=20' ``` ### GET /api/knowledgebase/stats **Auth:** jwt-optional **Params:** none **Purpose:** Return aggregate stats for active articles: `totalclicks` (sum of clicks) and `totalarticles` (count). ```bash curl 'http://localhost:5001/api/knowledgebase/stats' ``` ### GET /api/knowledgebase/<int:link_id> **Auth:** jwt-optional **Params:** path: `link_id` (int) **Purpose:** Fetch a single active article by id with its application (appid/appname) or null; 404 if missing or inactive. ```bash curl 'http://localhost:5001/api/knowledgebase/42' ``` ### POST /api/knowledgebase/<int:link_id>/click **Auth:** jwt-optional **Params:** path: `link_id` (int); no body **Purpose:** Increment the article's click counter and return the target `linkurl` plus new click count (used for redirect tracking); 404 if missing or inactive. ```bash curl -X POST 'http://localhost:5001/api/knowledgebase/42/click' ``` ### POST /api/knowledgebase **Auth:** permission `kb.create` (jwt required) **Params:** body JSON: `shortdescription` (required), `linkurl` (required), `appid` (optional int, must exist), `keywords` (optional); import-mode may pass timestamp fields **Purpose:** Create a new KB article; validates required fields and that `appid` (if given) exists; honors `X-Import-Mode` timestamp preservation via `apply_import_timestamps`; returns 201. ```bash curl -X POST 'http://localhost:5001/api/knowledgebase' -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{"shortdescription":"VPN setup guide","linkurl":"https://wiki.example.com/vpn","appid":3,"keywords":"vpn,remote"}' ``` ### PUT /api/knowledgebase/<int:link_id> **Auth:** permission `kb.edit` (jwt required) **Params:** path: `link_id` (int); body JSON: any of `shortdescription`, `linkurl`, `appid`, `keywords`, `isactive` **Purpose:** Update an article's shortdescription, linkurl, appid, keywords, and/or isactive; validates `appid` if changed; honors import timestamps; 404 if article missing. ```bash curl -X PUT 'http://localhost:5001/api/knowledgebase/42' -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{"keywords":"vpn,zscaler","isactive":true}' ``` ### DELETE /api/knowledgebase/<int:link_id> **Auth:** permission `kb.delete` (jwt required) **Params:** path: `link_id` (int) **Purpose:** Soft-delete an article by setting `isactive=false` (row is retained); 404 if article missing. ```bash curl -X DELETE 'http://localhost:5001/api/knowledgebase/42' -H 'Authorization: Bearer $TOKEN' ``` ## plugin-machines ### GET /api/machines/types **Auth:** jwt-optional **Params:** query: `page`, `per_page`, `active` (pass `false` to include inactive), `search` (ilike on `machinetype`) **Purpose:** List machine types (active by default) with pagination and name search. ```bash curl 'http://localhost:5001/api/machines/types?search=cnc&page=1&per_page=25' ``` ### GET /api/machines/types/<type_id> **Auth:** jwt-optional **Params:** path: `type_id` (int) **Purpose:** Get a single machine type by ID. ```bash curl 'http://localhost:5001/api/machines/types/3' ``` ### POST /api/machines/types **Auth:** jwt + permission:machines.create **Params:** body: `machinetype` (required), `description`, `icon`, `color` **Purpose:** Create a machine type; reactivates a soft-deleted type of the same name instead of returning 409. ```bash curl -X POST 'http://localhost:5001/api/machines/types' -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{"machinetype":"Lathe","description":"Turning machines","icon":"mdi-rotate-3d","color":"#1976d2"}' ``` ### PUT /api/machines/types/<type_id> **Auth:** jwt + permission:machines.edit **Params:** path: `type_id`; body: any of `machinetype`, `description`, `icon`, `color`, `isactive` **Purpose:** Update a machine type; rename guarded by 409 on duplicate name. ```bash curl -X PUT 'http://localhost:5001/api/machines/types/3' -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{"description":"5-axis mills","isactive":true}' ``` ### DELETE /api/machines/types/<type_id> **Auth:** jwt + permission:machines.delete **Params:** path: `type_id` (int) **Purpose:** Hard-delete a machine type; refused with 409 if any machine still references it. ```bash curl -X DELETE 'http://localhost:5001/api/machines/types/3' -H 'Authorization: Bearer $TOKEN' ``` ### GET /api/machines **Auth:** jwt-optional **Params:** query: `page`, `per_page`, `active` (`false` includes inactive), `assetnumber` (exact-match for idempotent import), `search` (assetnumber/name/serialnumber ilike), `typeid`|`type_id`, `vendorid`|`vendor_id`, `locationid`|`location_id`, `businessunitid`|`businessunit_id`, `sort` (`assetnumber`|`name`), `dir` (`asc`|`desc`) **Purpose:** List machines (Asset+Machine join) with filters, sorting, pagination; collapses Dualpath dual-bay pairs to one row (annotated with `dualpathpartner`) when the site setting is enabled. ```bash curl 'http://localhost:5001/api/machines?search=2007&typeid=2&sort=name&dir=desc&page=1&per_page=50' ``` ### GET /api/machines/<machine_id> **Auth:** jwt-optional **Params:** path: `machine_id` (int) **Purpose:** Get one machine with full asset details, nested machine dict, and `dualpathpartner` info. ```bash curl 'http://localhost:5001/api/machines/42' ``` ### GET /api/machines/by-asset/<asset_id> **Auth:** jwt-optional **Params:** path: `asset_id` (int) **Purpose:** Get machine data looked up by core asset ID instead of machine ID. ```bash curl 'http://localhost:5001/api/machines/by-asset/1001' ``` ### POST /api/machines **Auth:** jwt + permission:machines.create **Params:** body: `assetnumber` (required, 409 on duplicate); asset fields: `name`, `gaugelabreference`, `maintenancereference`, `serialnumber`, `statusid` (default 1), `locationid`, `businessunitid`, `mapx`, `mapy`, `notes`; machine fields: `machinetypeid`, `vendorid`, `modelnumberid`, `requiresmanualconfig`, `islocationonly`, `lastmaintenancedate`, `nextmaintenancedate`, `maintenanceintervaldays`, `controllervendorid`, `controllermodelid` **Purpose:** Create a machine (creates both core Asset row and Machine extension row); audit-logged; honors `X-Import-Mode` legacy timestamps via `apply_import_timestamps`. ```bash curl -X POST 'http://localhost:5001/api/machines' -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{"assetnumber":"2007","name":"Makino a51","machinetypeid":2,"vendorid":5,"locationid":3,"statusid":1}' ``` ### PUT /api/machines/<machine_id> **Auth:** jwt + permission:machines.edit **Params:** path: `machine_id`; body: any of `assetnumber`, `name`, `gaugelabreference`, `maintenancereference`, `serialnumber`, `statusid`, `locationid`, `businessunitid`, `mapx`, `mapy`, `notes`, `isactive`, `machinetypeid`, `vendorid`, `modelnumberid`, `requiresmanualconfig`, `islocationonly`, `lastmaintenancedate`, `nextmaintenancedate`, `maintenanceintervaldays`, `controllervendorid`, `controllermodelid` **Purpose:** Update machine (asset + machine fields) with per-field change tracking to AuditLog; 409 on assetnumber conflict. ```bash curl -X PUT 'http://localhost:5001/api/machines/42' -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{"locationid":7,"nextmaintenancedate":"2026-09-01"}' ``` ### DELETE /api/machines/<machine_id> **Auth:** jwt + permission:machines.delete **Params:** path: `machine_id` (int) **Purpose:** Soft-delete a machine (sets `asset.isactive=false`, keeps Machine row linked); audit-logged. ```bash curl -X DELETE 'http://localhost:5001/api/machines/42' -H 'Authorization: Bearer $TOKEN' ``` ### GET /api/machines/dashboard/summary **Auth:** jwt-optional **Params:** none **Purpose:** Dashboard summary: total active machine count plus counts grouped by machine type and by asset status (total and by-type exclude Dualpath secondary bays when the collapse setting is enabled; by-status does not). ```bash curl 'http://localhost:5001/api/machines/dashboard/summary' ``` ## plugin-measuringtools ### GET /api/measuringtools/types **Auth:** jwt-optional **Params:** query: `active` (default true; `'false'` includes inactive), `search` (name ilike), `page`, `perpage` **Purpose:** List measuring-tool types (active-only by default), paginated, name-sorted. ```bash curl 'http://localhost:5001/api/measuringtools/types?search=caliper&page=1&perpage=25' ``` ### GET /api/measuringtools/types/<type_id> **Auth:** jwt-optional **Params:** path: `type_id` (int) **Purpose:** Get one measuring-tool type by id (404 if missing). ```bash curl 'http://localhost:5001/api/measuringtools/types/3' ``` ### POST /api/measuringtools/types **Auth:** jwt + permission:measuringtools.create **Params:** body JSON: `name` (required), `description`, `color` **Purpose:** Create a measuring-tool type; reactivates a soft-deleted same-named one, 409 if an active one exists. ```bash curl -X POST 'http://localhost:5001/api/measuringtools/types' -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{"name":"Thread Gage","description":"Go/no-go thread gages","color":"#4caf50"}' ``` ### PUT /api/measuringtools/types/<type_id> **Auth:** jwt + permission:measuringtools.edit **Params:** path: `type_id`; body JSON: `name`, `description`, `color`, `isactive` (only keys present are applied) **Purpose:** Update a measuring-tool type; 409 on rename collision with an existing name. ```bash curl -X PUT 'http://localhost:5001/api/measuringtools/types/3' -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{"description":"Updated","isactive":true}' ``` ### DELETE /api/measuringtools/types/<type_id> **Auth:** jwt + permission:measuringtools.delete **Params:** path: `type_id` (int) **Purpose:** Hard-delete a measuring-tool type; refused with 409 if any tool still references it. ```bash curl -X DELETE 'http://localhost:5001/api/measuringtools/types/3' -H 'Authorization: Bearer $TOKEN' ``` ### GET /api/measuringtools **Auth:** jwt-optional **Params:** query: `active` (default true), `assetnumber` (exact match, for idempotent import), `search` (assetnumber/name/serialnumber ilike), `typeid`, `locationid`, `statusid`, `calibrationstatus` (overdue|duesoon|current|unknown), `page`, `perpage` **Purpose:** List measuring tools (Asset core merged with extension), filtered + paginated; derived calibrationstatus filter applied post-pagination. ```bash curl 'http://localhost:5001/api/measuringtools?typeid=2&calibrationstatus=overdue&page=1&perpage=50' ``` ### GET /api/measuringtools/<tool_id> **Auth:** jwt-optional **Params:** path: `tool_id` (int) **Purpose:** Get one measuring tool by measuringtoolid, asset core dict with extension nested under `measuringtool`. ```bash curl 'http://localhost:5001/api/measuringtools/17' ``` ### GET /api/measuringtools/by-asset/<asset_id> **Auth:** jwt-optional **Params:** path: `asset_id` (int) **Purpose:** Get a measuring tool by its core assetid (404 if the asset has no extension row). ```bash curl 'http://localhost:5001/api/measuringtools/by-asset/1042' ``` ### POST /api/measuringtools **Auth:** jwt + permission:measuringtools.create **Params:** body JSON: `assetnumber` (required, 409 on duplicate), `name`, `gaugelabreference`, `maintenancereference`, `serialnumber`, `statusid` (default 1), `locationid`, `businessunitid`, `mapx`, `mapy`, `notes`, `measuringtooltypeid`, `calibrationintervaldays`, `lastcalibrationdate` (YYYY-MM-DD), `nextcalibrationdate` (YYYY-MM-DD), `calibrationprovider`; import timestamps honored via `X-Import-Mode` **Purpose:** Create a measuring tool: one Asset core row (assettype `measuring_tool`) plus one measuringtools extension row in a single payload; audit-logged. ```bash curl -X POST 'http://localhost:5001/api/measuringtools' -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{"assetnumber":"MT-0042","name":"6in Digital Caliper","measuringtooltypeid":2,"calibrationintervaldays":365,"lastcalibrationdate":"2026-01-15","nextcalibrationdate":"2027-01-15","calibrationprovider":"Gage Lab"}' ``` ### PUT /api/measuringtools/<tool_id> **Auth:** jwt + permission:measuringtools.edit **Params:** path: `tool_id`; body JSON (only present keys applied): asset fields `assetnumber`, `name`, `gaugelabreference`, `maintenancereference`, `serialnumber`, `statusid`, `locationid`, `businessunitid`, `mapx`, `mapy`, `notes`, `isactive`; extension fields `measuringtooltypeid`, `calibrationintervaldays`, `calibrationprovider`, `notes`, `lastcalibrationdate`, `nextcalibrationdate` (YYYY-MM-DD) **Purpose:** Update asset core fields and extension fields in one payload; 409 on assetnumber collision; changes audit-logged. ```bash curl -X PUT 'http://localhost:5001/api/measuringtools/17' -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{"locationid":5,"nextcalibrationdate":"2026-12-01"}' ``` ### DELETE /api/measuringtools/<tool_id> **Auth:** jwt + permission:measuringtools.delete **Params:** path: `tool_id` (int) **Purpose:** Soft-delete a measuring tool by setting its asset isactive=false; audit-logged. ```bash curl -X DELETE 'http://localhost:5001/api/measuringtools/17' -H 'Authorization: Bearer $TOKEN' ``` ### GET /api/measuringtools/map-overlay **Auth:** jwt-optional **Params:** none **Purpose:** ADR-010 map overlay: per-asset calibration badges `[{assetid, color, label}]` for active tools that are overdue or due soon only; no coordinates returned. ```bash curl 'http://localhost:5001/api/measuringtools/map-overlay' ``` ### GET /api/measuringtools/report/calibration **Auth:** jwt-optional **Params:** none **Purpose:** Calibration report for the Reports hub: counts and full tool lists bucketed by derived status (overdue/duesoon/current/unknown) plus statuscolors map. ```bash curl 'http://localhost:5001/api/measuringtools/report/calibration' ``` ## plugin-network ### GET /api/network/types **Auth:** jwt-optional **Params:** `page`, `per_page`, `active` (default true; `false` includes inactive), `search` (ilike on networkdevicetype) **Purpose:** List network device types, paginated. ```bash curl 'http://localhost:5001/api/network/types?search=switch&per_page=50' ``` ### GET /api/network/types/<int:type_id> **Auth:** jwt-optional **Params:** path: `type_id` **Purpose:** Get one network device type by ID. ```bash curl http://localhost:5001/api/network/types/3 ``` ### POST /api/network/types **Auth:** jwt + permission `network.create` **Params:** body: `networkdevicetype` (required), `description`, `icon`, `color` **Purpose:** Create a network device type. Reactivates a soft-deleted duplicate instead of returning 409. ```bash curl -X POST http://localhost:5001/api/network/types -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"networkdevicetype":"Switch","icon":"mdi-switch","color":"#2196f3"}' ``` ### PUT /api/network/types/<int:type_id> **Auth:** jwt + permission `network.edit` **Params:** body: `networkdevicetype`, `description`, `icon`, `color`, `isactive` **Purpose:** Update a network device type. 409 on name collision. ```bash curl -X PUT http://localhost:5001/api/network/types/3 -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"description":"Access switches"}' ``` ### DELETE /api/network/types/<int:type_id> **Auth:** jwt + permission `network.delete` **Params:** path: `type_id` **Purpose:** Hard-delete a device type. 409 if any device still references it. ```bash curl -X DELETE http://localhost:5001/api/network/types/3 -H "Authorization: Bearer $TOKEN" ``` ### GET /api/network **Auth:** jwt-optional **Params:** `page`, `per_page`, `active`, `assetnumber` (exact match for idempotent import), `search` (assetnumber/name/serialnumber/hostname), `typeid`|`type_id`, `vendorid`|`vendor_id`, `locationid`|`location_id`, `businessunitid`|`businessunit_id`, `poe` (true/false), `managed` (true/false), `sort` (hostname|assetnumber|name), `dir` (asc|desc) **Purpose:** List network devices (Asset joined with NetworkDevice extension plus primary IP), filtered, sorted, paginated. ```bash curl 'http://localhost:5001/api/network?typeid=2&poe=true&sort=hostname&dir=asc' ``` ### GET /api/network/<int:device_id> **Auth:** jwt-optional **Params:** path: `device_id` (networkdeviceid) **Purpose:** Get one network device (asset dict + `networkdevice` sub-object + primary `ipaddress`). ```bash curl http://localhost:5001/api/network/17 ``` ### GET /api/network/by-asset/<int:asset_id> **Auth:** jwt-optional **Params:** path: `asset_id` **Purpose:** Look up a network device by its core assetid. ```bash curl http://localhost:5001/api/network/by-asset/1042 ``` ### GET /api/network/by-hostname/<hostname> **Auth:** jwt-optional **Params:** path: `hostname` **Purpose:** Look up a network device by exact hostname. ```bash curl http://localhost:5001/api/network/by-hostname/wjf-sw-idf3-01 ``` ### POST /api/network **Auth:** jwt + permission `network.create` **Params:** body: `assetnumber` (required); `name`, `serialnumber`, `gaugelabreference`, `maintenancereference`, `statusid` (default 1), `locationid`, `businessunitid`, `mapx`, `mapy`, `notes`, `networkdevicetypeid`, `vendorid`, `hostname` (unique), `firmwareversion`, `portcount`, `ispoe`, `ismanaged`, `rackunit`, `ipaddress` **Purpose:** Create a network device. Creates core Asset + NetworkDevice extension, upserts primary-IP Communication, audit-logged, honors `X-Import-Mode` legacy timestamps. ```bash curl -X POST http://localhost:5001/api/network -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"assetnumber":"NET-0042","hostname":"wjf-sw-idf3-01","networkdevicetypeid":1,"portcount":48,"ispoe":true,"ipaddress":"10.1.3.10"}' ``` ### PUT /api/network/<int:device_id> **Auth:** jwt + permission `network.edit` **Params:** body: any of `assetnumber`, `name`, `serialnumber`, `gaugelabreference`, `maintenancereference`, `statusid`, `locationid`, `businessunitid`, `mapx`, `mapy`, `notes`, `isactive`, `networkdevicetypeid`, `vendorid`, `hostname`, `firmwareversion`, `portcount`, `ispoe`, `ismanaged`, `rackunit`, `ipaddress` **Purpose:** Update asset + network-device fields. 409 on assetnumber/hostname conflicts, change-diff audit log, upserts primary IP when `ipaddress` present. ```bash curl -X PUT http://localhost:5001/api/network/17 -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"firmwareversion":"16.12.4","ipaddress":"10.1.3.11"}' ``` ### DELETE /api/network/<int:device_id> **Auth:** jwt + permission `network.delete` **Params:** path: `device_id` **Purpose:** Soft-delete a network device (sets asset.isactive=false). Audit-logged. ```bash curl -X DELETE http://localhost:5001/api/network/17 -H "Authorization: Bearer $TOKEN" ``` ### GET /api/network/dashboard/summary **Auth:** jwt-optional **Params:** none **Purpose:** Dashboard counts: total active devices, by type, by vendor, PoE vs non-PoE. ```bash curl http://localhost:5001/api/network/dashboard/summary ``` ### GET /api/network/vlans **Auth:** jwt-optional **Params:** `page`, `per_page`, `active` (default true), `search` (name/description/vlannumber), `type` (exact vlantype) **Purpose:** List VLANs, paginated, ordered by vlannumber. ```bash curl 'http://localhost:5001/api/network/vlans?search=voice' ``` ### GET /api/network/vlans/<int:vlan_id> **Auth:** jwt-optional **Params:** path: `vlan_id` **Purpose:** Get one VLAN including its active subnets. ```bash curl http://localhost:5001/api/network/vlans/5 ``` ### POST /api/network/vlans **Auth:** jwt + permission `network.create` **Params:** body: `vlannumber` (required), `name` (required), `description`, `vlantype` **Purpose:** Create a VLAN. 409 on duplicate vlannumber. Audit-logged. ```bash curl -X POST http://localhost:5001/api/network/vlans -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"vlannumber":120,"name":"Shopfloor","vlantype":"production"}' ``` ### PUT /api/network/vlans/<int:vlan_id> **Auth:** jwt + permission `network.edit` **Params:** body: `vlannumber`, `name`, `description`, `vlantype`, `isactive` **Purpose:** Update a VLAN. 409 on vlannumber conflict. Change-diff audit log. ```bash curl -X PUT http://localhost:5001/api/network/vlans/5 -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"description":"CNC cell VLAN"}' ``` ### DELETE /api/network/vlans/<int:vlan_id> **Auth:** jwt + permission `network.delete` **Params:** path: `vlan_id` **Purpose:** Soft-delete a VLAN. 400 if it still has active subnets. Audit-logged. ```bash curl -X DELETE http://localhost:5001/api/network/vlans/5 -H "Authorization: Bearer $TOKEN" ``` ### GET /api/network/subnets **Auth:** jwt-optional **Params:** `page`, `per_page`, `active` (default true), `search` (cidr/name/description), `vlanid`, `locationid`, `type` (exact subnettype) **Purpose:** List subnets, paginated, ordered by cidr. ```bash curl 'http://localhost:5001/api/network/subnets?vlanid=5' ``` ### GET /api/network/subnets/<int:subnet_id> **Auth:** jwt-optional **Params:** path: `subnet_id` **Purpose:** Get one subnet plus `devices`: every asset of any type (PC/printer/network) whose primary-IP Communication falls inside the CIDR, with cross-plugin detail URLs. ```bash curl http://localhost:5001/api/network/subnets/2 ``` ### POST /api/network/subnets **Auth:** jwt + permission `network.create` **Params:** body: `cidr` (required, must contain `/`), `name` (required), `description`, `gatewayip`, `subnetmask`, `networkaddress`, `broadcastaddress`, `vlanid`, `subnettype`, `locationid`, `dhcpenabled` (default true), `dhcprangestart`, `dhcprangeend`, `dns1`, `dns2` **Purpose:** Create a subnet. Validates CIDR notation and vlanid existence. 409 on duplicate cidr. Audit-logged. ```bash curl -X POST http://localhost:5001/api/network/subnets -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"cidr":"10.1.3.0/24","name":"IDF3 access","vlanid":5,"gatewayip":"10.1.3.1"}' ``` ### PUT /api/network/subnets/<int:subnet_id> **Auth:** jwt + permission `network.edit` **Params:** body: any of `cidr`, `name`, `description`, `gatewayip`, `subnetmask`, `networkaddress`, `broadcastaddress`, `vlanid`, `subnettype`, `locationid`, `dhcpenabled`, `dhcprangestart`, `dhcprangeend`, `dns1`, `dns2`, `isactive` **Purpose:** Update a subnet. 409 on cidr conflict. Change-diff audit log. ```bash curl -X PUT http://localhost:5001/api/network/subnets/2 -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"dhcpenabled":false}' ``` ### DELETE /api/network/subnets/<int:subnet_id> **Auth:** jwt + permission `network.delete` **Params:** path: `subnet_id` **Purpose:** Soft-delete a subnet (isactive=false). Audit-logged. ```bash curl -X DELETE http://localhost:5001/api/network/subnets/2 -H "Authorization: Bearer $TOKEN" ``` ## plugin-notifications ### GET /api/notifications/types **Auth:** none **Params:** `page`, `per_page`; `active=false` to include inactive types **Purpose:** List notification types, paginated, active-only by default. ```bash curl 'http://localhost:5001/api/notifications/types?active=false&page=1&per_page=50' ``` ### POST /api/notifications/types **Auth:** JWT + permission `notifications.create` **Params:** body: `typename` (required, unique), `typedescription`/`description`, `typecolor`/`color`, `expirymode` (`none`|`duration`|`dailytime`), `expirydays`, `expiryhour`, `expiryminute`, `splitperemployee`, `showemployeephoto`, `displaystyle` (`standard`|`carousel`|`grid`|`banner`) **Purpose:** Create a notification type including expiry rule and shopfloor display config. ```bash curl -X POST http://localhost:5001/api/notifications/types -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{"typename":"Recognition","typecolor":"recognition","expirymode":"dailytime","expiryhour":8,"splitperemployee":true,"showemployeephoto":true}' ``` ### PUT, PATCH /api/notifications/types/<int:type_id> **Auth:** JWT + permission `notifications.create` **Params:** body: any of `typename` (unique-checked), `typedescription`/`description`, `typecolor`/`color`, `isactive`, `expirymode`, `expirydays`, `expiryhour`, `expiryminute`, `splitperemployee`, `showemployeephoto`, `displaystyle` **Purpose:** Update a notification type (name/desc/color/isactive plus expiry and display fields). ```bash curl -X PATCH http://localhost:5001/api/notifications/types/3 -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{"expirymode":"duration","expirydays":14}' ``` ### GET /api/notifications **Auth:** none **Params:** `page`, `per_page`; `active=false` to include inactive; `typeid`/`type_id`; `ticketnumber` (exact match, for idempotent import); `current=true` (within start/end window now); `search` (ILIKE on text) **Purpose:** List notifications with filters, newest-first, paginated. ```bash curl 'http://localhost:5001/api/notifications?current=true&typeid=2&search=outage&page=1' ``` ### GET /api/notifications/<int:notification_id> **Auth:** none **Params:** path: `notification_id` **Purpose:** Get a single notification by ID. ```bash curl http://localhost:5001/api/notifications/42 ``` ### POST /api/notifications **Auth:** JWT + permission `notifications.create` **Params:** body: `notification`/`message` (required), `notificationtypeid`, `businessunitid`, `appid`, `starttime`/`startdate` (ISO, default now), `endtime`/`enddate` (ISO), `ticketnumber`, `link`/`linkurl`, `isshopfloor` (default false), `employeesso` (comma-list allowed), `employeename` **Purpose:** Create a notification; `endtime` auto-derived from type expiry rule when omitted. ```bash curl -X POST http://localhost:5001/api/notifications -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{"notification":"Line 2 press down","notificationtypeid":1,"isshopfloor":true,"ticketnumber":"INC0012345"}' ``` ### PUT /api/notifications/<int:notification_id> **Auth:** JWT + permission `notifications.edit` **Params:** body: `notification`/`message`, `notificationtypeid`, `businessunitid`, `appid`, `ticketnumber`, `link`/`linkurl`, `isactive`, `isshopfloor`, `employeesso`, `employeename`, `starttime`/`startdate`, `endtime`/`enddate` **Purpose:** Update any notification field; empty `starttime` resets to now, empty `endtime` clears it. ```bash curl -X PUT http://localhost:5001/api/notifications/42 -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{"endtime":"2026-08-01T12:00:00Z","isactive":true}' ``` ### DELETE /api/notifications/<int:notification_id> **Auth:** JWT + permission `notifications.delete` **Params:** path: `notification_id` **Purpose:** Soft-delete a notification (sets `isactive=false`, row kept). ```bash curl -X DELETE http://localhost:5001/api/notifications/42 -H 'Authorization: Bearer $TOKEN' ``` ### GET /api/notifications/active **Auth:** none **Params:** none **Purpose:** Currently active notifications for display, including ones starting within a 10-day lookahead. ```bash curl http://localhost:5001/api/notifications/active ``` ### GET /api/notifications/calendar **Auth:** none **Params:** `start` (ISO), `end` (ISO); invalid dates silently ignored **Purpose:** Active notifications as FullCalendar event objects for a date range. ```bash curl 'http://localhost:5001/api/notifications/calendar?start=2026-07-01T00:00:00Z&end=2026-07-31T23:59:59Z' ``` ### GET /api/notifications/dashboard/summary **Auth:** none **Params:** none **Purpose:** Dashboard counts: total currently-active notifications plus active counts grouped by type/color. ```bash curl http://localhost:5001/api/notifications/dashboard/summary ``` ### GET /api/notifications/employee/<sso> **Auth:** none **Params:** path: `sso` (digits only, 400 otherwise) **Purpose:** All active recognition-type notifications mentioning an employee SSO (exact or within comma-separated `employeesso` list). ```bash curl http://localhost:5001/api/notifications/employee/212345678 ``` ### GET /api/notifications/shopfloor **Auth:** none **Params:** `businessunit` (numeric BU id: returns that BU's plus null-BU notifications; omitted: null-BU only) **Purpose:** Shopfloor TV feed: current cards (active now, or ended less than 30 min ago flagged resolved) plus upcoming (starts within 5 days); splits multi-employee cards per type config, resolves employee names/photos via employees plugin, returns `configversion` hash so kiosks reload on layout changes. ```bash curl 'http://localhost:5001/api/notifications/shopfloor?businessunit=3' ``` ## plugin-printedparts ### GET /api/printedparts/items **Auth:** jwt + permission:printedparts.view **Params:** query: page, per_page, search (matches itemcode/gagelabtag/itemname/itemdescription/binlocation), active (default true; 'false' includes retired), lowstock=true **Purpose:** List printed items, paginated, with search and low-stock filter. ```bash curl -H "Authorization: Bearer $TOK" 'http://localhost:5001/api/printedparts/items?search=bracket&lowstock=true&page=1&per_page=25' ``` ### GET /api/printedparts/items/ **Auth:** jwt + permission:printedparts.view **Params:** path: item_id **Purpose:** Get one printed item plus its 25 most recent ledger transactions. ```bash curl -H "Authorization: Bearer $TOK" http://localhost:5001/api/printedparts/items/42 ``` ### POST /api/printedparts/items **Auth:** jwt + permission:printedparts.create **Params:** json body: itemname (required), gagelabtag, itemdescription, lowstockthreshold (default from printedparts_default_threshold setting, fallback 5), binlocation, printnotes; quantityonhand starts at 0 **Purpose:** Create a printed item; itemcode auto-minted from printedparts_code_prefix setting + row id; optional gagelabtag unique-checked (409 on clash). ```bash curl -X POST -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"itemname":"Fixture clip","gagelabtag":"WJRP0042","binlocation":"B3","lowstockthreshold":10}' http://localhost:5001/api/printedparts/items ``` ### PUT /api/printedparts/items/ **Auth:** jwt + permission:printedparts.edit **Params:** path: item_id; json body: any of the editable fields; gagelabtag uppercased, empty string clears it **Purpose:** Update catalog fields (itemname, itemdescription, lowstockthreshold, binlocation, printnotes, gagelabtag); rejects quantityonhand (ledger-managed) and duplicate gagelabtag (409). ```bash curl -X PUT -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"binlocation":"C1","lowstockthreshold":8}' http://localhost:5001/api/printedparts/items/42 ``` ### DELETE /api/printedparts/items/ **Auth:** jwt + permission:printedparts.delete **Params:** path: item_id **Purpose:** Soft-retire an item (isactive=false); ledger history preserved. ```bash curl -X DELETE -H "Authorization: Bearer $TOK" http://localhost:5001/api/printedparts/items/42 ``` ### POST /api/printedparts/items//restore **Auth:** jwt + permission:printedparts.delete **Params:** path: item_id **Purpose:** Un-retire a soft-deleted item (isactive=true); code, photo, history intact. ```bash curl -X POST -H "Authorization: Bearer $TOK" http://localhost:5001/api/printedparts/items/42/restore ``` ### POST /api/printedparts/items//image **Auth:** jwt + permission:printedparts.edit **Params:** path: item_id; multipart/form-data: file= **Purpose:** Upload or replace the item's photo (png/jpg/jpeg/gif/webp); old image files for the item are deleted first, imageurl updated. ```bash curl -X POST -H "Authorization: Bearer $TOK" -F 'file=@clip.jpg' http://localhost:5001/api/printedparts/items/42/image ``` ### GET /api/printedparts/image/ **Auth:** none **Params:** path: filename (e.g. printeditem-42.jpg) **Purpose:** Serve an uploaded item image from instance/printedpartsimages (public: fetched by `` tags on kiosk and lists). ```bash curl http://localhost:5001/api/printedparts/image/printeditem-42.jpg -o clip.jpg ``` ### DELETE /api/printedparts/items//image **Auth:** jwt + permission:printedparts.delete **Params:** path: item_id **Purpose:** Clear the item's imageurl; deletes the file on disk only if the URL is plugin-owned (starts with /api/printedparts/image/). ```bash curl -X DELETE -H "Authorization: Bearer $TOK" http://localhost:5001/api/printedparts/items/42/image ``` ### POST /api/printedparts/items//restock **Auth:** jwt + permission:printedparts.restock **Params:** path: item_id; json body: quantity (positive int, required), badge (required, 422 BadgeError if unresolvable) **Purpose:** Add freshly printed stock via a 'restock' ledger write (single-commit ledger + cached quantity); badge resolved server-side to sso/name; 404 if item inactive. ```bash curl -X POST -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"quantity":20,"badge":"123456789"}' http://localhost:5001/api/printedparts/items/42/restock ``` ### POST /api/printedparts/items//adjust **Auth:** jwt + permission:printedparts.restock **Params:** path: item_id; json body: quantitychange (non-zero int, required), reason (required), badge (required) **Purpose:** Correct the count (damage, recount) via an 'adjust' ledger write; reason mandatory; rejects driving stock below zero; fires low-stock alert on downward threshold crossing. ```bash curl -X POST -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"quantitychange":-3,"reason":"damaged in bin","badge":"123456789"}' http://localhost:5001/api/printedparts/items/42/adjust ``` ### GET /api/printedparts/kiosk/item/ **Auth:** none (deliberately open per decision record; kiosk cannot carry JWT) **Params:** path: itemcode (e.g. WJRP0042, 3DP0042, 42, or WJRP0042|3) **Purpose:** Item summary for a scanned bin barcode; resolves full code (itemcode or gagelabtag), 'TAG|rev' QR payloads (rev stripped), or bare keypad digits when the numeric tail uniquely matches one active item. ```bash curl http://localhost:5001/api/printedparts/kiosk/item/WJRP0042 ``` ### POST /api/printedparts/kiosk/take **Auth:** none (deliberately open per decision record) **Params:** json body: itemcode (required, same resolution as kiosk/item), badge (required, resolved server-side, 422 on BadgeError), quantity (positive int <= on hand), revision (optional int) **Purpose:** Kiosk checkout: decrement-only 'take' ledger write, badge-attributed, bounded by quantityonhand; records optional print-file revision (explicit field or 'TAG|rev' tail); triggers low-stock alert on threshold crossing; the product's only open write. ```bash curl -X POST -H 'Content-Type: application/json' -d '{"itemcode":"WJRP0042|3","badge":"123456789","quantity":2}' http://localhost:5001/api/printedparts/kiosk/take ``` ### GET /api/printedparts/reports/stock **Auth:** jwt-optional (like all product reports) **Params:** query: format=csv for CSV download (printedparts-stock.csv), else JSON {columns, rows} **Purpose:** Stock-level report for active items with low-stock flags and ledgerdelta (cache-vs-ledger reconcile; nonzero means a write bypassed the single-commit rule). ```bash curl 'http://localhost:5001/api/printedparts/reports/stock?format=csv' -o stock.csv ``` ### GET /api/printedparts/reports/consumption **Auth:** jwt-optional **Params:** query: days (default 30; 0 or negative = all time), format=csv (printedparts-consumption.csv) **Purpose:** Take-transactions aggregated per item (takes count + quantitytaken), sorted by quantity taken descending. ```bash curl 'http://localhost:5001/api/printedparts/reports/consumption?days=90' ``` ### GET /api/printedparts/reports/by-person **Auth:** jwt-optional **Params:** query: days (default 30; 0 or negative = all time), format=csv (printedparts-by-person.csv) **Purpose:** Take-transactions grouped by employee SSO (takes count + quantitytaken), sorted by quantity taken descending. ```bash curl 'http://localhost:5001/api/printedparts/reports/by-person?days=30&format=csv' -o by-person.csv ``` ### GET /api/printedparts/items//files **Auth:** jwt + permission:printedparts.view **Params:** path: item_id **Purpose:** List the item's print-file revision history, newest revision first. ```bash curl -H "Authorization: Bearer $TOK" http://localhost:5001/api/printedparts/items/42/files ``` ### POST /api/printedparts/items//files **Auth:** jwt + permission:printedparts.edit **Params:** path: item_id; multipart/form-data: file= (required), note= (optional) **Purpose:** Upload the next print-file revision (append-only, auto-numbered max+1); allowed ext: stl/3mf/gcode/gco/bgcode/step/stp/obj/amf; 100 MB cap; uploader recorded from JWT identity. ```bash curl -X POST -H "Authorization: Bearer $TOK" -F 'file=@clip-v2.stl' -F 'note=thicker wall' http://localhost:5001/api/printedparts/items/42/files ``` ### GET /api/printedparts/files//download **Auth:** jwt-optional **Params:** path: file_id **Purpose:** Download a print-file revision as an attachment under its original filename (jwt-optional so plain anchor downloads work). ```bash curl -OJ http://localhost:5001/api/printedparts/files/7/download ``` ### DELETE /api/printedparts/files/ **Auth:** jwt + permission:printedparts.delete **Params:** path: file_id **Purpose:** Delete a bad print-file revision (wrong file uploaded): removes the stored file and the DB record. ```bash curl -X DELETE -H "Authorization: Bearer $TOK" http://localhost:5001/api/printedparts/files/7 ``` ## plugin-printers ### GET /api/printers/types **Auth:** jwt-optional **Params:** page, per_page, active (default true; 'false' includes inactive), search **Purpose:** List printer types, paginated, with active filter and name search. ```bash curl -H "Authorization: Bearer $TOKEN" 'http://localhost:5001/api/printers/types?search=laser&active=false' ``` ### GET /api/printers/types/<type_id> **Auth:** jwt-optional **Params:** path: type_id (int) **Purpose:** Get a single printer type by ID. ```bash curl http://localhost:5001/api/printers/types/3 ``` ### POST /api/printers/types **Auth:** permission:printers.create **Params:** body: printertype (required), description, icon, color; reactivates a matching inactive type instead of 409 **Purpose:** Create a new printer type (or reactivate an inactive duplicate). ```bash curl -X POST -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"printertype":"Label","color":"#00f"}' http://localhost:5001/api/printers/types ``` ### PUT /api/printers/types/<type_id> **Auth:** permission:printers.edit **Params:** body: printertype, description, icon, color, isactive (any subset); 409 on name clash **Purpose:** Update a printer type. ```bash curl -X PUT -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"isactive":false}' http://localhost:5001/api/printers/types/3 ``` ### DELETE /api/printers/types/<type_id> **Auth:** permission:printers.delete **Params:** path: type_id; 409 if any printer still references the type **Purpose:** Hard-delete a printer type when unused. ```bash curl -X DELETE -H "Authorization: Bearer $TOKEN" http://localhost:5001/api/printers/types/3 ``` ### GET /api/printers/drivers **Auth:** jwt-optional **Params:** active (default true; 'false' includes inactive). Unpaginated. **Purpose:** List printer driver packages (named SMB/HTTP links). ```bash curl 'http://localhost:5001/api/printers/drivers?active=false' ``` ### POST /api/printers/drivers **Auth:** permission:printers.create **Params:** body: name (required), location (required), description, modelnumberid, isactive **Purpose:** Create a driver entry. ```bash curl -X POST -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"name":"HP UPD","location":"\\\\fileserver\\drivers\\hpupd"}' http://localhost:5001/api/printers/drivers ``` ### PUT /api/printers/drivers/<driver_id> **Auth:** permission:printers.edit **Params:** body: name, location, description, isactive, modelnumberid (any subset) **Purpose:** Update a driver entry. ```bash curl -X PUT -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"isactive":false}' http://localhost:5001/api/printers/drivers/5 ``` ### DELETE /api/printers/drivers/<driver_id> **Auth:** permission:printers.delete **Params:** path: driver_id **Purpose:** Hard-delete a driver entry. ```bash curl -X DELETE -H "Authorization: Bearer $TOKEN" http://localhost:5001/api/printers/drivers/5 ``` ### GET /api/printers **Auth:** jwt-optional **Params:** page, per_page, active, assetnumber (exact-match for import idempotency), search (assetnumber/name/serial/hostname/windowsname), typeid|type_id, vendorid|vendor_id, locationid|location_id, businessunitid|businessunit_id, sort (hostname|assetnumber|name), dir (asc|desc) **Purpose:** List printers (joined Asset+Printer) with filters, search, sorting, pagination; each row includes primary IP. ```bash curl 'http://localhost:5001/api/printers?search=csf&typeid=2&sort=assetnumber&dir=desc' ``` ### GET /api/printers/install-list **Auth:** jwt-optional **Params:** format=text for pipe-delimited installer variant (printerid|windowsname|vendorname|modelnumber|hostname|ipaddress|mapx|mapy); default JSON **Purpose:** Flat unpaginated list of active network printers (with map positions) for the signed printer-installer EXE; replaces classic apiprinters.asp. ```bash curl 'http://localhost:5001/api/printers/install-list?format=text' ``` ### GET /api/printers/install-batch **Auth:** jwt-optional **Params:** printerids (required, comma-separated printer IDs) **Purpose:** Generate and download a Windows .bat that installs selected printers (HP/Xerox via universal PrinterInstaller.exe, per-printer .exe /SILENT, or flags manual). ```bash curl -OJ 'http://localhost:5001/api/printers/install-batch?printerids=1,2,3' ``` ### GET /api/printers/pc-default **Auth:** jwt-optional **Params:** machine (PC asset number), format=text for pipe-delimited variant; returns {} / empty body if no default set **Purpose:** Look up a PC's default printer via the defaultprinter asset relationship (parity with classic apipcdefaultprinter.asp); used by installer to preselect map hotspot. ```bash curl 'http://localhost:5001/api/printers/pc-default?machine=0421&format=text' ``` ### GET /api/printers/<printer_id> **Auth:** jwt-optional **Params:** path: printer_id (int) **Purpose:** Get one printer with full asset details, communications, and active drivers matching its model. ```bash curl http://localhost:5001/api/printers/17 ``` ### GET /api/printers/by-asset/<asset_id> **Auth:** jwt-optional **Params:** path: asset_id (int) **Purpose:** Get printer data keyed by core asset ID. ```bash curl http://localhost:5001/api/printers/by-asset/204 ``` ### POST /api/printers **Auth:** permission:printers.create **Params:** body: assetnumber (required); name, serialnumber, gaugelabreference, maintenancereference, statusid, locationid, businessunitid, printertypeid, vendorid, modelnumberid, hostname, windowsname, sharename, iscsf, installpath, pin, iscolor, isduplex, isnetwork, mapx, mapy, notes, ipaddress (creates primary IP comm); X-Import-Mode header preserves legacy timestamps **Purpose:** Create a printer (Asset + Printer extension + optional primary IP communication). ```bash curl -X POST -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"assetnumber":"PR-0042","hostname":"wjprn042","ipaddress":"10.1.2.42","printertypeid":1}' http://localhost:5001/api/printers ``` ### PUT /api/printers/<printer_id> **Auth:** permission:printers.edit **Params:** body: any subset of asset fields (assetnumber, name, serialnumber, gaugelabreference, maintenancereference, statusid, locationid, businessunitid, mapx, mapy, notes, isactive) + printer fields (printertypeid, vendorid, modelnumberid, hostname, windowsname, sharename, iscsf, installpath, pin, iscolor, isduplex, isnetwork) + ipaddress (upserts/clears primary IP comm); 409 on assetnumber clash **Purpose:** Update printer: asset, extension, and primary IP in one call. ```bash curl -X PUT -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"ipaddress":"10.1.2.99","iscolor":true}' http://localhost:5001/api/printers/17 ``` ### DELETE /api/printers/<printer_id> **Auth:** permission:printers.delete **Params:** path: printer_id **Purpose:** Soft-delete a printer (sets the underlying asset isactive=false). ```bash curl -X DELETE -H "Authorization: Bearer $TOKEN" http://localhost:5001/api/printers/17 ``` ### GET /api/printers/<printer_id>/supplies **Auth:** jwt-optional **Params:** path: printer_id; needs an IP communication on the printer **Purpose:** Real-time supply levels from Zabbix for one printer, annotated with status/color/part numbers; fails soft (empty supplies, pingstatus -1) when Zabbix is off/unreachable. ```bash curl -H "Authorization: Bearer $TOKEN" http://localhost:5001/api/printers/17/supplies ``` ### GET /api/printers/lowsupplies **Auth:** jwt-optional **Params:** none (data cached 5 min) **Purpose:** Fleet-wide report of printers with low/critical supply levels from Zabbix, with summary counts. ```bash curl http://localhost:5001/api/printers/lowsupplies ``` ### GET /api/printers/lookup **Auth:** jwt-optional **Params:** ip or fqdn (one required; value matched against communication ipaddress) **Purpose:** Find a printer by IP/FQDN (parity with classic printerlookup.asp; used by Zabbix to deep-link to a printer record). ```bash curl 'http://localhost:5001/api/printers/lookup?ip=10.1.2.42' ``` ### POST /api/printers/supplies/refresh **Auth:** permission:printers.create **Params:** none **Purpose:** Clear the cached Zabbix supply data so next reads pull fresh values (toner-report Refresh button). ```bash curl -X POST -H "Authorization: Bearer $TOKEN" http://localhost:5001/api/printers/supplies/refresh ``` ### GET /api/printers/dashboard/summary **Auth:** jwt-optional **Params:** none **Purpose:** Dashboard summary: total active printers, counts by type and vendor, low/critical supply counts (Zabbix, if reachable). ```bash curl http://localhost:5001/api/printers/dashboard/summary ``` ### GET /api/printers/supplies/meta **Auth:** jwt-optional **Params:** none **Purpose:** Allowed enum values for supplytype, color, and capacitytier (for the UI forms). ```bash curl http://localhost:5001/api/printers/supplies/meta ``` ### GET /api/printers/models **Auth:** jwt-optional **Params:** page, per_page, search (modelnumber), vendorid|vendor_id, withsupplies=true (only models that already have supplies) **Purpose:** List printer models with supply counts for the supply-management picker (restricted to models attached to printers or already carrying supplies). ```bash curl 'http://localhost:5001/api/printers/models?withsupplies=true&search=M404' ``` ### GET /api/printers/models/<modelnumberid>/supplies **Auth:** jwt-optional **Params:** path: modelnumberid (int) **Purpose:** List all active supplies (toner/drum/waste part numbers) mapped to a model. ```bash curl http://localhost:5001/api/printers/models/12/supplies ``` ### POST /api/printers/models/<modelnumberid>/supplies **Auth:** permission:printers.create **Params:** body: partnumber (required), supplytype (default toner), color (default none), capacitytier (default standard), marketingname, pageyield, notes; 409 if part already mapped to model **Purpose:** Add a supply part-number mapping to a model. ```bash curl -X POST -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"partnumber":"CF259A","supplytype":"toner","color":"black"}' http://localhost:5001/api/printers/models/12/supplies ``` ### PUT /api/printers/supplies/<modelsupplyid> **Auth:** permission:printers.edit **Params:** body: any subset of supplytype, color, capacitytier, partnumber, marketingname, pageyield, notes; enum-validated, 409 on partnumber clash within model **Purpose:** Update a model supply mapping. ```bash curl -X PUT -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"capacitytier":"high"}' http://localhost:5001/api/printers/supplies/44 ``` ### DELETE /api/printers/supplies/<modelsupplyid> **Auth:** permission:printers.delete **Params:** path: modelsupplyid **Purpose:** Hard-delete a model supply mapping. ```bash curl -X DELETE -H "Authorization: Bearer $TOKEN" http://localhost:5001/api/printers/supplies/44 ``` ## plugin-slides ### GET /api/slides/feed **Auth:** none **Params:** query: `surface=lobby|shopfloor` (default `lobby`; invalid values fall back to `lobby`). Returns `{success, surface, basepath, interval, slides:[{filename, seconds}]}`; only slides whose file exists on disk are listed. **Purpose:** Public flat playlist for a surface (lobby/shopfloor). Raw `jsonify`, not the `success_response` envelope, so the screensaver parser works unchanged. ```bash curl 'http://localhost:5001/api/slides/feed?surface=shopfloor' ``` ### GET /api/slides/img/<surface>/<path:filename> **Auth:** none **Params:** path: `surface` (`lobby|shopfloor`), `filename` (must equal its basename). **Purpose:** Public serving of a single slide image from `instance/slides//`, with basename path-traversal guard. 404 on unknown surface, traversal attempt, or missing file. ```bash curl 'http://localhost:5001/api/slides/img/lobby/Slide1.png' -o Slide1.png ``` ### GET /api/slides/<surface> **Auth:** jwt + permission:slides.manage **Params:** path: `surface` (`lobby|shopfloor`; else `VALIDATION_ERROR`). **Purpose:** Admin list of a surface's slides (TvSlide rows ordered by `sortorder, slideid`, filtered to files present on disk), each with a `url` field for the img route. ```bash curl -H 'Authorization: Bearer $TOKEN' 'http://localhost:5001/api/slides/lobby' ``` ### POST /api/slides/<surface>/upload **Auth:** jwt + permission:slides.manage **Params:** path: `surface`. multipart/form-data body: `files` (repeatable) or single `file`. Allowed extensions: `.jpg .jpeg .png .gif .bmp .webp`. Returns `{added:[names]}`. **Purpose:** Upload one or more slide images. Non-image extensions skipped, names `secure_filename`'d and unique-renamed (`stem_N.ext`) on collision, appended after current max sortorder in natural filename order, `seconds=0` (surface default). ```bash curl -X POST -H 'Authorization: Bearer $TOKEN' -F 'files=@Slide1.png' -F 'files=@Slide2.png' 'http://localhost:5001/api/slides/lobby/upload' ``` ### POST /api/slides/<surface>/order **Auth:** jwt + permission:slides.manage **Params:** path: `surface`. JSON body: `{order: [filename, ...]}`. **Purpose:** Save play order: each filename in the `order` array gets `sortorder` set to its index; unknown filenames silently ignored. ```bash curl -X POST -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{"order":["Slide2.png","Slide1.png"]}' 'http://localhost:5001/api/slides/lobby/order' ``` ### POST /api/slides/<surface>/delete **Auth:** jwt + permission:slides.manage **Params:** path: `surface`. JSON body: `{files: [filename, ...]}`. **Purpose:** Delete named slides: removes file from disk (OSError swallowed) and the TvSlide row. Filenames reduced to basename first; returns count of DB rows removed. ```bash curl -X POST -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{"files":["Slide1.png"]}' 'http://localhost:5001/api/slides/shopfloor/delete' ``` ### PATCH /api/slides/<surface>/<int:slideid> **Auth:** jwt + permission:slides.manage **Params:** path: `surface`, `slideid` (int). JSON body: `{seconds: int}` (non-int -> `VALIDATION_ERROR`). **Purpose:** Update a single slide's per-slide duration. `seconds` clamped to `>=0` (`0` means use the 10s surface default); 404 if slide missing or belongs to a different surface. ```bash curl -X PATCH -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{"seconds":15}' 'http://localhost:5001/api/slides/lobby/12' ``` ## plugin-usb USB device inventory with check-in/check-out tracking. Dual-mode backend: self-hosted tables or external `cmmc_usb` DB, selected by the `usb_directory_mode` setting. ### GET /api/usb **Auth:** JWT optional **Params:** query: `page`, `per_page`, `status` (`available` | `checkedout` | `retired`), `search` (matches `device_id` or `device_desc`) **Purpose:** List USB devices with checkout status (paginated). ```bash curl 'http://localhost:5001/api/usb?status=available&search=kingston&page=1&per_page=25' -H 'Authorization: Bearer $JWT' ``` ### GET /api/usb/<device_id> **Auth:** JWT optional **Params:** path: `device_id` **Purpose:** Get one device plus its last 20 check-in/out log rows. 404 if unknown. ```bash curl 'http://localhost:5001/api/usb/USB-0042' -H 'Authorization: Bearer $JWT' ``` ### POST /api/usb **Auth:** JWT + permission `usb.create` **Params:** body JSON: `device_id` (required), `device_desc`, `device_owner` (badge), `locker_location` **Purpose:** Create a device; starts in checked-in status. 409 on duplicate `device_id`. ```bash curl -X POST 'http://localhost:5001/api/usb' -H 'Authorization: Bearer $JWT' -H 'Content-Type: application/json' -d '{"device_id":"USB-0042","device_desc":"Kingston 32GB","device_owner":"212345678","locker_location":"A3"}' ``` ### PUT /api/usb/<device_id> **Auth:** JWT + permission `usb.edit` **Params:** path: `device_id`; body JSON: any of `device_desc`, `device_owner`, `locker_location`, `status` **Purpose:** Edit device fields. 404 if unknown. ```bash curl -X PUT 'http://localhost:5001/api/usb/USB-0042' -H 'Authorization: Bearer $JWT' -H 'Content-Type: application/json' -d '{"device_desc":"Kingston 64GB","locker_location":"B1"}' ``` ### POST /api/usb/<device_id>/retire **Auth:** JWT + permission `usb.edit` **Params:** path: `device_id`; no body **Purpose:** Retire a device (sets status to `retired`). 404 if unknown. ```bash curl -X POST 'http://localhost:5001/api/usb/USB-0042/retire' -H 'Authorization: Bearer $JWT' ``` ### POST /api/usb/<device_id>/checkout **Auth:** JWT + permission `usb.create` **Params:** path: `device_id`; body JSON: `badge` (required), `locker_location` (optional, also updates the device) **Purpose:** Check a device out to a badge. Writes a check-out log row, sets status to checked-out, auto-creates the user from the HR directory. 409 if already checked out. ```bash curl -X POST 'http://localhost:5001/api/usb/USB-0042/checkout' -H 'Authorization: Bearer $JWT' -H 'Content-Type: application/json' -d '{"badge":"212345678","locker_location":"A3"}' ``` ### POST /api/usb/<device_id>/checkin **Auth:** JWT + permission `usb.create` **Params:** path: `device_id`; body JSON: `badge` (required), `locker_location`, `sanitized` (bool/1/0), `scanned_viruses` (bool/1/0) **Purpose:** Check a device back in. Writes a check-in log row with sanitized/virus-scan flags, sets status to checked-in. 400 if not currently checked out. ```bash curl -X POST 'http://localhost:5001/api/usb/USB-0042/checkin' -H 'Authorization: Bearer $JWT' -H 'Content-Type: application/json' -d '{"badge":"212345678","sanitized":true,"scanned_viruses":true}' ``` ### GET /api/usb/<device_id>/history **Auth:** JWT optional **Params:** path: `device_id`; query: `page`, `per_page` **Purpose:** Paginated check-in/out log for one device, newest first. ```bash curl 'http://localhost:5001/api/usb/USB-0042/history?page=1&per_page=50' -H 'Authorization: Bearer $JWT' ``` ### GET /api/usb/checkouts **Auth:** JWT optional **Params:** query: `page`, `per_page`, `active=true` (only rows whose device is still checked out), `badge` (filter by `badge_number`) **Purpose:** List check-out log rows (paginated), each with the device's current status joined in. ```bash curl 'http://localhost:5001/api/usb/checkouts?active=true&badge=212345678' -H 'Authorization: Bearer $JWT' ``` ### GET /api/usb/checkouts/active **Auth:** JWT optional **Params:** query: `badge` (filter by `badge_number`) **Purpose:** Latest check-out log row for every currently checked-out device (non-paginated list). ```bash curl 'http://localhost:5001/api/usb/checkouts/active?badge=212345678' -H 'Authorization: Bearer $JWT' ``` ## plugin-warranty ### GET /api/warranty **Auth:** jwt-optional **Params:** query: `active` (default true; 'false' includes inactive), `servicetag` (exact match, for idempotent import), `vendor` (exact match), `assetid` (int, filter to warranties covering that asset), `status` (post-filter on derived status: `expired|expiring|active|unknown`) **Purpose:** List warranties with linked-asset summaries and derived status (batch asset fetch, ordered by enddate with null last). ```bash curl -H "Authorization: Bearer $TOKEN" 'http://localhost:5001/api/warranty?status=expiring&assetid=42' ``` ### GET /api/warranty/asset/<int:assetid> **Auth:** jwt-optional **Params:** path: `assetid` **Purpose:** Active warranties covering one asset, for the asset-detail panel. ```bash curl -H "Authorization: Bearer $TOKEN" http://localhost:5001/api/warranty/asset/42 ``` ### GET /api/warranty/<int:warrantyid> **Auth:** jwt-optional **Params:** path: `warrantyid` **Purpose:** Fetch a single warranty by id with asset summaries; 404 if missing. ```bash curl -H "Authorization: Bearer $TOKEN" http://localhost:5001/api/warranty/7 ``` ### POST /api/warranty **Auth:** jwt + permission: `warranty.create` **Params:** JSON body: `vendor` (required), `servicetag`, `provider` (default 'manual', lowercased), `servicelevel`, `startdate` (YYYY-MM-DD), `enddate` (YYYY-MM-DD), `notes`, `assetids` (list of asset ids to link) **Purpose:** Create a warranty (vendor required) and optionally link it to assets; returns 201. ```bash curl -X POST -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"vendor":"Dell","servicetag":"ABC1234","enddate":"2027-06-30","assetids":[42]}' http://localhost:5001/api/warranty ``` ### PUT /api/warranty/<int:warrantyid> **Auth:** jwt + permission: `warranty.edit` **Params:** path: `warrantyid`; JSON body (all optional): `vendor`, `servicetag`, `provider`, `servicelevel`, `startdate`, `enddate`, `notes`, `isactive` (bool), `assetids` (full replacement list) **Purpose:** Partial update of any warranty field (only keys present in body change), including isactive and replacing asset links via assetids. ```bash curl -X PUT -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"enddate":"2028-01-15","assetids":[42,43]}' http://localhost:5001/api/warranty/7 ``` ### DELETE /api/warranty/<int:warrantyid> **Auth:** jwt + permission: `warranty.delete` **Params:** path: `warrantyid` **Purpose:** Hard-delete a warranty (and its asset links); 404 if missing. ```bash curl -X DELETE -H "Authorization: Bearer $TOKEN" http://localhost:5001/api/warranty/7 ``` ### POST /api/warranty/<int:warrantyid>/refresh **Auth:** jwt + permission: `warranty.edit` **Params:** path: `warrantyid`; no body **Purpose:** Re-query the warranty's provider (dell etc.) by service tag and update servicelevel/startdate/enddate + lastcheckeddate; 400 if provider is manual or not configured. ```bash curl -X POST -H "Authorization: Bearer $TOKEN" http://localhost:5001/api/warranty/7/refresh ``` ### POST /api/warranty/sync/dell **Auth:** jwt + permission: `warranty.edit` **Params:** query: `all` (default false; 'true' re-checks assets that already have a dated warranty); no body **Purpose:** Bulk Dell sync: look up serials of active assets lacking a dated warranty via Dell bulk_lookup, creating or updating (canonicalizing to provider dell) warranties; returns candidates/tags/matched/created/updated counts. ```bash curl -X POST -H "Authorization: Bearer $TOKEN" 'http://localhost:5001/api/warranty/sync/dell?all=true' ``` ### GET /api/warranty/report **Auth:** jwt-optional **Params:** none **Purpose:** Report for the Reports hub: active warranties bucketed by derived status (expired/expiring/active/unknown) with per-bucket counts and full lists. ```bash curl -H "Authorization: Bearer $TOKEN" http://localhost:5001/api/warranty/report ```