Files
shopdb-flask/docs/API-REFERENCE.md

140 KiB

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.

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).

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.

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.

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).

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.

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).

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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).

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).

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, ...).

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.

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 ********.

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.

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.

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.

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.).

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.

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_<theme> at its served URL.

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).

curl -O http://localhost:5001/api/settings/map-blueprint/blueprint-light.png

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).

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).

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).

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.

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).

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).

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).

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.

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).

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).

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.

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.

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).

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).

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.

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.

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.

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.

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.

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).

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).

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.

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.

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).

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.

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.

curl http://localhost:5001/api/assets/42/communications

GET /api/locations/types

Auth: JWT optional Params: active=false includes inactive Purpose: List location types.

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.

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).

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.

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.

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.

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.

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.

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.

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.

curl 'http://localhost:5001/api/vendors?search=fanuc'

GET /api/vendors/<vendor_id>

Auth: JWT optional Params: path: vendor_id Purpose: Get one vendor.

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.

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.

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.

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.

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.

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.

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.

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.

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=<image> (.png/.jpg/.jpeg/.gif/.webp/.svg) Purpose: Upload/replace model photo (saved as instance/modelimages/model-<id><ext>, one per model); sets imageurl.

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).

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).

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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).

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.

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.

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.

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).

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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).

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.

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).

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).

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.

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.

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).

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).

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.

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.

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.

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.

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.

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.

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.

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_<type>_enabled settings.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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_<NAME> 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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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).

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<serial>.<domain> FQDN for the Dashboard Defaults picker.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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).

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.

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.

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).

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.

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.

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.

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.

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.

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.

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=<csv> 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.

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=<image>, extensions .png/.jpg/.jpeg/.gif/.webp only Purpose: Upload or replace a self-hosted employee's photo (saved as photo-<sso><ext> in instance employeephotos dir; old file wiped even on extension change); 409 in external mode, 404 if employee missing.

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).

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.

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.

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).

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.

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.

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.

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).

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.

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}.

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.

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.

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.

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.

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}.

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.

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.

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).

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.

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).

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.

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).

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).

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.

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.

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).

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.

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.

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.

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.

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).

curl 'http://localhost:5001/api/knowledgebase/stats'

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.

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.

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.

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"}'

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.

curl -X PUT 'http://localhost:5001/api/knowledgebase/42' -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{"keywords":"vpn,zscaler","isactive":true}'

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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).

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.

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).

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.

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.

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.

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.

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.

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).

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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).

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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).

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.

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.

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.

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.

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).

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.

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.

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.

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).

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.

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.

curl -H "Authorization: Bearer $TOK" 'http://localhost:5001/api/printedparts/items?search=bracket&lowstock=true&page=1&per_page=25'

GET /api/printedparts/items/int:item_id

Auth: jwt + permission:printedparts.view Params: path: item_id Purpose: Get one printed item plus its 25 most recent ledger transactions.

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).

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/int:item_id

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).

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/int:item_id

Auth: jwt + permission:printedparts.delete Params: path: item_id Purpose: Soft-retire an item (isactive=false); ledger history preserved.

curl -X DELETE -H "Authorization: Bearer $TOK" http://localhost:5001/api/printedparts/items/42

POST /api/printedparts/items/int:item_id/restore

Auth: jwt + permission:printedparts.delete Params: path: item_id Purpose: Un-retire a soft-deleted item (isactive=true); code, photo, history intact.

curl -X POST -H "Authorization: Bearer $TOK" http://localhost:5001/api/printedparts/items/42/restore

POST /api/printedparts/items/int:item_id/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.

curl -X POST -H "Authorization: Bearer $TOK" -F 'file=@clip.jpg' http://localhost:5001/api/printedparts/items/42/image

GET /api/printedparts/image/path:filename

Auth: none Params: path: filename (e.g. printeditem-42.jpg) Purpose: Serve an uploaded item image from instance/printedpartsimages (public: fetched by <img> tags on kiosk and lists).

curl http://localhost:5001/api/printedparts/image/printeditem-42.jpg -o clip.jpg

DELETE /api/printedparts/items/int:item_id/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/).

curl -X DELETE -H "Authorization: Bearer $TOK" http://localhost:5001/api/printedparts/items/42/image

POST /api/printedparts/items/int:item_id/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.

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/int:item_id/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.

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.

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.

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).

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.

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.

curl 'http://localhost:5001/api/printedparts/reports/by-person?days=30&format=csv' -o by-person.csv

GET /api/printedparts/items/int:item_id/files

Auth: jwt + permission:printedparts.view Params: path: item_id Purpose: List the item's print-file revision history, newest revision first.

curl -H "Authorization: Bearer $TOK" http://localhost:5001/api/printedparts/items/42/files

POST /api/printedparts/items/int:item_id/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.

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/int:file_id/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).

curl -OJ http://localhost:5001/api/printedparts/files/7/download

DELETE /api/printedparts/files/int:file_id

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.

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.

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.

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).

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.

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.

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).

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.

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.

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.

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.

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.

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).

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.

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.

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.

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).

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.

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).

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.

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.

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).

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).

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).

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).

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).

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.

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.

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.

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.

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.

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/<surface>/, with basename path-traversal guard. 404 on unknown surface, traversal attempt, or missing file.

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.

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).

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.

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.

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.

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).

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.

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.

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.

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.

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.

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.

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.

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.

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).

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).

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.

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.

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.

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.

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.

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.

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.

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.

curl -H "Authorization: Bearer $TOKEN" http://localhost:5001/api/warranty/report