diff --git a/docs/openapi.json b/docs/openapi.json index 4140895..7d3ae5f 100644 --- a/docs/openapi.json +++ b/docs/openapi.json @@ -12,6 +12,98 @@ } ], "components": { + "schemas": { + "SuccessEnvelope": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "success" + ] + }, + "data": { + "description": "The payload. Shape is per endpoint." + }, + "meta": { + "type": "object", + "properties": { + "timestamp": { + "type": "string", + "format": "date-time" + }, + "requestid": { + "type": "string" + }, + "pagination": { + "type": "object", + "properties": { + "page": { + "type": "integer" + }, + "perpage": { + "type": "integer" + }, + "total": { + "type": "integer" + }, + "pages": { + "type": "integer" + } + } + } + } + } + }, + "required": [ + "status" + ] + }, + "ErrorEnvelope": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "error" + ] + }, + "data": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "details": { + "type": "object" + } + }, + "required": [ + "code", + "message" + ] + } + }, + "required": [ + "error" + ] + }, + "meta": { + "type": "object" + } + }, + "required": [ + "status", + "data" + ] + } + }, "securitySchemes": { "bearerAuth": { "type": "http", @@ -83,12 +175,37 @@ "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", + "summary": "Authenticate and issue JWT access+refresh tokens; per-IP fixed-window rate limit (429) and 5-strike/15-min account...", "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: username, password (both required)" + } + } } } } @@ -107,7 +224,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "none; refresh token in Authorization header" + } + } } } } @@ -126,7 +274,26 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." } } } @@ -145,7 +312,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: new_password (min 8, required), current_password (required unless mustchangepassword is set)" + } + } } } } @@ -164,7 +362,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "none" + } + } } } } @@ -183,7 +412,26 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." } } }, @@ -200,7 +448,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: username, email, password (required); firstname, lastname, isactive, roles[roleids], mustchangepassword (default true), sendwelcome (default true)" + } + } } } } @@ -219,15 +498,47 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "userid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] }, "put": { "tags": [ "core-identity" ], - "summary": "Update a user; email uniqueness enforced; admins can also change active state, roles, and unlock the account; changes au", + "summary": "Update a user; email uniqueness enforced; admins can also change active state, roles, and unlock the account; changes...", "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": [ { @@ -236,7 +547,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "userid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "path: userid; body: email, firstname, lastname, password; admin-only: isactive, roles[roleids], unlock (bool)" + } + } } } }, @@ -253,9 +608,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "userid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/users/permissions": { @@ -272,7 +659,26 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." } } } @@ -291,7 +697,26 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." } } }, @@ -308,7 +733,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: rolename (required), description, color, permissions[names]" + } + } } } } @@ -327,7 +783,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "roleid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "path: roleid; body: description, color, permissions[names] (permissions immutable on the admin role)" + } + } } } }, @@ -344,9 +844,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "roleid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/apitokens": { @@ -363,7 +895,26 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." } } }, @@ -380,7 +931,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: name (required), expiresat (date/datetime), scopes[permission names, ceiling = owner's permissions], resourcescopes[resource names, plugin-defined]" + } + } } } } @@ -399,7 +981,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "tokenid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "path: tokenid; body: name, isactive (bool), scopes[names, validated against the token OWNER's permissions], resourcescopes[names]" + } + } } } }, @@ -416,9 +1042,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "tokenid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/setup/needs-admin": { @@ -431,7 +1089,20 @@ "security": [], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -443,14 +1114,35 @@ ], "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": [] - } - ], + "security": [], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: username, email, password (all required)" + } + } } } } @@ -469,7 +1161,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "none" + } + } } } } @@ -488,7 +1211,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "none" + } + } } } } @@ -507,7 +1261,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "none" + } + } } } } @@ -517,16 +1302,30 @@ "tags": [ "core-identity" ], - "summary": "List settings; unauthenticated callers see only the public allowlist (branding+map categories plus named bootstrap keys)", + "summary": "List settings; unauthenticated callers see only the public allowlist (branding+map categories plus named bootstrap...", "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } }, @@ -543,7 +1342,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: key (required), value, valuetype (default string), category (default general), description" + } + } } } } @@ -556,21 +1386,48 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "key", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] }, "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", + "summary": "Update a setting (upserts a plugin-scoped string row if the key is new); audit-logged with secrets masked; invalidates...", "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": [ { @@ -579,7 +1436,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "key", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "path: key; body: value (required; bool coerced to 'true'/'false'; secret mask ******** means leave unchanged)" + } + } } } } @@ -598,7 +1499,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "none" + } + } } } } @@ -617,7 +1549,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: to (optional; falls back to alert_recipients setting)" + } + } } } } @@ -636,7 +1599,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "multipart/form-data: file (png/jpg/jpeg/gif/webp/svg), theme=light|dark" + } + } } } } @@ -651,9 +1645,36 @@ "security": [], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "filename", + "in": "path", + "required": true, + "schema": { + "type": "string", + "description": "may contain slashes" + } + } + ] } }, "/api/settings/branding-logo": { @@ -661,7 +1682,7 @@ "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", + "summary": "Upload a branding logo/favicon to the instance branding dir and set the matching branding setting...", "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": [ { @@ -670,7 +1691,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "multipart/form-data: file (map image types plus .ico), kind=site|qr|badge|favicon" + } + } } } } @@ -685,9 +1737,36 @@ "security": [], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "filename", + "in": "path", + "required": true, + "schema": { + "type": "string", + "description": "may contain slashes" + } + } + ] } }, "/api/reports": { @@ -698,13 +1777,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -714,16 +1807,30 @@ "tags": [ "core-platform" ], - "summary": "Machine count grouped by machine type (dualpath secondary bays collapsed when the site setting is on); 404 if machines p", + "summary": "Machine count grouped by machine type (dualpath secondary bays collapsed when the site setting is on); 404 if machines...", "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -736,13 +1843,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -755,13 +1876,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -771,16 +1906,30 @@ "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", + "summary": "Required applications vs installed per PC with compliance rate and up to 100 non-compliant PCs per app; 503 if...", "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -793,13 +1942,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -809,16 +1972,30 @@ "tags": [ "core-platform" ], - "summary": "PC-to-shop-floor-machine relationships (machine number, vendor, model, hostname, IP) matched in both edge directions via", + "summary": "PC-to-shop-floor-machine relationships (machine number, vendor, model, hostname, IP) matched in both edge directions...", "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -828,7 +2005,7 @@ "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", + "summary": "Email a report's rows as an HTML table on demand; recipient defaults to the site Alert Recipients setting; intended...", "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": [ { @@ -837,7 +2014,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: subject, columns [{key,label}], rows [{..}], intro (optional), to (optional email)" + } + } } } } @@ -847,16 +2055,30 @@ "tags": [ "core-platform" ], - "summary": "Global search across assets, applications, KB, employees, notifications, custom fields, hostnames, IPs/subnets, vendor/m", + "summary": "Global search across assets, applications, KB, employees, notifications, custom fields, hostnames, IPs/subnets...", "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__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=WKSTN0042'\n```", "security": [ + {}, { "bearerAuth": [] } ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -866,16 +2088,30 @@ "tags": [ "core-platform" ], - "summary": "Dashboard summary: asset counts by type (machines/PCs/network/printers/measuring tools, dualpath-collapsed), counts by s", + "summary": "Dashboard summary: asset counts by type (machines/PCs/network/printers/measuring tools, dualpath-collapsed), counts by...", "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -888,13 +2124,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -907,13 +2157,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -928,7 +2192,20 @@ "security": [], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -941,13 +2218,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -962,7 +2253,20 @@ "security": [], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -972,12 +2276,25 @@ "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 ", + "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.example.net'\n```", "security": [], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -987,12 +2304,25 @@ "tags": [ "core-platform" ], - "summary": "Resolve what a display PC should show at boot: role (dashboard/lobby/partskiosk), frontend path, and business unit; null", + "summary": "Resolve what a display PC should show at boot: role (dashboard/lobby/partskiosk), frontend path, and business unit...", "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -1005,13 +2335,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } }, @@ -1019,7 +2363,7 @@ "tags": [ "core-platform" ], - "summary": "Create a display mapping; requires fqdn or ipaddress, dashboard role requires businessunitid, 409 on duplicate fqdn/IP; ", + "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": [ { @@ -1028,7 +2372,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: fqdn, ipaddress, displayrole (dashboard|lobby|partskiosk, default dashboard), businessunitid, description" + } + } } } } @@ -1047,7 +2422,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "default_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: any of fqdn, ipaddress, displayrole, businessunitid, description" + } + } } } }, @@ -1064,9 +2483,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "default_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] } }, "/api/collector/{pluginname}": { @@ -1074,7 +2525,7 @@ "tags": [ "core-platform" ], - "summary": "Generic collector ingest (ADR-006): schema-validated identity field, idempotent upsert via the plugin's apply_collector_", + "summary": "Generic collector ingest (ADR-006): schema-validated identity field, idempotent upsert via the plugin's...", "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_ 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\":\"WKSTN0042\",\"serialnumber\":\"ABC123\"}'\n```", "security": [ { @@ -1083,7 +2534,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "pluginname", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: JSON payload whose schema identityfield (e.g. hostname) is required; rest is plugin-defined" + } + } } } } @@ -1102,7 +2597,26 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." } } } @@ -1112,7 +2626,7 @@ "tags": [ "core-platform" ], - "summary": "Legacy computers-specific ingest: update one PC matched by hostname (or asset number) - lastreporteddate, lastboottime, ", + "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\":\"WKSTN0042\",\"currentuser\":\"212345678\"}'\n```", "security": [ { @@ -1121,7 +2635,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: hostname (required), lastboottime (ISO), currentuser, serialnumber" + } + } } } } @@ -1131,7 +2676,7 @@ "tags": [ "core-platform" ], - "summary": "Update installed applications for one PC; only apps already in the Application table are tracked, others skipped; return", + "summary": "Update installed applications for one PC; only apps already in the Application table are tracked, others skipped...", "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\":\"WKSTN0042\",\"apps\":[{\"appname\":\"PC-DMIS\",\"version\":\"2023.2\"}]}'\n```", "security": [ { @@ -1140,7 +2685,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: hostname (required), apps [{appname, version}] (required)" + } + } } } } @@ -1159,7 +2735,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: hostname (string) or hostnames (array)" + } + } } } } @@ -1169,7 +2776,7 @@ "tags": [ "core-platform" ], - "summary": "Bulk update many PCs in one call (lastreporteddate, currentuser, lastboottime per entry); returns updated/notfound/error", + "summary": "Bulk update many PCs in one call (lastreporteddate, currentuser, lastboottime per entry); returns...", "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": [ { @@ -1178,7 +2785,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: pcs [{hostname (required), currentuser, lastboottime}]" + } + } } } } @@ -1197,7 +2835,26 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." } } } @@ -1216,7 +2873,26 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." } } } @@ -1235,9 +2911,49 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "entitytype", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "entityid", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] } }, "/api/auditlogs/stats": { @@ -1254,7 +2970,26 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." } } } @@ -1267,13 +3002,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -1283,16 +3032,30 @@ "tags": [ "core-platform" ], - "summary": "Flat sorted array of enabled plugin names (registry read, no DB); deliberately anonymous so kiosk routes can gate plugin", + "summary": "Flat sorted array of enabled plugin names (registry read, no DB); deliberately anonymous so kiosk routes can gate...", "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -1302,7 +3065,7 @@ "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", + "summary": "Enable or disable a plugin (route changes need an app restart); 409 when unknown or a dependency conflict refuses the...", "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": [ { @@ -1311,7 +3074,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: enabled (bool, required)" + } + } } } } @@ -1321,16 +3128,30 @@ "tags": [ "core-platform" ], - "summary": "Merge enabled plugins' settings-catalog cards (get_settings_cards hook) sorted by position for the settings rail/overvie", + "summary": "Merge enabled plugins' settings-catalog cards (get_settings_cards hook) sorted by position for the settings...", "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -1340,16 +3161,30 @@ "tags": [ "core-platform" ], - "summary": "Asset-detail panels from enabled plugins (get_asset_panels hook) matching one asset's type ('*' wildcard supported), sor", + "summary": "Asset-detail panels from enabled plugins (get_asset_panels hook) matching one asset's type ('*' wildcard supported)...", "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -1362,13 +3197,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -1378,16 +3227,30 @@ "tags": [ "core-platform" ], - "summary": "Merge enabled plugins' asset-type presentation entries (icon + detail route per type) used by search rows and cross-link", + "summary": "Merge enabled plugins' asset-type presentation entries (icon + detail route per type) used by search rows and...", "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -1397,12 +3260,25 @@ "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", + "summary": "Search employees by first name, last name, or SSO substring (self-hosted table or external HR DB depending on...", "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -1417,9 +3293,35 @@ "security": [], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "sso", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/employees/lookup": { @@ -1432,7 +3334,20 @@ "security": [], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -1445,13 +3360,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } }, @@ -1468,7 +3397,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "JSON body: sso (numeric, required), firstname, lastname (required), team, role, picture (also accepts external-style keys SSO/First_Name/Last_Name/Team/Role/Picture)" + } + } } } } @@ -1487,7 +3447,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "sso", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "sso (path); JSON body: firstname, lastname, team, role, picture (external-style keys also accepted; team/role/picture can be cleared)" + } + } } } }, @@ -1504,9 +3508,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "sso", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] } }, "/api/employees/directory/import": { @@ -1514,7 +3550,7 @@ "tags": [ "plugin-employees" ], - "summary": "Bulk upsert the self-hosted directory from CSV (headers SSO,First_Name,Last_Name,Team,Role,Picture case-insensitive; pla", + "summary": "Bulk upsert the self-hosted directory from CSV (headers SSO,First_Name,Last_Name,Team,Role,Picture case-insensitive...", "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= 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": [ { @@ -1523,7 +3559,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "multipart file= OR JSON body {\"csv\": \"...\"}; rows missing numeric sso or names are skipped" + } + } } } } @@ -1533,7 +3600,7 @@ "tags": [ "plugin-employees" ], - "summary": "Upload or replace a self-hosted employee's photo (saved as photo- in instance employeephotos dir; old file wip", + "summary": "Upload or replace a self-hosted employee's photo (saved as photo- in instance employeephotos dir; old file...", "description": "Upload or replace a self-hosted employee's photo (saved as photo- in instance employeephotos dir; old file wiped even on extension change); 409 in external mode, 404 if employee missing.\n\n**Auth:** jwt + require_role admin\n\n**Params:** sso (path); multipart/form-data file=, 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": [ { @@ -1542,7 +3609,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "sso", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "sso (path); multipart/form-data file=, extensions .png/.jpg/.jpeg/.gif/.webp only" + } + } } } }, @@ -1559,9 +3670,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "sso", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] } }, "/api/employees/photo/{filename}": { @@ -1569,14 +3712,41 @@ "tags": [ "plugin-employees" ], - "summary": "Serve an uploaded employee photo file from the instance employeephotos dir (public so kiosk recognition cards can read i", + "summary": "Serve an uploaded employee photo file from the instance employeephotos dir (public so kiosk recognition cards can read...", "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "filename", + "in": "path", + "required": true, + "schema": { + "type": "string", + "description": "may contain slashes" + } + } + ] } }, "/api/knowledgebase": { @@ -1584,16 +3754,30 @@ "tags": [ "plugin-knowledgebase" ], - "summary": "List active KB articles with search, topic filter, natural-key lookup, sorting, and pagination; each item includes its l", + "summary": "List active KB articles with search, topic filter, natural-key lookup, sorting, and pagination; each item includes its...", "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } }, @@ -1601,7 +3785,7 @@ "tags": [ "plugin-knowledgebase" ], - "summary": "Create a new KB article; validates required fields and that appid (if given) exists; honors X-Import-Mode timestamp pres", + "summary": "Create a new KB article; validates required fields and that appid (if given) exists; honors X-Import-Mode timestamp...", "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": [ { @@ -1610,7 +3794,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body JSON: shortdescription (required), linkurl (required), appid (optional int, must exist), keywords (optional); import-mode may pass timestamp fields" + } + } } } } @@ -1623,13 +3838,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -1642,21 +3871,48 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "link_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] }, "put": { "tags": [ "plugin-knowledgebase" ], - "summary": "Update an article's shortdescription, linkurl, appid, keywords, and/or isactive; validates appid if changed; honors impo", + "summary": "Update an article's shortdescription, linkurl, appid, keywords, and/or isactive; validates appid if changed; honors...", "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": [ { @@ -1665,7 +3921,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "link_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "path: link_id (int); body JSON: any of shortdescription, linkurl, appid, keywords, isactive" + } + } } } }, @@ -1682,9 +3982,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "link_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] } }, "/api/knowledgebase/{link_id}/click": { @@ -1692,16 +4024,55 @@ "tags": [ "plugin-knowledgebase" ], - "summary": "Increment the article's click counter and return the target linkurl plus new click count (used for redirect tracking); 4", + "summary": "Increment the article's click counter and return the target linkurl plus new click count (used for redirect tracking)...", "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "link_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "path: link_id (int); no body" + } + } } } } @@ -1714,13 +4085,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } }, @@ -1737,7 +4122,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: assettype (req), pluginname, tablename, description, icon, color" + } + } } } } @@ -1750,15 +4166,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "type_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] }, "put": { "tags": [ @@ -1773,7 +4216,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "type_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: description, icon, color, isactive" + } + } } } } @@ -1786,13 +4273,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } }, @@ -1809,7 +4310,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: status (req), description, color" + } + } } } } @@ -1822,15 +4354,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "status_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] }, "put": { "tags": [ @@ -1845,7 +4404,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "status_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: status, description, color, isactive" + } + } } } }, @@ -1862,9 +4465,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "status_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/assets/relationshiptypes": { @@ -1875,13 +4510,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } }, @@ -1898,7 +4547,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: relationshiptype (req), description, color, isdirectional (default true)" + } + } } } } @@ -1917,7 +4597,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "type_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: relationshiptype, description, color, isdirectional" + } + } } } }, @@ -1934,9 +4658,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "type_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/assets": { @@ -1947,13 +4703,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } }, @@ -1970,7 +4740,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: assetnumber (req), assettypeid (req), name, serialnumber, statusid (default 1), locationid, businessunitid, mapx, mapy, notes" + } + } } } } @@ -1983,15 +4784,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "asset_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] }, "put": { "tags": [ @@ -2006,7 +4834,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "asset_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: assetnumber, name, serialnumber, assettypeid, statusid, locationid, businessunitid, mapx, mapy, notes, isactive" + } + } } } }, @@ -2023,9 +4895,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "asset_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/assets/lookup/{assetnumber}": { @@ -2036,15 +4940,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "assetnumber", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/assets/{asset_id}/relationships": { @@ -2055,15 +4986,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "asset_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/assets/relationships": { @@ -2071,7 +5029,7 @@ "tags": [ "core-catalog" ], - "summary": "Create relationship, then fan out across symmetric propagation rails (Dualpath); response carries propagated + propagate", + "summary": "Create relationship, then fan out across symmetric propagation rails (Dualpath); response carries propagated...", "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": [ { @@ -2080,7 +5038,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: sourceassetid (req), targetassetid (req), relationshiptypeid (req), notes; X-Import-Mode honored" + } + } } } } @@ -2099,9 +5088,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "rel_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/assets/map": { @@ -2112,13 +5133,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -2131,15 +5166,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "asset_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/locations/types": { @@ -2150,13 +5212,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } }, @@ -2173,7 +5249,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: locationtype (req), description, color" + } + } } } } @@ -2192,7 +5299,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "type_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: locationtype, description, color, isactive" + } + } } } }, @@ -2209,9 +5360,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "type_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/locations": { @@ -2222,13 +5405,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } }, @@ -2245,7 +5442,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: locationname (req), building, floor, room, description, locationtypeid, parentlocationid, mapimage, mapwidth, mapheight" + } + } } } } @@ -2258,15 +5486,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "location_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] }, "put": { "tags": [ @@ -2281,7 +5536,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "location_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: locationname, building, floor, room, description, locationtypeid, parentlocationid, mapimage, mapwidth, mapheight, isactive" + } + } } } }, @@ -2298,9 +5597,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "location_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/vendors": { @@ -2311,13 +5642,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } }, @@ -2334,7 +5679,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: vendor (req), description, website, supportphone, supportemail, notes" + } + } } } } @@ -2347,15 +5723,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "vendor_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] }, "put": { "tags": [ @@ -2370,7 +5773,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "vendor_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: vendor, description, website, supportphone, supportemail, notes, isactive" + } + } } } }, @@ -2387,9 +5834,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "vendor_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/models": { @@ -2400,13 +5879,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } }, @@ -2423,7 +5916,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: modelnumber (req), vendorid, modeltypeid, description, imageurl, documentationurl, notes" + } + } } } } @@ -2436,15 +5960,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "model_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] }, "put": { "tags": [ @@ -2459,7 +6010,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "model_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: modelnumber, vendorid, modeltypeid, description, imageurl, documentationurl, notes, isactive" + } + } } } }, @@ -2476,9 +6071,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "model_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/models/{model_id}/image": { @@ -2495,7 +6122,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "model_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "multipart/form-data: file= (.png/.jpg/.jpeg/.gif/.webp/.svg)" + } + } } } }, @@ -2512,9 +6183,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "model_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/models/image/{filename}": { @@ -2527,9 +6230,35 @@ "security": [], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "filename", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/modeltypes": { @@ -2540,13 +6269,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } }, @@ -2563,7 +6306,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: modeltype (req), category, description, icon" + } + } } } } @@ -2576,15 +6350,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "type_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] }, "put": { "tags": [ @@ -2599,7 +6400,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "type_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: modeltype, category, description, icon, isactive" + } + } } } }, @@ -2616,9 +6461,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "type_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/businessunits": { @@ -2629,13 +6506,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } }, @@ -2652,7 +6543,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: businessunit (req), code, description, parentid" + } + } } } } @@ -2665,15 +6587,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "bu_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] }, "put": { "tags": [ @@ -2688,7 +6637,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "bu_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: businessunit, code, description, parentid, isactive" + } + } } } }, @@ -2705,9 +6698,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "bu_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/operatingsystems": { @@ -2718,13 +6743,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } }, @@ -2741,7 +6780,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: osname (req), osversion, architecture, endoflife" + } + } } } } @@ -2754,15 +6824,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "os_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] }, "put": { "tags": [ @@ -2777,7 +6874,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "os_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: osname, osversion, architecture, endoflife, isactive" + } + } } } }, @@ -2794,9 +6935,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "os_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/customfields": { @@ -2807,13 +6980,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } }, @@ -2830,7 +7017,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: assettypeid (req), label (req), datatype (one of CUSTOM_FIELD_DATATYPES, default text), fieldkey, options (list or newline/comma text), showondetail, showonform, searchable, sortorder" + } + } } } } @@ -2849,7 +7067,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "fieldid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: label, datatype, options, showondetail, showonform, isactive, searchable, sortorder" + } + } } } }, @@ -2866,9 +7128,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "fieldid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/customfields/asset/{assetid}": { @@ -2879,15 +7173,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "assetid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] }, "put": { "tags": [ @@ -2902,7 +7223,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "assetid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: {values: {fieldid: value, ...}}" + } + } } } } @@ -2915,13 +7280,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } }, @@ -2938,7 +7317,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: teamname (req), teamurl, webhookurl, isactive" + } + } } } } @@ -2951,15 +7361,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "team_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] }, "put": { "tags": [ @@ -2974,7 +7411,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "team_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: teamname, teamurl, webhookurl, isactive" + } + } } } }, @@ -2991,9 +7472,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "team_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/supportteams/{team_id}/contacts": { @@ -3010,7 +7523,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "team_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: name (req), sso, sortorder, isactive" + } + } } } } @@ -3029,7 +7586,59 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "team_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "contact_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: name, sso, sortorder, isactive" + } + } } } }, @@ -3046,9 +7655,49 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "team_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "contact_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/applications": { @@ -3056,16 +7705,30 @@ "tags": [ "core-catalog" ], - "summary": "List applications (paginated) with installedcount; hidden apps excluded unless showhidden=true; exact appname lookup for", + "summary": "List applications (paginated) with installedcount; hidden apps excluded unless showhidden=true; exact appname lookup...", "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } }, @@ -3082,7 +7745,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: appname (req), appdescription, supportteamid, isinstallable, applicationnotes, installpath, applicationlink, documentationpath, ishidden, isprinter, islicenced, isrequired, image" + } + } } } } @@ -3095,15 +7789,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] }, "put": { "tags": [ @@ -3118,7 +7839,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: any of appname, appdescription, supportteamid, isinstallable, applicationnotes, installpath, applicationlink, documentationpath, ishidden, isprinter, islicenced, isrequired, image, isactive" + } + } } } }, @@ -3135,9 +7900,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/applications/{app_id}/versions": { @@ -3148,15 +7945,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] }, "post": { "tags": [ @@ -3171,7 +7995,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: version (req), releasedate, notes" + } + } } } } @@ -3184,15 +8052,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/applications/machines/{machine_id}": { @@ -3203,15 +8098,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "machine_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] }, "post": { "tags": [ @@ -3226,7 +8148,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "machine_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: appid (req), appversionid" + } + } } } } @@ -3245,9 +8211,49 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "machine_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] }, "put": { "tags": [ @@ -3262,7 +8268,59 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "machine_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: appversionid" + } + } } } } @@ -3281,7 +8339,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "multipart/form-data: file= (png/jpg/gif/webp/svg/ico)" + } + } } } }, @@ -3298,9 +8400,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/applications/image/{filename}": { @@ -3313,9 +8447,35 @@ "security": [], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "filename", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/applications/{app_id}/package": { @@ -3332,7 +8492,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "multipart/form-data: file= (exe/msi/msp/zip/7z/cab/iso/appx/msix/ps1/bat/txt/pdf), max 500MB" + } + } } } }, @@ -3349,9 +8553,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/applications/package/{filename}": { @@ -3368,9 +8604,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "filename", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/computers/types": { @@ -3381,13 +8649,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } }, @@ -3404,7 +8686,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: computertype (required), description, icon, color; matching a deactivated type revives it instead of 409" + } + } } } } @@ -3417,15 +8730,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "type_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] }, "put": { "tags": [ @@ -3440,7 +8780,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "type_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: computertype, description, icon, color, isactive; 409 on duplicate name" + } + } } } }, @@ -3457,9 +8841,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "type_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] } }, "/api/computers/protocols": { @@ -3470,13 +8886,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } }, @@ -3493,7 +8923,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: name, scheme, linktemplate (all required), defaultport, isactive; 409 on duplicate name" + } + } } } } @@ -3512,9 +8973,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "protocol_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] } }, "/api/computers/display-kiosks": { @@ -3525,13 +9018,27 @@ "summary": "List display-kiosk computers with derived F. FQDN for the Dashboard Defaults picker", "description": "List display-kiosk computers with derived F. 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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -3544,13 +9051,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } }, @@ -3567,7 +9088,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "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" + } + } } } } @@ -3580,15 +9132,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "computer_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] }, "put": { "tags": [ @@ -3603,7 +9182,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "computer_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "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" + } + } } } }, @@ -3620,9 +9243,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "computer_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] } }, "/api/computers/by-asset/{asset_id}": { @@ -3633,15 +9288,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "asset_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] } }, "/api/computers/by-hostname/{hostname}": { @@ -3652,15 +9334,42 @@ "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/WKSTN0042'\n```", "security": [ + {}, { "bearerAuth": [] } ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "hostname", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/computers/{computer_id}/apps": { @@ -3671,15 +9380,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "computer_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] }, "post": { "tags": [ @@ -3694,7 +9430,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "computer_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: appid (required, must exist in Applications), appversionid; reactivates a soft-deleted install; 409 if already installed" + } + } } } } @@ -3713,9 +9493,49 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "computer_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "app_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] } }, "/api/computers/{computer_id}/report": { @@ -3732,7 +9552,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "computer_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body (all optional): loggedinuser, lastboottime; server sets lastreporteddate to now (UTC)" + } + } } } } @@ -3745,13 +9609,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -3764,13 +9642,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } }, @@ -3787,7 +9679,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body JSON: name (required), description, color" + } + } } } } @@ -3800,15 +9723,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "type_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] }, "put": { "tags": [ @@ -3823,7 +9773,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "type_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "path: type_id; body JSON: name, description, color, isactive (only keys present are applied)" + } + } } } }, @@ -3840,9 +9834,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "type_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/measuringtools": { @@ -3850,16 +9876,30 @@ "tags": [ "plugin-measuringtools" ], - "summary": "List measuring tools (Asset core merged with extension), filtered + paginated; derived calibrationstatus filter applied ", + "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } }, @@ -3867,7 +9907,7 @@ "tags": [ "plugin-measuringtools" ], - "summary": "Create a measuring tool: one Asset core row (assettype 'measuring_tool') plus one measuringtools extension row in a sing", + "summary": "Create a measuring tool: one Asset core row (assettype 'measuring_tool') plus one measuringtools extension row in a...", "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": [ { @@ -3876,7 +9916,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "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" + } + } } } } @@ -3889,15 +9960,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "tool_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] }, "put": { "tags": [ @@ -3912,7 +10010,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "tool_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "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)" + } + } } } }, @@ -3929,9 +10071,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "tool_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/measuringtools/by-asset/{asset_id}": { @@ -3942,15 +10116,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "asset_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/measuringtools/map-overlay": { @@ -3958,16 +10159,30 @@ "tags": [ "plugin-measuringtools" ], - "summary": "ADR-010 map overlay: per-asset calibration badges [{assetid, color, label}] for active tools that are overdue or due soo", + "summary": "ADR-010 map overlay: per-asset calibration badges [{assetid, color, label}] for active tools that are overdue or due...", "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -3977,16 +10192,30 @@ "tags": [ "plugin-measuringtools" ], - "summary": "Calibration report for the Reports hub: counts and full tool lists bucketed by derived status (overdue/duesoon/current/u", + "summary": "Calibration report for the Reports hub: counts and full tool lists bucketed by derived status...", "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -3999,13 +10228,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } }, @@ -4022,7 +10265,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: machinetype (required), description, icon, color" + } + } } } } @@ -4035,15 +10309,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "type_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] }, "put": { "tags": [ @@ -4058,7 +10359,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "type_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "path: type_id; body: any of machinetype, description, icon, color, isactive" + } + } } } }, @@ -4075,9 +10420,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "type_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/machines": { @@ -4085,16 +10462,30 @@ "tags": [ "plugin-machines" ], - "summary": "List machines (Asset+Machine join) with filters, sorting, pagination; collapses Dualpath dual-bay pairs to one row (anno", + "summary": "List machines (Asset+Machine join) with filters, sorting, pagination; collapses Dualpath dual-bay pairs to one row...", "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } }, @@ -4102,7 +10493,7 @@ "tags": [ "plugin-machines" ], - "summary": "Create a machine (creates both core Asset row and Machine extension row); audit-logged; honors X-Import-Mode legacy time", + "summary": "Create a machine (creates both core Asset row and Machine extension row); audit-logged; honors X-Import-Mode legacy...", "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": [ { @@ -4111,7 +10502,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "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" + } + } } } } @@ -4124,15 +10546,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "machine_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] }, "put": { "tags": [ @@ -4147,7 +10596,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "machine_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "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" + } + } } } }, @@ -4164,9 +10657,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "machine_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/machines/by-asset/{asset_id}": { @@ -4177,15 +10702,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "asset_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/machines/dashboard/summary": { @@ -4193,16 +10745,30 @@ "tags": [ "plugin-machines" ], - "summary": "Dashboard summary: total active machine count plus counts grouped by machine type and by asset status (total and by-type", + "summary": "Dashboard summary: total active machine count plus counts grouped by machine type and by asset status (total and...", "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -4212,7 +10778,7 @@ "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", + "summary": "Serve the current PUBLISHED manifest JSON snapshot for a scope (never the live draft) to the GE-Enforce client, with...", "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": [ { @@ -4221,7 +10787,26 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." } } } @@ -4231,7 +10816,7 @@ "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", + "summary": "Download a payload blob by content hash (blob store first, then inline DB payload) so share-less PCs can pull...", "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": [ { @@ -4240,9 +10825,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "sha256", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/geenforce/report": { @@ -4250,7 +10867,7 @@ "tags": [ "plugin-geenforce" ], - "summary": "Record one PC's enforcement cycle: applied manifest version plus per-entry self-heal outcomes (installed/skipped/failed)", + "summary": "Record one PC's enforcement cycle: applied manifest version plus per-entry self-heal outcomes...", "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\":\"WKSTN0042\",\"scopename\":\"cmm\",\"appliedversion\":4,\"results\":[{\"entryname\":\"7zip\",\"action\":\"installed\"}]}' http://localhost:5001/api/geenforce/report\n```", "security": [ { @@ -4259,7 +10876,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "JSON body: hostname (required); remainder parsed by service.record_enforcement_report (scopename, phase, appliedversion, enforcerversion, status, per-entry results, counts); 400 on ValueError" + } + } } } } @@ -4269,7 +10917,7 @@ "tags": [ "plugin-geenforce" ], - "summary": "List all imaging PC-type scopes with entry counts and current published version numbers, ordered by phase then scopename", + "summary": "List all imaging PC-type scopes with entry counts and current published version numbers, ordered by phase then...", "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": [ { @@ -4278,7 +10926,26 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." } } }, @@ -4286,7 +10953,7 @@ "tags": [ "plugin-geenforce" ], - "summary": "Create a new manifest scope; 400 if scopename missing, phase invalid, or scope already exists for that scopename+phase; ", + "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": [ { @@ -4295,7 +10962,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "JSON body: scopename (required), phase (default 'runtime', must be in PHASES), manifestversion (default '1.0'), description, computertypeid, measuringtooltypeid, iscommon (defaults true when scopename=='common')" + } + } } } } @@ -4305,7 +11003,7 @@ "tags": [ "plugin-geenforce" ], - "summary": "Get one scope's summary plus its full draft entry list (each entry includes entryid, sortorder, curated appid/appname li", + "summary": "Get one scope's summary plus its full draft entry list (each entry includes entryid, sortorder, curated appid/appname...", "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": [ { @@ -4314,9 +11012,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "scopeid", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] }, "put": { "tags": [ @@ -4331,7 +11061,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "scopeid", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "JSON body (all optional): description, computertypeid, measuringtooltypeid, manifestversion (stringified), iscommon (bool-coerced)" + } + } } } }, @@ -4348,9 +11122,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "scopeid", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] } }, "/api/geenforce/scopes/{scopeid}/preview": { @@ -4367,9 +11173,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "scopeid", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] } }, "/api/geenforce/applications": { @@ -4377,7 +11215,7 @@ "tags": [ "plugin-geenforce" ], - "summary": "List the core active Applications catalog (appid + appname) for the curated entry-to-app link picker in the entry editor", + "summary": "List the core active Applications catalog (appid + appname) for the curated entry-to-app link picker in the entry...", "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": [ { @@ -4386,7 +11224,26 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." } } } @@ -4396,7 +11253,7 @@ "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", + "summary": "Create a manifest entry in a scope at the next sortorder; validates Name (required) and Type (must be in ENTRY_TYPES)...", "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": [ { @@ -4405,7 +11262,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "scopeid", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "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.)" + } + } } } } @@ -4415,7 +11316,7 @@ "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", + "summary": "Replace an entry's fields from the payload (re-populates via populate_entry, re-creating the one-to-one InUseCheck)...", "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": [ { @@ -4424,7 +11325,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "entryid", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "path: entryid; JSON body: Name (required), Type (required), optional appid, plus manifest fields" + } + } } } }, @@ -4441,9 +11386,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "entryid", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] } }, "/api/geenforce/scopes/{scopeid}/entries/reorder": { @@ -4460,7 +11437,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "scopeid", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "path: scopeid; JSON body: order = [entryid, ...] (must match the scope's entry ids exactly)" + } + } } } } @@ -4470,7 +11491,7 @@ "tags": [ "plugin-geenforce" ], - "summary": "Simulate which draft entries would apply to a given machine profile and why the rest are filtered out (PCTypes/TargetHos", + "summary": "Simulate which draft entries would apply to a given machine profile and why the rest are filtered out...", "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=WKSTN0042&cmmversion=2023.2'\n```", "security": [ { @@ -4479,9 +11500,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "scopeid", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] } }, "/api/geenforce/scopes/{scopeid}/compliance": { @@ -4489,7 +11542,7 @@ "tags": [ "plugin-geenforce" ], - "summary": "Fleet install coverage per app-linked entry (installed/version-match counts from the computers plugin's ComputerInstalle", + "summary": "Fleet install coverage per app-linked entry (installed/version-match counts from the computers plugin's...", "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": [ { @@ -4498,9 +11551,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "scopeid", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] } }, "/api/geenforce/entries/{entryid}/payload": { @@ -4508,7 +11593,7 @@ "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,", + "summary": "Upload an inline payload file (max 1 MB, rejects empty) for an entry and point the entry at it (stores sha256...", "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": [ { @@ -4517,7 +11602,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "entryid", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "path: entryid; multipart/form-data: file (required)" + } + } } } }, @@ -4534,9 +11663,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "entryid", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] } }, "/api/geenforce/scopes/{scopeid}/publish": { @@ -4544,7 +11705,7 @@ "tags": [ "plugin-geenforce" ], - "summary": "Freeze the scope's draft into a new published version (service.publish_scope), recording the publishing user from the JW", + "summary": "Freeze the scope's draft into a new published version (service.publish_scope), recording the publishing user from the...", "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": [ { @@ -4553,7 +11714,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "scopeid", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "path: scopeid; JSON body (optional): notes" + } + } } } } @@ -4572,9 +11777,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "scopeid", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] } }, "/api/geenforce/scopes/{scopeid}/versions/{versionnumber}": { @@ -4591,9 +11828,49 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "scopeid", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "versionnumber", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] } }, "/api/geenforce/scopes/{scopeid}/rollback": { @@ -4601,7 +11878,7 @@ "tags": [ "plugin-geenforce" ], - "summary": "Roll the scope's current published pointer back to an earlier versionnumber (service.rollback_scope); 400 with the error", + "summary": "Roll the scope's current published pointer back to an earlier versionnumber (service.rollback_scope); 400 with the...", "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": [ { @@ -4610,7 +11887,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "scopeid", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "path: scopeid; JSON body: versionnumber (required, int)" + } + } } } } @@ -4620,7 +11941,7 @@ "tags": [ "plugin-geenforce" ], - "summary": "Write the scope's current published JSON to the configured share root (geenforce_share_root setting), backing up the old", + "summary": "Write the scope's current published JSON to the configured share root (geenforce_share_root setting), backing up the...", "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": [ { @@ -4629,7 +11950,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "scopeid", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "path: scopeid; no body" + } + } } } } @@ -4639,7 +12004,7 @@ "tags": [ "plugin-geenforce" ], - "summary": "Read plugin config: the on-share export root (geenforce_share_root) and the client IP allowlist CIDRs (geenforce_allowed", + "summary": "Read plugin config: the on-share export root (geenforce_share_root) and the client IP allowlist CIDRs...", "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": [ { @@ -4648,7 +12013,26 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." } } }, @@ -4656,7 +12040,7 @@ "tags": [ "plugin-geenforce" ], - "summary": "Update plugin config settings; allowedcidrs is validated/normalized (comma/newline-separated CIDRs or bare IPs, 400 list", + "summary": "Update plugin config settings; allowedcidrs is validated/normalized (comma/newline-separated CIDRs or bare IPs, 400...", "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": [ { @@ -4665,7 +12049,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "JSON body (both optional): shareroot (string path), allowedcidrs (CSV/newline CIDR list)" + } + } } } } @@ -4675,7 +12090,7 @@ "tags": [ "plugin-geenforce" ], - "summary": "Latest enforcement report per PC (iscurrent rows) for the fleet compliance view: applied vs latest published version (re", + "summary": "Latest enforcement report per PC (iscurrent rows) for the fleet compliance view: applied vs latest published version...", "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": [ { @@ -4684,7 +12099,26 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." } } } @@ -4694,7 +12128,7 @@ "tags": [ "plugin-geenforce" ], - "summary": "One enforcement report in detail with per-entry outcomes (entryname, action, selfhealed, exitcode, message) plus applied", + "summary": "One enforcement report in detail with per-entry outcomes (entryname, action, selfhealed, exitcode, message) plus...", "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": [ { @@ -4703,9 +12137,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "reportid", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] } }, "/api/notifications/types": { @@ -4718,7 +12184,20 @@ "security": [], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } }, @@ -4735,7 +12214,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: typename (required, unique), typedescription/description, typecolor/color, expirymode (none|duration|dailytime), expirydays, expiryhour, expiryminute, splitperemployee, showemployeephoto, displaystyle (standard|carousel|grid|banner)" + } + } } } } @@ -4754,7 +12264,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "type_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: any of typename (unique-checked), typedescription/description, typecolor/color, isactive, expirymode, expirydays, expiryhour, expiryminute, splitperemployee, showemployeephoto, displaystyle" + } + } } } }, @@ -4771,7 +12325,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "type_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: any of typename (unique-checked), typedescription/description, typecolor/color, isactive, expirymode, expirydays, expiryhour, expiryminute, splitperemployee, showemployeephoto, displaystyle" + } + } } } } @@ -4786,7 +12384,20 @@ "security": [], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } }, @@ -4803,7 +12414,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "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" + } + } } } } @@ -4818,9 +12460,35 @@ "security": [], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "notification_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] }, "put": { "tags": [ @@ -4835,7 +12503,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "notification_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: notification/message, notificationtypeid, businessunitid, appid, ticketnumber, link/linkurl, isactive, isshopfloor, employeesso, employeename, starttime/startdate, endtime/enddate" + } + } } } }, @@ -4852,9 +12564,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "notification_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] } }, "/api/notifications/active": { @@ -4867,7 +12611,20 @@ "security": [], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -4882,7 +12639,20 @@ "security": [], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -4897,7 +12667,20 @@ "security": [], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -4912,9 +12695,35 @@ "security": [], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "sso", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/notifications/shopfloor": { @@ -4922,12 +12731,25 @@ "tags": [ "plugin-notifications" ], - "summary": "Shopfloor TV feed: current cards (active now, or ended <30 min ago flagged resolved) + upcoming (starts within 5 days); ", + "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -4937,12 +12759,25 @@ "tags": [ "plugin-slides" ], - "summary": "Public flat playlist for a surface (lobby/shopfloor); raw jsonify, not the success_response envelope, so the screensaver", + "summary": "Public flat playlist for a surface (lobby/shopfloor); raw jsonify, not the success_response envelope, so the...", "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -4952,14 +12787,49 @@ "tags": [ "plugin-slides" ], - "summary": "Public serving of a single slide image from instance/slides//, with basename path-traversal guard; 404 on unkno", + "summary": "Public serving of a single slide image from instance/slides//, with basename path-traversal guard; 404 on...", "description": "Public serving of a single slide image from instance/slides//, 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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "surface", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "filename", + "in": "path", + "required": true, + "schema": { + "type": "string", + "description": "may contain slashes" + } + } + ] } }, "/api/slides/{surface}": { @@ -4967,7 +12837,7 @@ "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", + "summary": "Admin list of a surface's slides (TvSlide rows ordered by sortorder,slideid, filtered to files present on disk), each...", "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": [ { @@ -4976,9 +12846,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "surface", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/slides/{surface}/upload": { @@ -4986,7 +12888,7 @@ "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", + "summary": "Upload one or more slide images; non-image extensions skipped, names secure_filename'd and unique-renamed (stem_N.ext)...", "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": [ { @@ -4995,7 +12897,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "surface", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "path: surface. multipart/form-data body: files (repeatable) or single file. Allowed extensions: .jpg .jpeg .png .gif .bmp .webp. Returns {added:[names]}" + } + } } } } @@ -5014,7 +12960,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "surface", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "path: surface. JSON body: {order: [filename, ...]}" + } + } } } } @@ -5024,7 +13014,7 @@ "tags": [ "plugin-slides" ], - "summary": "Delete named slides: removes file from disk (OSError swallowed) and the TvSlide row; filenames reduced to basename first", + "summary": "Delete named slides: removes file from disk (OSError swallowed) and the TvSlide row; filenames reduced to basename...", "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": [ { @@ -5033,7 +13023,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "surface", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "path: surface. JSON body: {files: [filename, ...]}" + } + } } } } @@ -5043,7 +13077,7 @@ "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", + "summary": "Update a single slide's per-slide duration; seconds clamped to >=0 (0 means use the 10s surface default); 404 if slide...", "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": [ { @@ -5052,7 +13086,59 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "surface", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "slideid", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "path: surface, slideid (int). JSON body: {seconds: int} (non-int -> VALIDATION_ERROR)" + } + } } } } @@ -5071,7 +13157,26 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." } } }, @@ -5079,7 +13184,7 @@ "tags": [ "plugin-printedparts" ], - "summary": "Create a printed item; itemcode auto-minted from printedparts_code_prefix setting + row id; optional gagelabtag unique-c", + "summary": "Create a printed item; itemcode auto-minted from printedparts_code_prefix setting + row id; optional gagelabtag...", "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": [ { @@ -5088,7 +13193,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "json body: itemname (required), gagelabtag, itemdescription, lowstockthreshold (default from printedparts_default_threshold setting, fallback 5), binlocation, printnotes; quantityonhand starts at 0" + } + } } } } @@ -5107,15 +13243,47 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "item_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] }, "put": { "tags": [ "plugin-printedparts" ], - "summary": "Update catalog fields (itemname, itemdescription, lowstockthreshold, binlocation, printnotes, gagelabtag); rejects quant", + "summary": "Update catalog fields (itemname, itemdescription, lowstockthreshold, binlocation, printnotes, gagelabtag); rejects...", "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": [ { @@ -5124,7 +13292,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "item_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "path: item_id; json body: any of the editable fields; gagelabtag uppercased, empty string clears it" + } + } } } }, @@ -5141,9 +13353,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "item_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] } }, "/api/printedparts/items/{item_id}/restore": { @@ -5160,7 +13404,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "item_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "path: item_id" + } + } } } } @@ -5170,7 +13458,7 @@ "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", + "summary": "Upload or replace the item's photo (png/jpg/jpeg/gif/webp); old image files for the item are deleted first, imageurl...", "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=\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": [ { @@ -5179,7 +13467,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "item_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "path: item_id; multipart/form-data: file=" + } + } } } }, @@ -5187,7 +13519,7 @@ "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", + "summary": "Clear the item's imageurl; deletes the file on disk only if the URL is plugin-owned (starts with...", "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": [ { @@ -5196,9 +13528,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "item_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] } }, "/api/printedparts/image/{filename}": { @@ -5211,9 +13575,36 @@ "security": [], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "filename", + "in": "path", + "required": true, + "schema": { + "type": "string", + "description": "may contain slashes" + } + } + ] } }, "/api/printedparts/items/{item_id}/restock": { @@ -5221,7 +13612,7 @@ "tags": [ "plugin-printedparts" ], - "summary": "Add freshly printed stock via a 'restock' ledger write (single-commit ledger + cached quantity); badge resolved server-s", + "summary": "Add freshly printed stock via a 'restock' ledger write (single-commit ledger + cached quantity); badge resolved...", "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": [ { @@ -5230,7 +13621,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "item_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "path: item_id; json body: quantity (positive int, required), badge (required, 422 BadgeError if unresolvable)" + } + } } } } @@ -5240,7 +13675,7 @@ "tags": [ "plugin-printedparts" ], - "summary": "Correct the count (damage, recount) via an 'adjust' ledger write; reason mandatory; rejects driving stock below zero; fi", + "summary": "Correct the count (damage, recount) via an 'adjust' ledger write; reason mandatory; rejects driving stock below zero...", "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": [ { @@ -5249,7 +13684,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "item_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "path: item_id; json body: quantitychange (non-zero int, required), reason (required), badge (required)" + } + } } } } @@ -5259,18 +13738,40 @@ "tags": [ "plugin-printedparts" ], - "summary": "Item summary for a scanned bin barcode; resolves full code (itemcode or gagelabtag), 'TAG|rev' QR payloads (rev stripped", + "summary": "Item summary for a scanned bin barcode; resolves full code (itemcode or gagelabtag), 'TAG|rev' QR payloads (rev...", "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": [] - } - ], + "security": [], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "itemcode", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/printedparts/kiosk/take": { @@ -5278,16 +13779,37 @@ "tags": [ "plugin-printedparts" ], - "summary": "Kiosk checkout: decrement-only 'take' ledger write, badge-attributed, bounded by quantityonhand; records optional print-", + "summary": "Kiosk checkout: decrement-only 'take' ledger write, badge-attributed, bounded by quantityonhand; records optional...", "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": [] - } - ], + "security": [], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "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)" + } + } } } } @@ -5297,16 +13819,30 @@ "tags": [ "plugin-printedparts" ], - "summary": "Stock-level report for active items with low-stock flags and ledgerdelta (cache-vs-ledger reconcile; nonzero means a wri", + "summary": "Stock-level report for active items with low-stock flags and ledgerdelta (cache-vs-ledger reconcile; nonzero means a...", "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -5319,13 +13855,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -5338,13 +13888,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -5363,15 +13927,47 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "item_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] }, "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", + "summary": "Upload the next print-file revision (append-only, auto-numbered max+1); allowed ext...", "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= (required), note= (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": [ { @@ -5380,7 +13976,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "item_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "path: item_id; multipart/form-data: file= (required), note= (optional)" + } + } } } } @@ -5390,18 +14030,45 @@ "tags": [ "plugin-printedparts" ], - "summary": "Download a print-file revision as an attachment under its original filename (jwt-optional so plain anchor downloads work", + "summary": "Download a print-file revision as an attachment under its original filename (jwt-optional so plain anchor downloads...", "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "file_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] } }, "/api/printedparts/files/{file_id}": { @@ -5418,9 +14085,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "file_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] } }, "/api/network/types": { @@ -5431,13 +14130,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } }, @@ -5454,7 +14167,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: networkdevicetype (required), description, icon, color" + } + } } } } @@ -5467,15 +14211,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "type_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] }, "put": { "tags": [ @@ -5490,7 +14261,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "type_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: networkdevicetype, description, icon, color, isactive" + } + } } } }, @@ -5507,9 +14322,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "type_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] } }, "/api/network": { @@ -5520,13 +14367,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } }, @@ -5534,7 +14395,7 @@ "tags": [ "plugin-network" ], - "summary": "Create a network device (creates core Asset + NetworkDevice extension; upserts primary-IP Communication; audit-logged; h", + "summary": "Create a network device (creates core Asset + NetworkDevice extension; upserts primary-IP Communication; audit-logged...", "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": [ { @@ -5543,7 +14404,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "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" + } + } } } } @@ -5556,21 +14448,48 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "device_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] }, "put": { "tags": [ "plugin-network" ], - "summary": "Update asset + network-device fields; 409 on assetnumber/hostname conflicts; change-diff audit log; upserts primary IP w", + "summary": "Update asset + network-device fields; 409 on assetnumber/hostname conflicts; change-diff audit log; upserts primary IP...", "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": [ { @@ -5579,7 +14498,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "device_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: any of assetnumber, name, serialnumber, gaugelabreference, maintenancereference, statusid, locationid, businessunitid, mapx, mapy, notes, isactive, networkdevicetypeid, vendorid, hostname, firmwareversion, portcount, ispoe, ismanaged, rackunit, ipaddress" + } + } } } }, @@ -5596,9 +14559,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "device_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] } }, "/api/network/by-asset/{asset_id}": { @@ -5609,15 +14604,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "asset_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] } }, "/api/network/by-hostname/{hostname}": { @@ -5628,15 +14650,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "hostname", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/network/dashboard/summary": { @@ -5647,13 +14696,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -5666,13 +14729,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } }, @@ -5689,7 +14766,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: vlannumber (required), name (required), description, vlantype" + } + } } } } @@ -5702,15 +14810,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "vlan_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] }, "put": { "tags": [ @@ -5725,7 +14860,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "vlan_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: vlannumber, name, description, vlantype, isactive" + } + } } } }, @@ -5742,9 +14921,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "vlan_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] } }, "/api/network/subnets": { @@ -5755,13 +14966,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } }, @@ -5778,7 +15003,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: cidr (required, must contain '/'), name (required), description, gatewayip, subnetmask, networkaddress, broadcastaddress, vlanid, subnettype, locationid, dhcpenabled (default true), dhcprangestart, dhcprangeend, dns1, dns2" + } + } } } } @@ -5788,18 +15044,45 @@ "tags": [ "plugin-network" ], - "summary": "Get one subnet plus 'devices': every asset of any type (PC/printer/network) whose primary-IP Communication falls inside ", + "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "subnet_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] }, "put": { "tags": [ @@ -5814,7 +15097,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "subnet_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: any of cidr, name, description, gatewayip, subnetmask, networkaddress, broadcastaddress, vlanid, subnettype, locationid, dhcpenabled, dhcprangestart, dhcprangeend, dns1, dns2, isactive" + } + } } } }, @@ -5831,9 +15158,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "subnet_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] } }, "/api/usb": { @@ -5841,16 +15200,30 @@ "tags": [ "plugin-usb" ], - "summary": "List USB devices with checkout status (paginated); dual-mode: selfhosted tables or external cmmc_usb DB per usb_director", + "summary": "List USB devices with checkout status (paginated); dual-mode: selfhosted tables or external cmmc_usb DB per...", "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } }, @@ -5867,7 +15240,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body JSON: device_id (required), device_desc, device_owner (badge), locker_location" + } + } } } } @@ -5880,15 +15284,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "device_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] }, "put": { "tags": [ @@ -5903,7 +15334,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "device_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "path: device_id; body JSON: any of device_desc, device_owner, locker_location, status" + } + } } } } @@ -5922,7 +15397,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "device_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "path: device_id; no body" + } + } } } } @@ -5932,7 +15451,7 @@ "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); ", + "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": [ { @@ -5941,7 +15460,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "device_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "path: device_id; body JSON: badge (required), locker_location (optional, also updates device)" + } + } } } } @@ -5951,7 +15514,7 @@ "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", + "summary": "Check a device back in (writes check-in log row with sanitized/virus-scan flags, sets status checked-in); 400 if not...", "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": [ { @@ -5960,7 +15523,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "device_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "path: device_id; body JSON: badge (required), locker_location, sanitized (bool/1/0), scanned_viruses (bool/1/0)" + } + } } } } @@ -5973,15 +15580,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "device_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/usb/checkouts": { @@ -5992,13 +15626,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -6011,13 +15659,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -6030,13 +15692,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } }, @@ -6053,7 +15729,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: printertype (required), description, icon, color; reactivates a matching inactive type instead of 409" + } + } } } } @@ -6066,15 +15773,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "type_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] }, "put": { "tags": [ @@ -6089,7 +15823,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "type_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: printertype, description, icon, color, isactive (any subset); 409 on name clash" + } + } } } }, @@ -6106,9 +15884,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "type_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/printers/drivers": { @@ -6119,13 +15929,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } }, @@ -6142,7 +15966,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: name (required), location (required), description, modelnumberid, isactive" + } + } } } } @@ -6161,7 +16016,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "driver_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: name, location, description, isactive, modelnumberid (any subset)" + } + } } } }, @@ -6178,9 +16077,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "driver_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/printers": { @@ -6191,13 +16122,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } }, @@ -6214,7 +16159,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "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" + } + } } } } @@ -6224,16 +16200,30 @@ "tags": [ "plugin-printers" ], - "summary": "Flat unpaginated list of active network printers (with map positions) for the signed printer-installer EXE; replaces cla", + "summary": "Flat unpaginated list of active network printers (with map positions) for the signed printer-installer EXE; replaces...", "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -6243,16 +16233,30 @@ "tags": [ "plugin-printers" ], - "summary": "Generate and download a Windows .bat that installs selected printers (HP/Xerox via universal PrinterInstaller.exe, per-p", + "summary": "Generate and download a Windows .bat that installs selected printers (HP/Xerox via universal PrinterInstaller.exe...", "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -6262,16 +16266,30 @@ "tags": [ "plugin-printers" ], - "summary": "Look up a PC's default printer via the defaultprinter asset relationship (parity with classic apipcdefaultprinter.asp); ", + "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -6284,15 +16302,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "printer_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] }, "put": { "tags": [ @@ -6307,7 +16352,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "printer_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "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" + } + } } } }, @@ -6324,9 +16413,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "printer_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/printers/by-asset/{asset_id}": { @@ -6337,15 +16458,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "asset_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/printers/{printer_id}/supplies": { @@ -6353,18 +16501,45 @@ "tags": [ "plugin-printers" ], - "summary": "Real-time supply levels from Zabbix for one printer, annotated with status/color/part numbers; fails soft (empty supplie", + "summary": "Real-time supply levels from Zabbix for one printer, annotated with status/color/part numbers; fails soft (empty...", "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "printer_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/printers/lowsupplies": { @@ -6375,13 +16550,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -6394,13 +16583,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -6419,7 +16622,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "none" + } + } } } } @@ -6432,13 +16666,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -6451,13 +16699,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -6467,16 +16729,30 @@ "tags": [ "plugin-printers" ], - "summary": "List printer models with supply counts for the supply-management picker (restricted to models attached to printers or al", + "summary": "List printer models with supply counts for the supply-management picker (restricted to models attached to printers or...", "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -6489,15 +16765,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "modelnumberid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] }, "post": { "tags": [ @@ -6512,7 +16815,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "modelnumberid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: partnumber (required), supplytype (default toner), color (default none), capacitytier (default standard), marketingname, pageyield, notes; 409 if part already mapped to model" + } + } } } } @@ -6531,7 +16878,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "modelsupplyid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "body: any subset of supplytype, color, capacitytier, partnumber, marketingname, pageyield, notes; enum-validated, 409 on partnumber clash within model" + } + } } } }, @@ -6548,9 +16939,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "modelsupplyid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/printers/supplies/forecast": { @@ -6558,16 +16981,30 @@ "tags": [ "plugin-printers" ], - "summary": "One row per CARTRIDGE: days-to-empty from Zabbix history (hourly trends over a long window), soonest first, banded empty", + "summary": "One row per CARTRIDGE: days-to-empty from Zabbix history (hourly trends over a long window), soonest first, banded...", "description": "One row per CARTRIDGE: days-to-empty from Zabbix history (hourly trends over a long window), soonest first, banded empty/soon/month/later, each with its part numbers and replacement count. Also returns orderlist - what to buy within horizondays, grouped by part number with a quantity. Cartridges with no honest estimate come back separately with the reason. available=false when Zabbix is unreachable\n\n**Auth:** jwt-optional\n\n**Params:** days (1-365, default 90)\n\n**Example:**\n```\ncurl 'http://localhost:5001/api/printers/supplies/forecast?days=90'\n```", "security": [ + {}, { "bearerAuth": [] } ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -6580,13 +17017,27 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } }, @@ -6603,7 +17054,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "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)" + } + } } } } @@ -6616,15 +17098,42 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "assetid", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] } }, "/api/warranty/{warrantyid}": { @@ -6635,21 +17144,48 @@ "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "warrantyid", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] }, "put": { "tags": [ "plugin-warranty" ], - "summary": "Partial update of any warranty field (only keys present in body change), including isactive and replacing asset links vi", + "summary": "Partial update of any warranty field (only keys present in body change), including isactive and replacing asset links...", "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": [ { @@ -6658,7 +17194,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "warrantyid", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "path: warrantyid; JSON body (all optional): vendor, servicetag, provider, servicelevel, startdate, enddate, notes, isactive (bool), assetids (full replacement list)" + } + } } } }, @@ -6675,9 +17255,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "warrantyid", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ] } }, "/api/warranty/{warrantyid}/refresh": { @@ -6685,7 +17297,7 @@ "tags": [ "plugin-warranty" ], - "summary": "Re-query the warranty's provider (dell etc.) by service tag and update servicelevel/startdate/enddate + lastcheckeddate;", + "summary": "Re-query the warranty's provider (dell etc.) by service tag and update servicelevel/startdate/enddate...", "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": [ { @@ -6694,7 +17306,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "warrantyid", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "path: warrantyid; no body" + } + } } } } @@ -6704,7 +17360,7 @@ "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", + "summary": "Bulk Dell sync: look up serials of active assets lacking a dated warranty via Dell bulk_lookup, creating or updating...", "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": [ { @@ -6713,7 +17369,38 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "query: all (default false; 'true' re-checks assets that already have a dated warranty); no body" + } + } } } } @@ -6723,16 +17410,30 @@ "tags": [ "plugin-warranty" ], - "summary": "Report for the Reports hub: active warranties bucketed by derived status (expired/expiring/active/unknown) with per-buck", + "summary": "Report for the Reports hub: active warranties bucketed by derived status (expired/expiring/active/unknown) with...", "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)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } } } } @@ -6751,7 +17452,51 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." + } + }, + "parameters": [ + { + "name": "warrantyid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "description": "multipart/form-data: file= (pdf/png/jpg/gif/webp/tif/msg/eml/doc/docx/xls/xlsx), max 25MB" + } + } } } }, @@ -6768,9 +17513,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "warrantyid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } }, "/api/warranty/proof/{filename}": { @@ -6787,9 +17564,41 @@ ], "responses": { "200": { - "description": "Success (success_response envelope)" + "description": "Success. Body is the success_response envelope: {status, data, meta}.", + "content": { + "application/json": { + "$ref": "#/components/schemas/SuccessEnvelope" + } + } + }, + "default": { + "description": "Error. Body is the error envelope; the code and message are nested under data.error.", + "content": { + "application/json": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + }, + "401": { + "description": "Missing or invalid credentials." + }, + "403": { + "description": "Authenticated, but not permitted." + }, + "404": { + "description": "No such record." } - } + }, + "parameters": [ + { + "name": "filename", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] } } } diff --git a/scripts/gen_openapi.py b/scripts/gen_openapi.py index 91fc0cf..54da427 100644 --- a/scripts/gen_openapi.py +++ b/scripts/gen_openapi.py @@ -58,14 +58,121 @@ def _norm_path(p): def _security(auth): + """OpenAPI security for one endpoint's documented auth string. + + `jwt-optional` is the interesting case, and the common one - 121 endpoints. + It means "works logged out, returns more when logged in", which OpenAPI + expresses as a list containing BOTH the empty requirement and the scheme. + Publishing those as bearer-required told every reader, human and machine, + that a public endpoint needs a token. + """ a = (auth or '').lower() - if a in ('none', 'public', ''): + if 'optional' in a: + return [{}, {'bearerAuth': []}] + if a in ('none', 'public', '') or a.startswith('none') or a.startswith('public'): return [] if 'api-key' in a or 'api_key' in a or 'x-api-key' in a: return [{'apiKeyAuth': []}] return [{'bearerAuth': []}] +def _path_parameters(rawpath): + """Path parameters, typed from the Flask converter that declared them. + + The generator emitted no `parameters` at all, which makes the document + invalid OpenAPI 3.1 - and breaks its only machine consumer outright, because + a tool built from an operation with no parameters has nowhere to put the id + the caller supplied, so it is dropped in silence. + """ + parameters = [] + for converter, name in re.findall(r'<(?:([^:>]+):)?([^>]+)>', rawpath or ''): + schema = {'type': 'integer'} if converter in ('int',) else {'type': 'string'} + if converter == 'float': + schema = {'type': 'number'} + if converter == 'path': + schema = {'type': 'string', 'description': 'may contain slashes'} + parameters.append({ + 'name': name, + 'in': 'path', + 'required': True, + 'schema': schema, + }) + return parameters + + +def _request_body(verb, entry): + """A JSON body for the verbs that take one. + + The inventory describes bodies in prose, not as schemas, so this does not + invent field names it cannot verify - it declares an object and carries the + prose. That is honest, and it is the difference between a machine client + that can send a body and one that cannot send anything at all. + """ + if verb not in ('post', 'put', 'patch'): + return None + described = entry.get('params') or '' + return { + 'required': verb in ('post', 'put'), + 'content': { + 'application/json': { + 'schema': { + 'type': 'object', + 'additionalProperties': True, + 'description': described or 'See the endpoint description.', + } + } + }, + } + + +def _responses(security, parameters): + """The envelope, plus the failures a caller has to handle. + + One hardcoded 200 was the whole response section, so a generated client had + no idea any call could fail, and 19 operations that do not return the + envelope at all were documented as if they did. + """ + responses = { + '200': { + 'description': 'Success. Body is the success_response envelope: ' + '{status, data, meta}.', + 'content': {'application/json': { + '$ref': '#/components/schemas/SuccessEnvelope'}}, + }, + 'default': { + 'description': 'Error. Body is the error envelope; the code and ' + 'message are nested under data.error.', + 'content': {'application/json': { + '$ref': '#/components/schemas/ErrorEnvelope'}}, + }, + } + # An endpoint that can be called without a token cannot answer 401. + if security and security != [{}]: + requires = not any(item == {} for item in security) + if requires: + responses['401'] = {'description': 'Missing or invalid credentials.'} + responses['403'] = {'description': 'Authenticated, but not permitted.'} + if parameters: + responses['404'] = {'description': 'No such record.'} + return responses + + +def _summary(text, fallback): + """A summary that ends on a word. + + Cutting at a fixed 120 characters truncated 95 of them mid-word, which is + what a tool picker shows a user as the whole description of the call. + """ + source = (text or fallback or '').strip() + if len(source) <= 120: + return source + head = source[:120] + cut = head.rfind(' ') + # Trailing connectives read worse than a clean cut: "servicelevel +..." + # promises a continuation the reader will never see. + return (head[:cut] if cut > 40 else head).rstrip(' ,;:.+-/&|(') + '...' + + def build(surfaces): paths = {} tags = [] @@ -88,14 +195,21 @@ def build(surfaces): desc.append('\n\n**Params:** ' + e['params']) if e.get('example'): desc.append('\n\n**Example:**\n```\n' + e['example'] + '\n```') - paths.setdefault(path, {})[verb] = { + security = _security(e.get('auth')) + parameters = _path_parameters(e.get('path')) + operation = { 'tags': [s['surface']], - 'summary': (e.get('purpose') or path)[:120], + 'summary': _summary(e.get('purpose'), path), 'description': ''.join(desc), - 'security': _security(e.get('auth')), - 'responses': {'200': { - 'description': 'Success (success_response envelope)'}}, + 'security': security, + 'responses': _responses(security, parameters), } + if parameters: + operation['parameters'] = parameters + body = _request_body(verb, e) + if body: + operation['requestBody'] = body + paths.setdefault(path, {})[verb] = operation return { 'openapi': '3.1.0', 'info': { @@ -117,7 +231,61 @@ def build(surfaces): # generator can honestly name; a site that wants its own in the spec # sets SHOPDB_PUBLIC_URL when regenerating. 'servers': _servers(), - 'components': {'securitySchemes': { + 'components': { + 'schemas': { + # The envelope every JSON endpoint returns. Worth spelling out + # because the error shape nests one level deeper than most + # people assume, and code written against the assumption reads + # undefined on every error it tries to report. + 'SuccessEnvelope': { + 'type': 'object', + 'properties': { + 'status': {'type': 'string', 'enum': ['success']}, + 'data': {'description': 'The payload. Shape is per endpoint.'}, + 'meta': { + 'type': 'object', + 'properties': { + 'timestamp': {'type': 'string', 'format': 'date-time'}, + 'requestid': {'type': 'string'}, + 'pagination': { + 'type': 'object', + 'properties': { + 'page': {'type': 'integer'}, + 'perpage': {'type': 'integer'}, + 'total': {'type': 'integer'}, + 'pages': {'type': 'integer'}, + }, + }, + }, + }, + }, + 'required': ['status'], + }, + 'ErrorEnvelope': { + 'type': 'object', + 'properties': { + 'status': {'type': 'string', 'enum': ['error']}, + 'data': { + 'type': 'object', + 'properties': { + 'error': { + 'type': 'object', + 'properties': { + 'code': {'type': 'string'}, + 'message': {'type': 'string'}, + 'details': {'type': 'object'}, + }, + 'required': ['code', 'message'], + }, + }, + 'required': ['error'], + }, + 'meta': {'type': 'object'}, + }, + 'required': ['status', 'data'], + }, + }, + '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', diff --git a/tests/test_docs_contract.py b/tests/test_docs_contract.py index b7ac2b9..e566b74 100644 --- a/tests/test_docs_contract.py +++ b/tests/test_docs_contract.py @@ -20,14 +20,24 @@ def test_hooks_doc_exists(): assert HOOKS_DOC.exists(), 'docs/PLUGIN-HOOKS.md is missing' -def test_hooks_doc_declares_current_contract_version(): - """The doc's version example must match the live __contract_version__.""" +def test_hooks_doc_points_at_the_generated_version(): + """The doc must name the symbol and send the reader to the generated map. + + This used to require the literal value in the page, which is what made it + stale everywhere else: nine documents copied a contract version and every + one of them was wrong, including a pin an external author would have failed + to load with. A doc that points at docs/PROJECT-MAP.md cannot go stale, + because the map is generated from shopdb/__init__.py. + + See tests/test_docs_versions.py, which enforces the same rule the other way + round: no document may declare a version literal at all. + """ text = HOOKS_DOC.read_text() - expected = f"__contract_version__ = '{__contract_version__}'" - assert expected in text, ( - f'docs/PLUGIN-HOOKS.md version example is stale: expected {expected}. ' - f'Update the "Contract version" section when bumping the contract.' - ) + assert '__contract_version__' in text, ( + 'docs/PLUGIN-HOOKS.md should still name the symbol a plugin pins against.') + assert 'PROJECT-MAP.md' in text, ( + 'docs/PLUGIN-HOOKS.md should send the reader to the generated map for ' + 'the current value rather than restating it.') def test_every_public_hook_is_documented(): diff --git a/tests/test_openapi_spec.py b/tests/test_openapi_spec.py new file mode 100644 index 0000000..82a8191 --- /dev/null +++ b/tests/test_openapi_spec.py @@ -0,0 +1,123 @@ +"""The generated spec has to be usable by a machine, not just readable. + +docs/openapi.json is the only machine-readable description of this API, and it +is what the MCP server builds its tools from. It was published with no +`parameters` and no `requestBody` on any operation - invalid OpenAPI 3.1, and +worse in practice: a tool built from an operation with no parameters has nowhere +to put the id its caller supplied, so every argument was dropped in silence. A +call for one asset returned the list. + +These tests are the floor. They check the spec's shape, not its prose. +""" + +import json +import re +from pathlib import Path + +import pytest + +REPO = Path(__file__).resolve().parents[1] +SPEC = REPO / 'docs' / 'openapi.json' + +pytestmark = pytest.mark.skipif( + not SPEC.is_file(), + reason='no generated spec in this checkout') + + +@pytest.fixture(scope='module') +def spec(): + return json.loads(SPEC.read_text()) + + +def operations(spec): + for path, item in spec['paths'].items(): + for verb, operation in item.items(): + yield path, verb, operation + + +def test_every_templated_path_declares_its_parameters(spec): + """`/api/assets/{id}` with no parameters is not a description of anything.""" + offenders = [ + '%s %s' % (verb.upper(), path) + for path, verb, operation in operations(spec) + if '{' in path and not operation.get('parameters') + ] + assert not offenders, ( + 'These operations have a templated path and no parameters, so a ' + 'generated client cannot fill it in:\n ' + '\n '.join(offenders[:15])) + + +def test_declared_parameters_match_the_template(spec): + """A parameter that is not in the path, or a placeholder with no parameter, + is the same defect wearing different clothes.""" + offenders = [] + for path, verb, operation in operations(spec): + intemplate = set(re.findall(r'\{([^}]+)\}', path)) + declared = {p['name'] for p in operation.get('parameters', []) + if p.get('in') == 'path'} + if intemplate != declared: + offenders.append('%s %s: template %s, declared %s' + % (verb.upper(), path, sorted(intemplate), sorted(declared))) + assert not offenders, '\n '.join(offenders[:15]) + + +def test_write_verbs_can_carry_a_body(spec): + offenders = [ + '%s %s' % (verb.upper(), path) + for path, verb, operation in operations(spec) + if verb in ('post', 'put') and 'requestBody' not in operation + ] + assert not offenders, ( + 'These write operations declare no request body, so a client built ' + 'from this spec cannot send one:\n ' + '\n '.join(offenders[:15])) + + +def test_optional_auth_is_expressed_as_optional(spec): + """An endpoint that works logged out must not be published as requiring a + token - that is a lie to every reader, and it is the majority case here.""" + optional = [operation for _, _, operation in operations(spec) + if any(item == {} for item in operation.get('security') or [])] + assert len(optional) > 50, ( + 'The inventory documents over a hundred jwt-optional endpoints; the ' + 'spec should express them with an empty security requirement ' + 'alongside the scheme, not as bearer-required.') + + +def test_an_endpoint_that_needs_no_token_cannot_fail_with_401(spec): + offenders = [ + '%s %s' % (verb.upper(), path) + for path, verb, operation in operations(spec) + if not operation.get('security') and '401' in operation.get('responses', {}) + ] + assert not offenders, '\n '.join(offenders[:15]) + + +def test_every_operation_documents_failure(spec): + offenders = [ + '%s %s' % (verb.upper(), path) + for path, verb, operation in operations(spec) + if 'default' not in operation.get('responses', {}) + ] + assert not offenders, ( + 'These operations document only success:\n ' + '\n '.join(offenders[:15])) + + +def test_the_envelopes_are_defined_and_referenced(spec): + schemas = spec.get('components', {}).get('schemas', {}) + assert 'SuccessEnvelope' in schemas and 'ErrorEnvelope' in schemas + # The error shape is the one people get wrong: nested under data. + error = schemas['ErrorEnvelope']['properties']['data']['properties']['error'] + assert set(error['required']) == {'code', 'message'} + + +def test_summaries_are_not_cut_mid_word(spec): + """A truncated summary is what a tool picker shows as the whole description + of a call.""" + offenders = [] + for path, verb, operation in operations(spec): + summary = operation.get('summary', '') + if summary.endswith('...'): + body = summary[:-3] + if body and not body[-1].isalnum() and body[-1] not in ')]"\'': + offenders.append('%s %s: %r' % (verb.upper(), path, summary[-40:])) + assert not offenders, '\n '.join(offenders[:15])