docs: fix the examples that fail if you paste them

Six procedures that could not be followed as written.

Eighty-nine curl examples single-quoted `Authorization: Bearer $TOKEN`, so the
shell never expanded it and the server answered 422 "Not enough segments". Nine
more did the same with X-API-Key. The other 129 examples in the same file
already used double quotes, so this was drift rather than a convention, and the
spec regenerated from it carried the fault onward.

The GE-Enforce report example put a `//` comment inside a JSON body. The server
parses with silent=True, so it saw `{}` and answered "hostname is required"
about a body that plainly has one - the worst kind of error message, one that
sends the reader to the wrong field entirely.

The IIS install ran `flask db upgrade` and a per-plugin install loop but never
`flask plugin upgrade-all`, leaving every plugin's own chain unapplied. That is
precisely the 1054 "Unknown column" a deploy then hits somewhere else, days
later, on the page that uses the new column.

The pilot runbook looped `flask plugin enable` over plugins that were not yet
installed; enable refuses those, so on a fresh database it exited 1 on every
iteration and enabled nothing. ADR-013 had already recorded that defect.
`apply-profile` installs and enables in dependency order, which is what the step
was reaching for.

DEPLOY-WINDOWS-IIS named a profile file that does not exist; the shipped ones do.

And PLUGIN-EXTERNAL-REPO never mentioned PLUGIN_TABLE_OWNERS, while the
migration engine raises for any plugin missing from it - so the guide's own
step 5 fails for any external plugin that owns a table. That the registry lives
in the framework repo is deliberate, so the guide now says so, and says what it
costs: a table-owning external plugin is a two-repository change, and a plugin
that owns no tables avoids it entirely.
This commit is contained in:
cproudlock
2026-08-14 15:43:27 -04:00
parent d1ba3a1a02
commit 05be4c4489
7 changed files with 242 additions and 202 deletions

View File

