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