@@ -341,7 +341,7 @@
"purpose": "Email a report's rows as an HTML table on demand; recipient defaults to the site Alert Recipients setting; intended cron target via a PAT scoped to reports.export",
"auth": "jwt + permission:reports.export",
"params": "body: subject, columns [{key,label}], rows [{..}], intro (optional), to (optional email)",
"example": "curl -X POST http://localhost:5001/api/reports/email -H 'Authorization: Bearer $PAT' -H 'Content-Type: application/json' -d '{\"subject\":\"Warranty Report\",\"columns\":[{\"key\":\"vendor\",\"label\":\"Vendor\"}],\"rows\":[{\"vendor\":\"Haas\"}],\"to\":\"ops@example.com\"}'"
"example": "curl -X POST http://localhost:5001/api/reports/email -H \"Authorization: Bearer $PAT\" -H 'Content-Type: application/json' -d '{\"subject\":\"Warranty Report\",\"columns\":[{\"key\":\"vendor\",\"label\":\"Vendor\"}],\"rows\":[{\"vendor\":\"Haas\"}],\"to\":\"ops@example.com\"}'"
},
{
"method": "GET",
@@ -429,7 +429,7 @@
"purpose": "Create a display mapping; requires fqdn or ipaddress, dashboard role requires businessunitid, 409 on duplicate fqdn/IP; audit-logged",
"auth": "jwt + role:admin",
"params": "body: fqdn, ipaddress, displayrole (dashboard|lobby|partskiosk, default dashboard), businessunitid, description",
"example": "curl -X POST http://localhost:5001/api/dashboarddefaults -H 'Authorization: Bearer $JWT' -H 'Content-Type: application/json' -d '{\"ipaddress\":\"10.1.2.3\",\"displayrole\":\"dashboard\",\"businessunitid\":2}'"
"example": "curl -X POST http://localhost:5001/api/dashboarddefaults -H \"Authorization: Bearer $JWT\" -H 'Content-Type: application/json' -d '{\"ipaddress\":\"10.1.2.3\",\"displayrole\":\"dashboard\",\"businessunitid\":2}'"
},
{
"method": "PUT",
@@ -437,7 +437,7 @@
"purpose": "Update a display mapping; non-dashboard roles get businessunitid nulled, dashboard role must keep one",
"auth": "jwt + role:admin",
"params": "body: any of fqdn, ipaddress, displayrole, businessunitid, description",
"example": "curl -X PUT http://localhost:5001/api/dashboarddefaults/7 -H 'Authorization: Bearer $JWT' -H 'Content-Type: application/json' -d '{\"displayrole\":\"lobby\"}'"
"example": "curl -X PUT http://localhost:5001/api/dashboarddefaults/7 -H \"Authorization: Bearer $JWT\" -H 'Content-Type: application/json' -d '{\"displayrole\":\"lobby\"}'"
},
{
"method": "DELETE",
@@ -445,7 +445,7 @@
"purpose": "Soft-delete (deactivate) a display mapping",
"auth": "jwt + role:admin",
"params": "none",
"example": "curl -X DELETE http://localhost:5001/api/dashboarddefaults/7 -H 'Authorization: Bearer $JWT'"
"example": "curl -X DELETE http://localhost:5001/api/dashboarddefaults/7 -H \"Authorization: Bearer $JWT\""
},
{
"method": "POST",
@@ -453,7 +453,7 @@
"purpose": "Generic collector ingest (ADR-006): schema-validated identity field, idempotent upsert via the plugin's apply_collector_payload; audit-logged; 404 when no collector registered for the plugin",
"auth": "api-key (X-API-Key: per-plugin COLLECTOR_API_KEY_<NAME> or shared COLLECTOR_API_KEY, or a collector.ingest-scoped managed PAT via Bearer/X-API-Key)",
"params": "body: JSON payload whose schema identityfield (e.g. hostname) is required; rest is plugin-defined",
"example": "curl -X POST http://localhost:5001/api/collector/computers -H 'X-API-Key: $KEY' -H 'Content-Type: application/json' -d '{\"hostname\":\"WKSTN0042\",\"serialnumber\":\"ABC123\"}'"
"example": "curl -X POST http://localhost:5001/api/collector/computers -H \"X-API-Key: $KEY\" -H 'Content-Type: application/json' -d '{\"hostname\":\"WKSTN0042\",\"serialnumber\":\"ABC123\"}'"
},
{
"method": "GET",
@@ -461,7 +461,7 @@
"purpose": "List collector schemas for all enabled plugins that accept collector input",
"auth": "jwt",
"params": "none",
"example": "curl http://localhost:5001/api/collector/_schemas -H 'Authorization: Bearer $JWT'"
"example": "curl http://localhost:5001/api/collector/_schemas -H \"Authorization: Bearer $JWT\""
},
{
"method": "POST",
@@ -469,7 +469,7 @@
"purpose": "Legacy computers-specific ingest: update one PC matched by hostname (or asset number) - lastreporteddate, lastboottime, loggedinuser, serialnumber",
"auth": "api-key",
"params": "body: hostname (required), lastboottime (ISO), currentuser, serialnumber",
"example": "curl -X POST http://localhost:5001/api/collector/pc -H 'X-API-Key: $KEY' -H 'Content-Type: application/json' -d '{\"hostname\":\"WKSTN0042\",\"currentuser\":\"212345678\"}'"
"example": "curl -X POST http://localhost:5001/api/collector/pc -H \"X-API-Key: $KEY\" -H 'Content-Type: application/json' -d '{\"hostname\":\"WKSTN0042\",\"currentuser\":\"212345678\"}'"
},
{
"method": "POST",
@@ -477,7 +477,7 @@
"purpose": "Update installed applications for one PC; only apps already in the Application table are tracked, others skipped; returns created/updated/skipped counts",
"auth": "api-key",
"params": "body: hostname (required), apps [{appname, version}] (required)",
"example": "curl -X POST http://localhost:5001/api/collector/apps -H 'X-API-Key: $KEY' -H 'Content-Type: application/json' -d '{\"hostname\":\"WKSTN0042\",\"apps\":[{\"appname\":\"PC-DMIS\",\"version\":\"2023.2\"}]}'"
"example": "curl -X POST http://localhost:5001/api/collector/apps -H \"X-API-Key: $KEY\" -H 'Content-Type: application/json' -d '{\"hostname\":\"WKSTN0042\",\"apps\":[{\"appname\":\"PC-DMIS\",\"version\":\"2023.2\"}]}'"
},
{
"method": "POST",
@@ -485,7 +485,7 @@
"purpose": "Record PC online heartbeat (single hostname or batch); stamps lastreporteddate, returns updated count and notfound list",
"auth": "api-key",
"params": "body: hostname (string) or hostnames (array)",
"example": "curl -X POST http://localhost:5001/api/collector/heartbeat -H 'X-API-Key: $KEY' -H 'Content-Type: application/json' -d '{\"hostnames\":[\"pc1\",\"pc2\"]}'"
"example": "curl -X POST http://localhost:5001/api/collector/heartbeat -H \"X-API-Key: $KEY\" -H 'Content-Type: application/json' -d '{\"hostnames\":[\"pc1\",\"pc2\"]}'"
},
{
"method": "POST",
@@ -493,7 +493,7 @@
"purpose": "Bulk update many PCs in one call (lastreporteddate, currentuser, lastboottime per entry); returns updated/notfound/errors",
"auth": "api-key",
"params": "body: pcs [{hostname (required), currentuser, lastboottime}]",
"example": "curl -X POST http://localhost:5001/api/collector/bulk -H 'X-API-Key: $KEY' -H 'Content-Type: application/json' -d '{\"pcs\":[{\"hostname\":\"pc1\",\"currentuser\":\"212345678\"}]}'"
"example": "curl -X POST http://localhost:5001/api/collector/bulk -H \"X-API-Key: $KEY\" -H 'Content-Type: application/json' -d '{\"pcs\":[{\"hostname\":\"pc1\",\"currentuser\":\"212345678\"}]}'"
},
{
"method": "GET",
@@ -501,7 +501,7 @@
"purpose": "Collector API liveness/credential check; returns timestamp and the collector endpoint list",
"auth": "api-key",
"params": "none",
"example": "curl http://localhost:5001/api/collector/status -H 'X-API-Key: $KEY'"
"example": "curl http://localhost:5001/api/collector/status -H \"X-API-Key: $KEY\""
},
{
"method": "GET",
@@ -509,7 +509,7 @@
"purpose": "List audit logs with filtering and pagination, newest first; rows enriched with best-effort SSO-to-full-name resolution",
"auth": "jwt + permission:audit.view",
"params": "page (default 1), perpage (default 50, max 200), action (created|updated|deleted), entitytype, userid (int), search (entityname/username ilike), from_date, to_date (ISO)",
"example": "curl 'http://localhost:5001/api/auditlogs?action=deleted&perpage=100' -H 'Authorization: Bearer $JWT'"
"example": "curl 'http://localhost:5001/api/auditlogs?action=deleted&perpage=100' -H \"Authorization: Bearer $JWT\""
},
{
"method": "GET",
@@ -517,7 +517,7 @@
"purpose": "Full audit history for one entity, newest first",
"auth": "jwt + permission:audit.view",
"params": "path only",
"example": "curl http://localhost:5001/api/auditlogs/entity/Asset/42 -H 'Authorization: Bearer $JWT'"
"example": "curl http://localhost:5001/api/auditlogs/entity/Asset/42 -H \"Authorization: Bearer $JWT\""
},
{
"method": "GET",
@@ -525,7 +525,7 @@
"purpose": "Audit statistics: counts by action and entity type, last-7-days activity count, top 5 most active users",
"auth": "jwt + permission:audit.view",
"params": "none",
"example": "curl http://localhost:5001/api/auditlogs/stats -H 'Authorization: Bearer $JWT'"
"example": "curl http://localhost:5001/api/auditlogs/stats -H \"Authorization: Bearer $JWT\""
},
{
"method": "GET",
@@ -549,7 +549,7 @@
"purpose": "Enable or disable a plugin (route changes need an app restart); 409 when unknown or a dependency conflict refuses the change",
"auth": "jwt + role:admin",
"params": "body: enabled (bool, required)",
"example": "curl -X PUT http://localhost:5001/api/plugins/warranty -H 'Authorization: Bearer $JWT' -H 'Content-Type: application/json' -d '{\"enabled\":false}'"
"example": "curl -X PUT http://localhost:5001/api/plugins/warranty -H \"Authorization: Bearer $JWT\" -H 'Content-Type: application/json' -d '{\"enabled\":false}'"
},
{
"method": "GET",
@@ -618,7 +618,7 @@
"purpose": "List the full self-hosted directory for the management page; 400 when directory mode is external.",
"auth": "jwt-optional",
"params": "none",
"example": "curl -H 'Authorization: Bearer $TOKEN' 'http://localhost:5001/api/employees/directory'"
"example": "curl -H \"Authorization: Bearer $TOKEN\" 'http://localhost:5001/api/employees/directory'"
},
{
"method": "POST",
@@ -626,7 +626,7 @@
"purpose": "Create a self-hosted directory employee; 409 if SSO exists, 400 in external mode.",
"auth": "jwt + require_role admin",
"params": "JSON body: sso (numeric, required), firstname, lastname (required), team, role, picture (also accepts external-style keys SSO/First_Name/Last_Name/Team/Role/Picture)",
"example": "curl -X POST -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{\"sso\":210009518,\"firstname\":\"Jane\",\"lastname\":\"Doe\",\"team\":\"CNC\",\"role\":\"Machinist\"}' 'http://localhost:5001/api/employees/directory'"
"example": "curl -X POST -H \"Authorization: Bearer $TOKEN\" -H 'Content-Type: application/json' -d '{\"sso\":210009518,\"firstname\":\"Jane\",\"lastname\":\"Doe\",\"team\":\"CNC\",\"role\":\"Machinist\"}' 'http://localhost:5001/api/employees/directory'"
},
{
"method": "PUT",
@@ -634,7 +634,7 @@
"purpose": "Update a self-hosted directory employee's name/team/role/picture; 404 if missing, 400 in external mode.",
"auth": "jwt + require_role admin",
"params": "sso (path); JSON body: firstname, lastname, team, role, picture (external-style keys also accepted; team/role/picture can be cleared)",
"example": "curl -X PUT -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{\"team\":\"Quality\"}' 'http://localhost:5001/api/employees/directory/210009518'"
"example": "curl -X PUT -H \"Authorization: Bearer $TOKEN\" -H 'Content-Type: application/json' -d '{\"team\":\"Quality\"}' 'http://localhost:5001/api/employees/directory/210009518'"
},
{
"method": "DELETE",
@@ -642,7 +642,7 @@
"purpose": "Delete a self-hosted directory employee; 404 if missing, 400 in external mode.",
"auth": "jwt + require_role admin",
"params": "sso (path)",
"example": "curl -X DELETE -H 'Authorization: Bearer $TOKEN' 'http://localhost:5001/api/employees/directory/210009518'"
"example": "curl -X DELETE -H \"Authorization: Bearer $TOKEN\" 'http://localhost:5001/api/employees/directory/210009518'"
},
{
"method": "POST",
@@ -650,7 +650,7 @@
"purpose": "Bulk upsert the self-hosted directory from CSV (headers SSO,First_Name,Last_Name,Team,Role,Picture case-insensitive; plain firstname/lastname also accepted); returns added/updated/skipped counts.",
"auth": "jwt + require_role admin",
"params": "multipart file=<csv> OR JSON body {\"csv\": \"...\"}; rows missing numeric sso or names are skipped",
"example": "curl -X POST -H 'Authorization: Bearer $TOKEN' -F 'file=@employees.csv' 'http://localhost:5001/api/employees/directory/import'"
"example": "curl -X POST -H \"Authorization: Bearer $TOKEN\" -F 'file=@employees.csv' 'http://localhost:5001/api/employees/directory/import'"
},
{
"method": "POST",
@@ -658,7 +658,7 @@
"purpose": "Upload or replace a self-hosted employee's photo (saved as photo-<sso><ext> in instance employeephotos dir; old file wiped even on extension change); 409 in external mode, 404 if employee missing.",
"auth": "jwt + require_role admin",
"params": "sso (path); multipart/form-data file=<image>, extensions .png/.jpg/.jpeg/.gif/.webp only",
"example": "curl -X POST -H 'Authorization: Bearer $TOKEN' -F 'file=@jane.jpg' 'http://localhost:5001/api/employees/210009518/photo'"
"example": "curl -X POST -H \"Authorization: Bearer $TOKEN\" -F 'file=@jane.jpg' 'http://localhost:5001/api/employees/210009518/photo'"
},
{
"method": "GET",
@@ -674,7 +674,7 @@
"purpose": "Clear a self-hosted employee's photo record and delete the uploaded file; 409 in external mode, 404 if employee missing.",
"auth": "jwt + require_role admin",
"params": "sso (path)",
"example": "curl -X DELETE -H 'Authorization: Bearer $TOKEN' 'http://localhost:5001/api/employees/210009518/photo'"
"example": "curl -X DELETE -H \"Authorization: Bearer $TOKEN\" 'http://localhost:5001/api/employees/210009518/photo'"
}
]
},
@@ -718,7 +718,7 @@
"purpose": "Create a new KB article; validates required fields and that appid (if given) exists; honors X-Import-Mode timestamp preservation via apply_import_timestamps; returns 201.",
"auth": "permission:kb.create (jwt required)",
"params": "body JSON: shortdescription (required), linkurl (required), appid (optional int, must exist), keywords (optional); import-mode may pass timestamp fields",
"example": "curl -X POST 'http://localhost:5001/api/knowledgebase' -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{\"shortdescription\":\"VPN setup guide\",\"linkurl\":\"https://wiki.example.com/vpn\",\"appid\":3,\"keywords\":\"vpn,remote\"}'"
"example": "curl -X POST 'http://localhost:5001/api/knowledgebase' -H \"Authorization: Bearer $TOKEN\" -H 'Content-Type: application/json' -d '{\"shortdescription\":\"VPN setup guide\",\"linkurl\":\"https://wiki.example.com/vpn\",\"appid\":3,\"keywords\":\"vpn,remote\"}'"
},
{
"method": "PUT",
@@ -726,7 +726,7 @@
"purpose": "Update an article's shortdescription, linkurl, appid, keywords, and/or isactive; validates appid if changed; honors import timestamps; 404 if article missing.",
"auth": "permission:kb.edit (jwt required)",
"params": "path: link_id (int); body JSON: any of shortdescription, linkurl, appid, keywords, isactive",
"example": "curl -X PUT 'http://localhost:5001/api/knowledgebase/42' -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{\"keywords\":\"vpn,zscaler\",\"isactive\":true}'"
"example": "curl -X PUT 'http://localhost:5001/api/knowledgebase/42' -H \"Authorization: Bearer $TOKEN\" -H 'Content-Type: application/json' -d '{\"keywords\":\"vpn,zscaler\",\"isactive\":true}'"
},
{
"method": "DELETE",
@@ -734,7 +734,7 @@
"purpose": "Soft-delete an article by setting isactive=false (row is retained); 404 if article missing.",
"auth": "permission:kb.delete (jwt required)",
"params": "path: link_id (int)",
"example": "curl -X DELETE 'http://localhost:5001/api/knowledgebase/42' -H 'Authorization: Bearer $TOKEN'"
"example": "curl -X DELETE 'http://localhost:5001/api/knowledgebase/42' -H \"Authorization: Bearer $TOKEN\""
}
],
"surface": "plugin-knowledgebase"
@@ -1496,7 +1496,7 @@
"auth": "jwt-optional",
"params": "page, per_page, active (default true; 'false' includes inactive), search (ilike on computertype)",
"purpose": "List computer types, paginated, active-only by default",
"example": "curl -H 'Authorization: Bearer $TOKEN' 'http://localhost:5001/api/computers/types?search=kiosk&per_page=50'"
"example": "curl -H \"Authorization: Bearer $TOKEN\" 'http://localhost:5001/api/computers/types?search=kiosk&per_page=50'"
},
{
"method": "GET",
@@ -1512,7 +1512,7 @@
"auth": "permission:computers.create (jwt_required)",
"params": "body: computertype (required), description, icon, color; matching a deactivated type revives it instead of 409",
"purpose": "Create (or reactivate) a computer type",
"example": "curl -X POST -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{\"computertype\":\"Shopfloor\",\"color\":\"#0066cc\"}' 'http://localhost:5001/api/computers/types'"
"example": "curl -X POST -H \"Authorization: Bearer $TOKEN\" -H 'Content-Type: application/json' -d '{\"computertype\":\"Shopfloor\",\"color\":\"#0066cc\"}' 'http://localhost:5001/api/computers/types'"
},
{
"method": "PUT",
@@ -1520,7 +1520,7 @@
"auth": "permission:computers.edit (jwt_required)",
"params": "body: computertype, description, icon, color, isactive; 409 on duplicate name",
"purpose": "Update a computer type",
"example": "curl -X PUT -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{\"isactive\":false}' 'http://localhost:5001/api/computers/types/3'"
"example": "curl -X PUT -H \"Authorization: Bearer $TOKEN\" -H 'Content-Type: application/json' -d '{\"isactive\":false}' 'http://localhost:5001/api/computers/types/3'"
},
{
"method": "DELETE",
@@ -1528,7 +1528,7 @@
"auth": "permission:computers.delete (jwt_required)",
"params": "type_id in path; 409 if any Computer still uses the type",
"purpose": "Hard-delete a computer type when unused",
"example": "curl -X DELETE -H 'Authorization: Bearer $TOKEN' 'http://localhost:5001/api/computers/types/3'"
"example": "curl -X DELETE -H \"Authorization: Bearer $TOKEN\" 'http://localhost:5001/api/computers/types/3'"
},
{
"method": "GET",
@@ -1544,7 +1544,7 @@
"auth": "permission:computers.edit (jwt_required)",
"params": "body: name, scheme, linktemplate (all required), defaultport, isactive; 409 on duplicate name",
"purpose": "Create an access protocol",
"example": "curl -X POST -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{\"name\":\"VNC\",\"scheme\":\"vnc\",\"defaultport\":5900,\"linktemplate\":\"vnc://{host}:{port}\"}' 'http://localhost:5001/api/computers/protocols'"
"example": "curl -X POST -H \"Authorization: Bearer $TOKEN\" -H 'Content-Type: application/json' -d '{\"name\":\"VNC\",\"scheme\":\"vnc\",\"defaultport\":5900,\"linktemplate\":\"vnc://{host}:{port}\"}' 'http://localhost:5001/api/computers/protocols'"
},
{
"method": "PUT|PATCH",
@@ -1552,7 +1552,7 @@
"auth": "permission:computers.edit (jwt_required)",
"params": "body: name, scheme, linktemplate, defaultport, isactive (all optional)",
"purpose": "Update an access protocol",
"example": "curl -X PATCH -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{\"defaultport\":5901}' 'http://localhost:5001/api/computers/protocols/2'"
"example": "curl -X PATCH -H \"Authorization: Bearer $TOKEN\" -H 'Content-Type: application/json' -d '{\"defaultport\":5901}' 'http://localhost:5001/api/computers/protocols/2'"
},
{
"method": "DELETE",
@@ -1560,7 +1560,7 @@
"auth": "permission:computers.edit (jwt_required)",
"params": "protocol_id in path; if referenced by any ComputerAccess it deactivates instead of deleting",
"purpose": "Delete an access protocol (soft-deactivate when in use)",
"example": "curl -X DELETE -H 'Authorization: Bearer $TOKEN' 'http://localhost:5001/api/computers/protocols/2'"
"example": "curl -X DELETE -H \"Authorization: Bearer $TOKEN\" 'http://localhost:5001/api/computers/protocols/2'"
},
{
"method": "GET",
@@ -1568,7 +1568,7 @@
"auth": "jwt-optional",
"params": "none; uses pctype mapping for gea-shopfloor-display (default 'Kiosk') and display_fqdn_domain setting",
"purpose": "List display-kiosk computers with derived F<serial>.<domain> FQDN for the Dashboard Defaults picker",
"example": "curl -H 'Authorization: Bearer $TOKEN' 'http://localhost:5001/api/computers/display-kiosks'"
"example": "curl -H \"Authorization: Bearer $TOKEN\" 'http://localhost:5001/api/computers/display-kiosks'"
},
{
"method": "GET",
@@ -1608,7 +1608,7 @@
"auth": "permission:computers.create (jwt_required)",
"params": "body: assetnumber (required); name, serialnumber, gaugelabreference, maintenancereference, statusid, locationid, businessunitid, mapx, mapy, notes, computertypeid, hostname, osid, vendorid, modelnumberid, loggedinuser, lastreporteddate, lastboottime, ipaddress (creates primary IP communication), accessmethods [{protocolid, portoverride?}]; X-Import-Mode header preserves legacy timestamps; 409 on duplicate assetnumber or hostname",
"purpose": "Create a computer (Asset + Computer records, optional primary IP and access methods), audit-logged",
"example": "curl -X POST -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{\"assetnumber\":\"PC-0042\",\"hostname\":\"tsgwp00042\",\"computertypeid\":1,\"osid\":2,\"ipaddress\":\"10.1.2.3\",\"accessmethods\":[{\"protocolid\":1}]}' 'http://localhost:5001/api/computers'"
"example": "curl -X POST -H \"Authorization: Bearer $TOKEN\" -H 'Content-Type: application/json' -d '{\"assetnumber\":\"PC-0042\",\"hostname\":\"tsgwp00042\",\"computertypeid\":1,\"osid\":2,\"ipaddress\":\"10.1.2.3\",\"accessmethods\":[{\"protocolid\":1}]}' 'http://localhost:5001/api/computers'"
},
{
"method": "PUT",
@@ -1616,7 +1616,7 @@
"auth": "permission:computers.edit (jwt_required)",
"params": "body: any asset field (assetnumber, name, serialnumber, gaugelabreference, maintenancereference, statusid, locationid, businessunitid, mapx, mapy, notes, isactive) or computer field (computertypeid, hostname, osid, vendorid, modelnumberid, loggedinuser, lastreporteddate, lastboottime); ipaddress upserts/clears the primary IP communication; accessmethods replaces protocol list; 409 on assetnumber/hostname conflict; changes audit-logged",
"purpose": "Update a computer's asset and extension fields",
"example": "curl -X PUT -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{\"locationid\":7,\"ipaddress\":\"10.1.2.4\"}' 'http://localhost:5001/api/computers/42'"
"example": "curl -X PUT -H \"Authorization: Bearer $TOKEN\" -H 'Content-Type: application/json' -d '{\"locationid\":7,\"ipaddress\":\"10.1.2.4\"}' 'http://localhost:5001/api/computers/42'"
},
{
"method": "DELETE",
@@ -1624,7 +1624,7 @@
"auth": "permission:computers.delete (jwt_required)",
"params": "computer_id in path",
"purpose": "Soft-delete a computer (sets asset isactive=false), audit-logged",
"example": "curl -X DELETE -H 'Authorization: Bearer $TOKEN' 'http://localhost:5001/api/computers/42'"
"example": "curl -X DELETE -H \"Authorization: Bearer $TOKEN\" 'http://localhost:5001/api/computers/42'"
},
{
"method": "GET",
@@ -1640,7 +1640,7 @@
"auth": "permission:computers.create (jwt_required)",
"params": "body: appid (required, must exist in Applications), appversionid; reactivates a soft-deleted install; 409 if already installed",
"purpose": "Record an application install on a computer",
"example": "curl -X POST -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{\"appid\":17,\"appversionid\":3}' 'http://localhost:5001/api/computers/42/apps'"
"example": "curl -X POST -H \"Authorization: Bearer $TOKEN\" -H 'Content-Type: application/json' -d '{\"appid\":17,\"appversionid\":3}' 'http://localhost:5001/api/computers/42/apps'"
},
{
"method": "DELETE",
@@ -1648,7 +1648,7 @@
"auth": "permission:computers.delete (jwt_required)",
"params": "computer_id and app_id in path",
"purpose": "Soft-remove an installed application (isactive=false)",
"example": "curl -X DELETE -H 'Authorization: Bearer $TOKEN' 'http://localhost:5001/api/computers/42/apps/17'"
"example": "curl -X DELETE -H \"Authorization: Bearer $TOKEN\" 'http://localhost:5001/api/computers/42/apps/17'"
},
{
"method": "POST",
@@ -1656,7 +1656,7 @@
"auth": "permission:computers.create (jwt_required)",
"params": "body (all optional): loggedinuser, lastboottime; server sets lastreporteddate to now (UTC)",
"purpose": "Agent status check-in: refresh last-reported timestamp plus logged-in user and boot time",
"example": "curl -X POST -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{\"loggedinuser\":\"jsmith\",\"lastboottime\":\"2026-07-30T06:00:00\"}' 'http://localhost:5001/api/computers/42/report'"
"example": "curl -X POST -H \"Authorization: Bearer $TOKEN\" -H 'Content-Type: application/json' -d '{\"loggedinuser\":\"jsmith\",\"lastboottime\":\"2026-07-30T06:00:00\"}' 'http://localhost:5001/api/computers/42/report'"
},
{
"method": "GET",
@@ -1694,7 +1694,7 @@
"purpose": "Create a measuring-tool type; reactivates a soft-deleted same-named one, 409 if an active one exists.",
"auth": "jwt + permission:measuringtools.create",
"params": "body JSON: name (required), description, color",
"example": "curl -X POST 'http://localhost:5001/api/measuringtools/types' -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{\"name\":\"Thread Gage\",\"description\":\"Go/no-go thread gages\",\"color\":\"#4caf50\"}'"
"example": "curl -X POST 'http://localhost:5001/api/measuringtools/types' -H \"Authorization: Bearer $TOKEN\" -H 'Content-Type: application/json' -d '{\"name\":\"Thread Gage\",\"description\":\"Go/no-go thread gages\",\"color\":\"#4caf50\"}'"
},
{
"method": "PUT",
@@ -1702,7 +1702,7 @@
"purpose": "Update a measuring-tool type; 409 on rename collision with an existing name.",
"auth": "jwt + permission:measuringtools.edit",
"params": "path: type_id; body JSON: name, description, color, isactive (only keys present are applied)",
"example": "curl -X PUT 'http://localhost:5001/api/measuringtools/types/3' -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{\"description\":\"Updated\",\"isactive\":true}'"
"example": "curl -X PUT 'http://localhost:5001/api/measuringtools/types/3' -H \"Authorization: Bearer $TOKEN\" -H 'Content-Type: application/json' -d '{\"description\":\"Updated\",\"isactive\":true}'"
},
{
"method": "DELETE",
@@ -1710,7 +1710,7 @@
"purpose": "Hard-delete a measuring-tool type; refused with 409 if any tool still references it.",
"auth": "jwt + permission:measuringtools.delete",
"params": "path: type_id (int)",
"example": "curl -X DELETE 'http://localhost:5001/api/measuringtools/types/3' -H 'Authorization: Bearer $TOKEN'"
"example": "curl -X DELETE 'http://localhost:5001/api/measuringtools/types/3' -H \"Authorization: Bearer $TOKEN\""
},
{
"method": "GET",
@@ -1742,7 +1742,7 @@
"purpose": "Create a measuring tool: one Asset core row (assettype 'measuring_tool') plus one measuringtools extension row in a single payload; audit-logged.",
"auth": "jwt + permission:measuringtools.create",
"params": "body JSON: assetnumber (required, 409 on duplicate), name, gaugelabreference, maintenancereference, serialnumber, statusid (default 1), locationid, businessunitid, mapx, mapy, notes, measuringtooltypeid, calibrationintervaldays, lastcalibrationdate (YYYY-MM-DD), nextcalibrationdate (YYYY-MM-DD), calibrationprovider; import timestamps honored via X-Import-Mode",
"example": "curl -X POST 'http://localhost:5001/api/measuringtools' -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{\"assetnumber\":\"MT-0042\",\"name\":\"6in Digital Caliper\",\"measuringtooltypeid\":2,\"calibrationintervaldays\":365,\"lastcalibrationdate\":\"2026-01-15\",\"nextcalibrationdate\":\"2027-01-15\",\"calibrationprovider\":\"Gage Lab\"}'"
"example": "curl -X POST 'http://localhost:5001/api/measuringtools' -H \"Authorization: Bearer $TOKEN\" -H 'Content-Type: application/json' -d '{\"assetnumber\":\"MT-0042\",\"name\":\"6in Digital Caliper\",\"measuringtooltypeid\":2,\"calibrationintervaldays\":365,\"lastcalibrationdate\":\"2026-01-15\",\"nextcalibrationdate\":\"2027-01-15\",\"calibrationprovider\":\"Gage Lab\"}'"
},
{
"method": "PUT",
@@ -1750,7 +1750,7 @@
"purpose": "Update asset core fields and extension fields in one payload; 409 on assetnumber collision; changes audit-logged.",
"auth": "jwt + permission:measuringtools.edit",
"params": "path: tool_id; body JSON (only present keys applied): asset fields assetnumber, name, gaugelabreference, maintenancereference, serialnumber, statusid, locationid, businessunitid, mapx, mapy, notes, isactive; extension fields measuringtooltypeid, calibrationintervaldays, calibrationprovider, notes, lastcalibrationdate, nextcalibrationdate (YYYY-MM-DD)",
"example": "curl -X PUT 'http://localhost:5001/api/measuringtools/17' -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{\"locationid\":5,\"nextcalibrationdate\":\"2026-12-01\"}'"
"example": "curl -X PUT 'http://localhost:5001/api/measuringtools/17' -H \"Authorization: Bearer $TOKEN\" -H 'Content-Type: application/json' -d '{\"locationid\":5,\"nextcalibrationdate\":\"2026-12-01\"}'"
},
{
"method": "DELETE",
@@ -1758,7 +1758,7 @@
"purpose": "Soft-delete a measuring tool by setting its asset isactive=false; audit-logged.",
"auth": "jwt + permission:measuringtools.delete",
"params": "path: tool_id (int)",
"example": "curl -X DELETE 'http://localhost:5001/api/measuringtools/17' -H 'Authorization: Bearer $TOKEN'"
"example": "curl -X DELETE 'http://localhost:5001/api/measuringtools/17' -H \"Authorization: Bearer $TOKEN\""
},
{
"method": "GET",
@@ -1803,7 +1803,7 @@
"purpose": "Create a machine type; reactivates a soft-deleted type of the same name instead of 409ing",
"auth": "jwt + permission:machines.create",
"params": "body: machinetype (required), description, icon, color",
"example": "curl -X POST 'http://localhost:5001/api/machines/types' -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{\"machinetype\":\"Lathe\",\"description\":\"Turning machines\",\"icon\":\"mdi-rotate-3d\",\"color\":\"#1976d2\"}'"
"example": "curl -X POST 'http://localhost:5001/api/machines/types' -H \"Authorization: Bearer $TOKEN\" -H 'Content-Type: application/json' -d '{\"machinetype\":\"Lathe\",\"description\":\"Turning machines\",\"icon\":\"mdi-rotate-3d\",\"color\":\"#1976d2\"}'"
},
{
"method": "PUT",
@@ -1811,7 +1811,7 @@
"purpose": "Update a machine type (rename guarded by 409 on duplicate name)",
"auth": "jwt + permission:machines.edit",
"params": "path: type_id; body: any of machinetype, description, icon, color, isactive",
"example": "curl -X PUT 'http://localhost:5001/api/machines/types/3' -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{\"description\":\"5-axis mills\",\"isactive\":true}'"
"example": "curl -X PUT 'http://localhost:5001/api/machines/types/3' -H \"Authorization: Bearer $TOKEN\" -H 'Content-Type: application/json' -d '{\"description\":\"5-axis mills\",\"isactive\":true}'"
},
{
"method": "DELETE",
@@ -1819,7 +1819,7 @@
"purpose": "Hard-delete a machine type; refused with 409 if any machine still references it",
"auth": "jwt + permission:machines.delete",
"params": "path: type_id (int)",
"example": "curl -X DELETE 'http://localhost:5001/api/machines/types/3' -H 'Authorization: Bearer $TOKEN'"
"example": "curl -X DELETE 'http://localhost:5001/api/machines/types/3' -H \"Authorization: Bearer $TOKEN\""
},
{
"method": "GET",
@@ -1851,7 +1851,7 @@
"purpose": "Create a machine (creates both core Asset row and Machine extension row); audit-logged; honors X-Import-Mode legacy timestamps via apply_import_timestamps",
"auth": "jwt + permission:machines.create",
"params": "body: assetnumber (required, 409 on duplicate); asset fields: name, gaugelabreference, maintenancereference, serialnumber, statusid (default 1), locationid, businessunitid, mapx, mapy, notes; machine fields: machinetypeid, vendorid, modelnumberid, requiresmanualconfig, islocationonly, lastmaintenancedate, nextmaintenancedate, maintenanceintervaldays, controllervendorid, controllermodelid",
"example": "curl -X POST 'http://localhost:5001/api/machines' -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{\"assetnumber\":\"2007\",\"name\":\"Makino a51\",\"machinetypeid\":2,\"vendorid\":5,\"locationid\":3,\"statusid\":1}'"
"example": "curl -X POST 'http://localhost:5001/api/machines' -H \"Authorization: Bearer $TOKEN\" -H 'Content-Type: application/json' -d '{\"assetnumber\":\"2007\",\"name\":\"Makino a51\",\"machinetypeid\":2,\"vendorid\":5,\"locationid\":3,\"statusid\":1}'"
},
{
"method": "PUT",
@@ -1859,7 +1859,7 @@
"purpose": "Update machine (asset + machine fields), with per-field change tracking to AuditLog and 409 on assetnumber conflict",
"auth": "jwt + permission:machines.edit",
"params": "path: machine_id; body: any of assetnumber, name, gaugelabreference, maintenancereference, serialnumber, statusid, locationid, businessunitid, mapx, mapy, notes, isactive, machinetypeid, vendorid, modelnumberid, requiresmanualconfig, islocationonly, lastmaintenancedate, nextmaintenancedate, maintenanceintervaldays, controllervendorid, controllermodelid",
"example": "curl -X PUT 'http://localhost:5001/api/machines/42' -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{\"locationid\":7,\"nextmaintenancedate\":\"2026-09-01\"}'"
"example": "curl -X PUT 'http://localhost:5001/api/machines/42' -H \"Authorization: Bearer $TOKEN\" -H 'Content-Type: application/json' -d '{\"locationid\":7,\"nextmaintenancedate\":\"2026-09-01\"}'"
},
{
"method": "DELETE",
@@ -1867,7 +1867,7 @@
"purpose": "Soft-delete a machine (sets asset.isactive=False, keeps Machine row linked); audit-logged",
"auth": "jwt + permission:machines.delete",
"params": "path: machine_id (int)",
"example": "curl -X DELETE 'http://localhost:5001/api/machines/42' -H 'Authorization: Bearer $TOKEN'"
"example": "curl -X DELETE 'http://localhost:5001/api/machines/42' -H \"Authorization: Bearer $TOKEN\""
},
{
"method": "GET",
@@ -1888,7 +1888,7 @@
"purpose": "Serve the current PUBLISHED manifest JSON snapshot for a scope (never the live draft) to the GE-Enforce client, with ETag/304 support and X-Manifest-Version header.",
"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)",
"params": "query: pctype (required, =scopename), phase (default 'runtime'); header: If-None-Match for 304",
"example": "curl -H 'X-API-Key: $TOKEN' 'http://localhost:5001/api/geenforce/manifest?pctype=cmm&phase=runtime'"
"example": "curl -H \"X-API-Key: $TOKEN\" 'http://localhost:5001/api/geenforce/manifest?pctype=cmm&phase=runtime'"
},
{
"method": "GET",
@@ -1896,7 +1896,7 @@
"purpose": "Download a payload blob by content hash (blob store first, then inline DB payload) so share-less PCs can pull installers over HTTPS; per-IP rate limited (120/60s default) and size-capped (512MB default, 413 above).",
"auth": "api-key (geenforce.fetch service token) OR IP allowlist; resource-bound tokens get 404 for blobs not referenced by their scopes",
"params": "path: sha256 (64 lowercase hex chars, 400 otherwise); header: If-None-Match (ETag = the hash)",
"example": "curl -H 'X-API-Key: $TOKEN' -o installer.exe 'http://localhost:5001/api/geenforce/payload/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'"
"example": "curl -H \"X-API-Key: $TOKEN\" -o installer.exe 'http://localhost:5001/api/geenforce/payload/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'"
},
{
"method": "POST",
@@ -1904,7 +1904,7 @@
"purpose": "Record one PC's enforcement cycle: applied manifest version plus per-entry self-heal outcomes (installed/skipped/failed); returns reportid + status.",
"auth": "api-key (managed service token with geenforce.report scope) OR IP allowlist",
"params": "JSON body: hostname (required); remainder parsed by service.record_enforcement_report (scopename, phase, appliedversion, enforcerversion, status, per-entry results, counts); 400 on ValueError",
"example": "curl -X POST -H 'X-API-Key: $TOKEN' -H 'Content-Type: application/json' -d '{\"hostname\":\"WKSTN0042\",\"scopename\":\"cmm\",\"appliedversion\":4,\"results\":[{\"entryname\":\"7zip\",\"action\":\"installed\"}]}' http://localhost:5001/api/geenforce/report"
"example": "curl -X POST -H \"X-API-Key: $TOKEN\" -H 'Content-Type: application/json' -d '{\"hostname\":\"WKSTN0042\",\"scopename\":\"cmm\",\"appliedversion\":4,\"results\":[{\"entryname\":\"7zip\",\"action\":\"installed\"}]}' http://localhost:5001/api/geenforce/report"
},
{
"method": "GET",
@@ -1912,7 +1912,7 @@
"purpose": "List all imaging PC-type scopes with entry counts and current published version numbers, ordered by phase then scopename.",
"auth": "jwt + permission:geenforce.manage",
"params": "none",
"example": "curl -H 'Authorization: Bearer $JWT' http://localhost:5001/api/geenforce/scopes"
"example": "curl -H \"Authorization: Bearer $JWT\" http://localhost:5001/api/geenforce/scopes"
},
{
"method": "POST",
@@ -1920,7 +1920,7 @@
"purpose": "Create a new manifest scope; 400 if scopename missing, phase invalid, or scope already exists for that scopename+phase; returns 201 with scope summary.",
"auth": "jwt + permission:geenforce.manage",
"params": "JSON body: scopename (required), phase (default 'runtime', must be in PHASES), manifestversion (default '1.0'), description, computertypeid, measuringtooltypeid, iscommon (defaults true when scopename=='common')",
"example": "curl -X POST -H 'Authorization: Bearer $JWT' -H 'Content-Type: application/json' -d '{\"scopename\":\"cmm\",\"phase\":\"runtime\",\"description\":\"CMM bays\"}' http://localhost:5001/api/geenforce/scopes"
"example": "curl -X POST -H \"Authorization: Bearer $JWT\" -H 'Content-Type: application/json' -d '{\"scopename\":\"cmm\",\"phase\":\"runtime\",\"description\":\"CMM bays\"}' http://localhost:5001/api/geenforce/scopes"
},
{
"method": "GET",
@@ -1928,7 +1928,7 @@
"purpose": "Get one scope's summary plus its full draft entry list (each entry includes entryid, sortorder, curated appid/appname link, and inline-payload metadata).",
"auth": "jwt + permission:geenforce.manage",
"params": "path: scopeid",
"example": "curl -H 'Authorization: Bearer $JWT' http://localhost:5001/api/geenforce/scopes/3"
"example": "curl -H \"Authorization: Bearer $JWT\" http://localhost:5001/api/geenforce/scopes/3"
},
{
"method": "PUT",
@@ -1936,7 +1936,7 @@
"purpose": "Update scope metadata fields (only keys present in the body are changed); scopename and phase are immutable here.",
"auth": "jwt + permission:geenforce.manage",
"params": "JSON body (all optional): description, computertypeid, measuringtooltypeid, manifestversion (stringified), iscommon (bool-coerced)",
"example": "curl -X PUT -H 'Authorization: Bearer $JWT' -H 'Content-Type: application/json' -d '{\"description\":\"updated\",\"manifestversion\":\"1.1\"}' http://localhost:5001/api/geenforce/scopes/3"
"example": "curl -X PUT -H \"Authorization: Bearer $JWT\" -H 'Content-Type: application/json' -d '{\"description\":\"updated\",\"manifestversion\":\"1.1\"}' http://localhost:5001/api/geenforce/scopes/3"
},
{
"method": "DELETE",
@@ -1944,7 +1944,7 @@
"purpose": "Delete a scope (and via cascade its entries); returns {deleted: scopeid}.",
"auth": "jwt + permission:geenforce.manage",
"params": "path: scopeid",
"example": "curl -X DELETE -H 'Authorization: Bearer $JWT' http://localhost:5001/api/geenforce/scopes/3"
"example": "curl -X DELETE -H \"Authorization: Bearer $JWT\" http://localhost:5001/api/geenforce/scopes/3"
},
{
"method": "GET",
@@ -1952,7 +1952,7 @@
"purpose": "Render the DRAFT manifest JSON that a publish would freeze, for admin review before shipping.",
"auth": "jwt + permission:geenforce.manage",
"params": "path: scopeid",
"example": "curl -H 'Authorization: Bearer $JWT' http://localhost:5001/api/geenforce/scopes/3/preview"
"example": "curl -H \"Authorization: Bearer $JWT\" http://localhost:5001/api/geenforce/scopes/3/preview"
},
{
"method": "GET",
@@ -1960,7 +1960,7 @@
"purpose": "List the core active Applications catalog (appid + appname) for the curated entry-to-app link picker in the entry editor.",
"auth": "jwt + permission:geenforce.manage",
"params": "none",
"example": "curl -H 'Authorization: Bearer $JWT' http://localhost:5001/api/geenforce/applications"
"example": "curl -H \"Authorization: Bearer $JWT\" http://localhost:5001/api/geenforce/applications"
},
{
"method": "POST",
@@ -1968,7 +1968,7 @@
"purpose": "Create a manifest entry in a scope at the next sortorder; validates Name (required) and Type (must be in ENTRY_TYPES); 400 on duplicate Name in scope; returns 201 with entry payload.",
"auth": "jwt + permission:geenforce.manage",
"params": "path: scopeid; JSON body: Name (required), Type (required, one of ENTRY_TYPES), optional appid (curated app link, unknown/non-numeric ids ignored), plus manifest fields consumed by build_entry (PCTypes, TargetHostnames, DetectionValue, etc.)",
"example": "curl -X POST -H 'Authorization: Bearer $JWT' -H 'Content-Type: application/json' -d '{\"Name\":\"7-Zip\",\"Type\":\"App\",\"appid\":12}' http://localhost:5001/api/geenforce/scopes/3/entries"
"example": "curl -X POST -H \"Authorization: Bearer $JWT\" -H 'Content-Type: application/json' -d '{\"Name\":\"7-Zip\",\"Type\":\"App\",\"appid\":12}' http://localhost:5001/api/geenforce/scopes/3/entries"
},
{
"method": "PUT",
@@ -1976,7 +1976,7 @@
"purpose": "Replace an entry's fields from the payload (re-populates via populate_entry, re-creating the one-to-one InUseCheck); same Name/Type validation and duplicate-Name 400 as create.",
"auth": "jwt + permission:geenforce.manage",
"params": "path: entryid; JSON body: Name (required), Type (required), optional appid, plus manifest fields",
"example": "curl -X PUT -H 'Authorization: Bearer $JWT' -H 'Content-Type: application/json' -d '{\"Name\":\"7-Zip\",\"Type\":\"App\",\"DetectionValue\":\"24.08\"}' http://localhost:5001/api/geenforce/entries/17"
"example": "curl -X PUT -H \"Authorization: Bearer $JWT\" -H 'Content-Type: application/json' -d '{\"Name\":\"7-Zip\",\"Type\":\"App\",\"DetectionValue\":\"24.08\"}' http://localhost:5001/api/geenforce/entries/17"
},
{
"method": "DELETE",
@@ -1984,7 +1984,7 @@
"purpose": "Delete a manifest entry; returns {deleted: entryid}.",
"auth": "jwt + permission:geenforce.manage",
"params": "path: entryid",
"example": "curl -X DELETE -H 'Authorization: Bearer $JWT' http://localhost:5001/api/geenforce/entries/17"
"example": "curl -X DELETE -H \"Authorization: Bearer $JWT\" http://localhost:5001/api/geenforce/entries/17"
},
{
"method": "PUT",
@@ -1992,7 +1992,7 @@
"purpose": "Set entry ordering from an entryid list; 400 unless the list is exactly the set of this scope's entry ids.",
"auth": "jwt + permission:geenforce.manage",
"params": "path: scopeid; JSON body: order = [entryid, ...] (must match the scope's entry ids exactly)",
"example": "curl -X PUT -H 'Authorization: Bearer $JWT' -H 'Content-Type: application/json' -d '{\"order\":[17,15,16]}' http://localhost:5001/api/geenforce/scopes/3/entries/reorder"
"example": "curl -X PUT -H \"Authorization: Bearer $JWT\" -H 'Content-Type: application/json' -d '{\"order\":[17,15,16]}' http://localhost:5001/api/geenforce/scopes/3/entries/reorder"
},
{
"method": "GET",
@@ -2000,7 +2000,7 @@
"purpose": "Simulate which draft entries would apply to a given machine profile and why the rest are filtered out (PCTypes/TargetHostnames/TargetMachineNumbers/_CmmVersion), using the engine-mirror filters.",
"auth": "jwt + permission:geenforce.manage",
"params": "path: scopeid; query (all optional): pctype (defaults to scopename), subtype, hostname, machinenumber, cmmversion; phase comes from the scope",
"example": "curl -H 'Authorization: Bearer $JWT' 'http://localhost:5001/api/geenforce/scopes/3/simulate?hostname=WKSTN0042&cmmversion=2023.2'"
"example": "curl -H \"Authorization: Bearer $JWT\" 'http://localhost:5001/api/geenforce/scopes/3/simulate?hostname=WKSTN0042&cmmversion=2023.2'"
},
{
"method": "GET",
@@ -2008,7 +2008,7 @@
"purpose": "Fleet install coverage per app-linked entry (installed/version-match counts from the computers plugin's ComputerInstalledApp; null counts with computersplugin:false when that plugin is absent).",
"auth": "jwt + permission:geenforce.manage",
"params": "path: scopeid",
"example": "curl -H 'Authorization: Bearer $JWT' http://localhost:5001/api/geenforce/scopes/3/compliance"
"example": "curl -H \"Authorization: Bearer $JWT\" http://localhost:5001/api/geenforce/scopes/3/compliance"
},
{
"method": "POST",
@@ -2016,7 +2016,7 @@
"purpose": "Upload an inline payload file (max 1 MB, rejects empty) for an entry and point the entry at it (stores sha256, filename, mimetype); returns 201 with entry payload.",
"auth": "jwt + permission:geenforce.publish",
"params": "path: entryid; multipart/form-data: file (required)",
"example": "curl -X POST -H 'Authorization: Bearer $JWT' -F 'file=@fix.ps1' http://localhost:5001/api/geenforce/entries/17/payload"
"example": "curl -X POST -H \"Authorization: Bearer $JWT\" -F 'file=@fix.ps1' http://localhost:5001/api/geenforce/entries/17/payload"
},
{
"method": "GET",
@@ -2024,7 +2024,7 @@
"purpose": "Download the stored inline payload bytes for an entry as an attachment (404 if the entry has no payload).",
"auth": "jwt + permission:geenforce.manage",
"params": "path: entryid",
"example": "curl -H 'Authorization: Bearer $JWT' -o fix.ps1 http://localhost:5001/api/geenforce/entries/17/payload"
"example": "curl -H \"Authorization: Bearer $JWT\" -o fix.ps1 http://localhost:5001/api/geenforce/entries/17/payload"
},
{
"method": "POST",
@@ -2032,7 +2032,7 @@
"purpose": "Freeze the scope's draft into a new published version (service.publish_scope), recording the publishing user from the JWT identity and optional notes; returns 201 with the new versionnumber.",
"auth": "jwt + permission:geenforce.publish",
"params": "path: scopeid; JSON body (optional): notes",
"example": "curl -X POST -H 'Authorization: Bearer $JWT' -H 'Content-Type: application/json' -d '{\"notes\":\"add 7zip 24.08\"}' http://localhost:5001/api/geenforce/scopes/3/publish"
"example": "curl -X POST -H \"Authorization: Bearer $JWT\" -H 'Content-Type: application/json' -d '{\"notes\":\"add 7zip 24.08\"}' http://localhost:5001/api/geenforce/scopes/3/publish"
},
{
"method": "GET",
@@ -2040,7 +2040,7 @@
"purpose": "List published versions for a scope, newest first (versionnumber, iscurrent, publishedat, publishedby, notes).",
"auth": "jwt + permission:geenforce.manage",
"params": "path: scopeid",
"example": "curl -H 'Authorization: Bearer $JWT' http://localhost:5001/api/geenforce/scopes/3/versions"
"example": "curl -H \"Authorization: Bearer $JWT\" http://localhost:5001/api/geenforce/scopes/3/versions"
},
{
"method": "GET",
@@ -2048,7 +2048,7 @@
"purpose": "Fetch one published version's frozen manifest JSON (parsed and returned in the success envelope).",
"auth": "jwt + permission:geenforce.manage",
"params": "path: scopeid, versionnumber",
"example": "curl -H 'Authorization: Bearer $JWT' http://localhost:5001/api/geenforce/scopes/3/versions/4"
"example": "curl -H \"Authorization: Bearer $JWT\" http://localhost:5001/api/geenforce/scopes/3/versions/4"
},
{
"method": "POST",
@@ -2056,7 +2056,7 @@
"purpose": "Roll the scope's current published pointer back to an earlier versionnumber (service.rollback_scope); 400 with the error message if the version is invalid.",
"auth": "jwt + permission:geenforce.publish",
"params": "path: scopeid; JSON body: versionnumber (required, int)",
"example": "curl -X POST -H 'Authorization: Bearer $JWT' -H 'Content-Type: application/json' -d '{\"versionnumber\":3}' http://localhost:5001/api/geenforce/scopes/3/rollback"
"example": "curl -X POST -H \"Authorization: Bearer $JWT\" -H 'Content-Type: application/json' -d '{\"versionnumber\":3}' http://localhost:5001/api/geenforce/scopes/3/rollback"
},
{
"method": "POST",
@@ -2064,7 +2064,7 @@
"purpose": "Write the scope's current published JSON to the configured share root (geenforce_share_root setting), backing up the old file to _meta/history; 400 if the share root is unconfigured or the export fails.",
"auth": "jwt + permission:geenforce.publish",
"params": "path: scopeid; no body",
"example": "curl -X POST -H 'Authorization: Bearer $JWT' http://localhost:5001/api/geenforce/scopes/3/export-share"
"example": "curl -X POST -H \"Authorization: Bearer $JWT\" http://localhost:5001/api/geenforce/scopes/3/export-share"
},
{
"method": "GET",
@@ -2072,7 +2072,7 @@
"purpose": "Read plugin config: the on-share export root (geenforce_share_root) and the client IP allowlist CIDRs (geenforce_allowed_cidrs).",
"auth": "jwt + permission:geenforce.manage",
"params": "none",
"example": "curl -H 'Authorization: Bearer $JWT' http://localhost:5001/api/geenforce/config"
"example": "curl -H \"Authorization: Bearer $JWT\" http://localhost:5001/api/geenforce/config"
},
{
"method": "PUT",
@@ -2080,7 +2080,7 @@
"purpose": "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.",
"auth": "jwt + permission:geenforce.publish",
"params": "JSON body (both optional): shareroot (string path), allowedcidrs (CSV/newline CIDR list)",
"example": "curl -X PUT -H 'Authorization: Bearer $JWT' -H 'Content-Type: application/json' -d '{\"allowedcidrs\":\"10.20.30.0/24, 192.168.5.7\"}' http://localhost:5001/api/geenforce/config"
"example": "curl -X PUT -H \"Authorization: Bearer $JWT\" -H 'Content-Type: application/json' -d '{\"allowedcidrs\":\"10.20.30.0/24, 192.168.5.7\"}' http://localhost:5001/api/geenforce/config"
},
{
"method": "GET",
@@ -2088,7 +2088,7 @@
"purpose": "Latest enforcement report per PC (iscurrent rows) for the fleet compliance view: applied vs latest published version (receivedlatest flag), install/skip/fail/filtered counts, status, check-in times.",
"auth": "jwt + permission:geenforce.manage",
"params": "query (optional): hostname (ILIKE match), scopename (exact)",
"example": "curl -H 'Authorization: Bearer $JWT' 'http://localhost:5001/api/geenforce/reports?scopename=cmm'"
"example": "curl -H \"Authorization: Bearer $JWT\" 'http://localhost:5001/api/geenforce/reports?scopename=cmm'"
},
{
"method": "GET",
@@ -2096,7 +2096,7 @@
"purpose": "One enforcement report in detail with per-entry outcomes (entryname, action, selfhealed, exitcode, message) plus applied-vs-latest version comparison.",
"auth": "jwt + permission:geenforce.manage",
"params": "path: reportid",
"example": "curl -H 'Authorization: Bearer $JWT' http://localhost:5001/api/geenforce/reports/42"
"example": "curl -H \"Authorization: Bearer $JWT\" http://localhost:5001/api/geenforce/reports/42"
}
]
},
@@ -2117,7 +2117,7 @@
"purpose": "Create a notification type incl. expiry rule and shopfloor display config",
"auth": "jwt + permission:notifications.create",
"params": "body: typename (required, unique), typedescription/description, typecolor/color, expirymode (none|duration|dailytime), expirydays, expiryhour, expiryminute, splitperemployee, showemployeephoto, displaystyle (standard|carousel|grid|banner)",
"example": "curl -X POST http://localhost:5001/api/notifications/types -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{\"typename\":\"Recognition\",\"typecolor\":\"recognition\",\"expirymode\":\"dailytime\",\"expiryhour\":8,\"splitperemployee\":true,\"showemployeephoto\":true}'"
"example": "curl -X POST http://localhost:5001/api/notifications/types -H \"Authorization: Bearer $TOKEN\" -H 'Content-Type: application/json' -d '{\"typename\":\"Recognition\",\"typecolor\":\"recognition\",\"expirymode\":\"dailytime\",\"expiryhour\":8,\"splitperemployee\":true,\"showemployeephoto\":true}'"
},
{
"method": "PUT, PATCH",
@@ -2125,7 +2125,7 @@
"purpose": "Update a notification type (name/desc/color/isactive plus expiry and display fields)",
"auth": "jwt + permission:notifications.create",
"params": "body: any of typename (unique-checked), typedescription/description, typecolor/color, isactive, expirymode, expirydays, expiryhour, expiryminute, splitperemployee, showemployeephoto, displaystyle",
"example": "curl -X PATCH http://localhost:5001/api/notifications/types/3 -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{\"expirymode\":\"duration\",\"expirydays\":14}'"
"example": "curl -X PATCH http://localhost:5001/api/notifications/types/3 -H \"Authorization: Bearer $TOKEN\" -H 'Content-Type: application/json' -d '{\"expirymode\":\"duration\",\"expirydays\":14}'"
},
{
"method": "GET",
@@ -2149,7 +2149,7 @@
"purpose": "Create a notification; endtime auto-derived from type expiry rule when omitted",
"auth": "jwt + permission:notifications.create",
"params": "body: notification/message (required), notificationtypeid, businessunitid, appid, starttime/startdate (ISO, default now), endtime/enddate (ISO), ticketnumber, link/linkurl, isshopfloor (default false), employeesso (comma-list allowed), employeename",
"example": "curl -X POST http://localhost:5001/api/notifications -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{\"notification\":\"Line 2 press down\",\"notificationtypeid\":1,\"isshopfloor\":true,\"ticketnumber\":\"INC0012345\"}'"
"example": "curl -X POST http://localhost:5001/api/notifications -H \"Authorization: Bearer $TOKEN\" -H 'Content-Type: application/json' -d '{\"notification\":\"Line 2 press down\",\"notificationtypeid\":1,\"isshopfloor\":true,\"ticketnumber\":\"INC0012345\"}'"
},
{
"method": "PUT",
@@ -2157,7 +2157,7 @@
"purpose": "Update any notification field; empty starttime resets to now, empty endtime clears it",
"auth": "jwt + permission:notifications.edit",
"params": "body: notification/message, notificationtypeid, businessunitid, appid, ticketnumber, link/linkurl, isactive, isshopfloor, employeesso, employeename, starttime/startdate, endtime/enddate",
"example": "curl -X PUT http://localhost:5001/api/notifications/42 -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{\"endtime\":\"2026-08-01T12:00:00Z\",\"isactive\":true}'"
"example": "curl -X PUT http://localhost:5001/api/notifications/42 -H \"Authorization: Bearer $TOKEN\" -H 'Content-Type: application/json' -d '{\"endtime\":\"2026-08-01T12:00:00Z\",\"isactive\":true}'"
},
{
"method": "DELETE",
@@ -2165,7 +2165,7 @@
"purpose": "Soft-delete a notification (sets isactive=false, row kept)",
"auth": "jwt + permission:notifications.delete",
"params": "path: notification_id",
"example": "curl -X DELETE http://localhost:5001/api/notifications/42 -H 'Authorization: Bearer $TOKEN'"
"example": "curl -X DELETE http://localhost:5001/api/notifications/42 -H \"Authorization: Bearer $TOKEN\""
},
{
"method": "GET",
@@ -2234,7 +2234,7 @@
"purpose": "Admin list of a surface's slides (TvSlide rows ordered by sortorder,slideid, filtered to files present on disk), each with a url field for the img route",
"auth": "jwt + permission:slides.manage",
"params": "path: surface (lobby|shopfloor; else VALIDATION_ERROR)",
"example": "curl -H 'Authorization: Bearer $TOKEN' 'http://localhost:5001/api/slides/lobby'"
"example": "curl -H \"Authorization: Bearer $TOKEN\" 'http://localhost:5001/api/slides/lobby'"
},
{
"method": "POST",
@@ -2242,7 +2242,7 @@
"purpose": "Upload one or more slide images; non-image extensions skipped, names secure_filename'd and unique-renamed (stem_N.ext) on collision, appended after current max sortorder in natural filename order, seconds=0 (surface default)",
"auth": "jwt + permission:slides.manage",
"params": "path: surface. multipart/form-data body: files (repeatable) or single file. Allowed extensions: .jpg .jpeg .png .gif .bmp .webp. Returns {added:[names]}",
"example": "curl -X POST -H 'Authorization: Bearer $TOKEN' -F 'files=@Slide1.png' -F 'files=@Slide2.png' 'http://localhost:5001/api/slides/lobby/upload'"
"example": "curl -X POST -H \"Authorization: Bearer $TOKEN\" -F 'files=@Slide1.png' -F 'files=@Slide2.png' 'http://localhost:5001/api/slides/lobby/upload'"
},
{
"method": "POST",
@@ -2250,7 +2250,7 @@
"purpose": "Save play order: each filename in the order array gets sortorder set to its index; unknown filenames silently ignored",
"auth": "jwt + permission:slides.manage",
"params": "path: surface. JSON body: {order: [filename, ...]}",
"example": "curl -X POST -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{\"order\":[\"Slide2.png\",\"Slide1.png\"]}' 'http://localhost:5001/api/slides/lobby/order'"
"example": "curl -X POST -H \"Authorization: Bearer $TOKEN\" -H 'Content-Type: application/json' -d '{\"order\":[\"Slide2.png\",\"Slide1.png\"]}' 'http://localhost:5001/api/slides/lobby/order'"
},
{
"method": "POST",
@@ -2258,7 +2258,7 @@
"purpose": "Delete named slides: removes file from disk (OSError swallowed) and the TvSlide row; filenames reduced to basename first; returns count of DB rows removed",
"auth": "jwt + permission:slides.manage",
"params": "path: surface. JSON body: {files: [filename, ...]}",
"example": "curl -X POST -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{\"files\":[\"Slide1.png\"]}' 'http://localhost:5001/api/slides/shopfloor/delete'"
"example": "curl -X POST -H \"Authorization: Bearer $TOKEN\" -H 'Content-Type: application/json' -d '{\"files\":[\"Slide1.png\"]}' 'http://localhost:5001/api/slides/shopfloor/delete'"
},
{
"method": "PATCH",
@@ -2266,7 +2266,7 @@
"purpose": "Update a single slide's per-slide duration; seconds clamped to >=0 (0 means use the 10s surface default); 404 if slide missing or belongs to a different surface",
"auth": "jwt + permission:slides.manage",
"params": "path: surface, slideid (int). JSON body: {seconds: int} (non-int -> VALIDATION_ERROR)",
"example": "curl -X PATCH -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{\"seconds\":15}' 'http://localhost:5001/api/slides/lobby/12'"
"example": "curl -X PATCH -H \"Authorization: Bearer $TOKEN\" -H 'Content-Type: application/json' -d '{\"seconds\":15}' 'http://localhost:5001/api/slides/lobby/12'"
}
]
},
@@ -2633,7 +2633,7 @@
"purpose": "List USB devices with checkout status (paginated); dual-mode: selfhosted tables or external cmmc_usb DB per usb_directory_mode setting",
"auth": "jwt-optional",
"params": "query: page, per_page, status=available|checkedout|retired, search (matches device_id or device_desc)",
"example": "curl 'http://localhost:5001/api/usb?status=available&search=kingston&page=1&per_page=25' -H 'Authorization: Bearer $JWT'"
"example": "curl 'http://localhost:5001/api/usb?status=available&search=kingston&page=1&per_page=25' -H \"Authorization: Bearer $JWT\""
},
{
"method": "GET",
@@ -2641,7 +2641,7 @@
"purpose": "Get one device plus its last 20 check-in/out log rows; 404 if unknown",
"auth": "jwt-optional",
"params": "path: device_id",
"example": "curl 'http://localhost:5001/api/usb/USB-0042' -H 'Authorization: Bearer $JWT'"
"example": "curl 'http://localhost:5001/api/usb/USB-0042' -H \"Authorization: Bearer $JWT\""
},
{
"method": "POST",
@@ -2649,7 +2649,7 @@
"purpose": "Create a device (starts in checked-in status); 409 on duplicate device_id",
"auth": "jwt + permission:usb.create",
"params": "body JSON: device_id (required), device_desc, device_owner (badge), locker_location",
"example": "curl -X POST 'http://localhost:5001/api/usb' -H 'Authorization: Bearer $JWT' -H 'Content-Type: application/json' -d '{\"device_id\":\"USB-0042\",\"device_desc\":\"Kingston 32GB\",\"device_owner\":\"212345678\",\"locker_location\":\"A3\"}'"
"example": "curl -X POST 'http://localhost:5001/api/usb' -H \"Authorization: Bearer $JWT\" -H 'Content-Type: application/json' -d '{\"device_id\":\"USB-0042\",\"device_desc\":\"Kingston 32GB\",\"device_owner\":\"212345678\",\"locker_location\":\"A3\"}'"
},
{
"method": "PUT",
@@ -2657,7 +2657,7 @@
"purpose": "Edit device fields (device_desc / device_owner / locker_location / status); 404 if unknown",
"auth": "jwt + permission:usb.edit",
"params": "path: device_id; body JSON: any of device_desc, device_owner, locker_location, status",
"example": "curl -X PUT 'http://localhost:5001/api/usb/USB-0042' -H 'Authorization: Bearer $JWT' -H 'Content-Type: application/json' -d '{\"device_desc\":\"Kingston 64GB\",\"locker_location\":\"B1\"}'"
"example": "curl -X PUT 'http://localhost:5001/api/usb/USB-0042' -H \"Authorization: Bearer $JWT\" -H 'Content-Type: application/json' -d '{\"device_desc\":\"Kingston 64GB\",\"locker_location\":\"B1\"}'"
},
{
"method": "POST",
@@ -2665,7 +2665,7 @@
"purpose": "Retire a device (sets status to retired); 404 if unknown",
"auth": "jwt + permission:usb.edit",
"params": "path: device_id; no body",
"example": "curl -X POST 'http://localhost:5001/api/usb/USB-0042/retire' -H 'Authorization: Bearer $JWT'"
"example": "curl -X POST 'http://localhost:5001/api/usb/USB-0042/retire' -H \"Authorization: Bearer $JWT\""
},
{
"method": "POST",
@@ -2673,7 +2673,7 @@
"purpose": "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",
"auth": "jwt + permission:usb.create",
"params": "path: device_id; body JSON: badge (required), locker_location (optional, also updates device)",
"example": "curl -X POST 'http://localhost:5001/api/usb/USB-0042/checkout' -H 'Authorization: Bearer $JWT' -H 'Content-Type: application/json' -d '{\"badge\":\"212345678\",\"locker_location\":\"A3\"}'"
"example": "curl -X POST 'http://localhost:5001/api/usb/USB-0042/checkout' -H \"Authorization: Bearer $JWT\" -H 'Content-Type: application/json' -d '{\"badge\":\"212345678\",\"locker_location\":\"A3\"}'"
},
{
"method": "POST",
@@ -2681,7 +2681,7 @@
"purpose": "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",
"auth": "jwt + permission:usb.create",
"params": "path: device_id; body JSON: badge (required), locker_location, sanitized (bool/1/0), scanned_viruses (bool/1/0)",
"example": "curl -X POST 'http://localhost:5001/api/usb/USB-0042/checkin' -H 'Authorization: Bearer $JWT' -H 'Content-Type: application/json' -d '{\"badge\":\"212345678\",\"sanitized\":true,\"scanned_viruses\":true}'"
"example": "curl -X POST 'http://localhost:5001/api/usb/USB-0042/checkin' -H \"Authorization: Bearer $JWT\" -H 'Content-Type: application/json' -d '{\"badge\":\"212345678\",\"sanitized\":true,\"scanned_viruses\":true}'"
},
{
"method": "GET",
@@ -2689,7 +2689,7 @@
"purpose": "Paginated check-in/out log for one device, newest first",
"auth": "jwt-optional",
"params": "path: device_id; query: page, per_page",
"example": "curl 'http://localhost:5001/api/usb/USB-0042/history?page=1&per_page=50' -H 'Authorization: Bearer $JWT'"
"example": "curl 'http://localhost:5001/api/usb/USB-0042/history?page=1&per_page=50' -H \"Authorization: Bearer $JWT\""
},
{
"method": "GET",
@@ -2697,7 +2697,7 @@
"purpose": "List check-out log rows (paginated), each with the device's current status joined in",
"auth": "jwt-optional",
"params": "query: page, per_page, active=true (only rows whose device is still checked out), badge (filter by badge_number)",
"example": "curl 'http://localhost:5001/api/usb/checkouts?active=true&badge=212345678' -H 'Authorization: Bearer $JWT'"
"example": "curl 'http://localhost:5001/api/usb/checkouts?active=true&badge=212345678' -H \"Authorization: Bearer $JWT\""
},
{
"method": "GET",
@@ -2705,7 +2705,7 @@
"purpose": "Latest check-out log row for every currently checked-out device (non-paginated list)",
"auth": "jwt-optional",
"params": "query: badge (filter by badge_number)",
"example": "curl 'http://localhost:5001/api/usb/checkouts/active?badge=212345678' -H 'Authorization: Bearer $JWT'"
"example": "curl 'http://localhost:5001/api/usb/checkouts/active?badge=212345678' -H \"Authorization: Bearer $JWT\""
}
]
},