Document the upload, proof and forecast endpoints
Some checks failed
CI / backend (push) Failing after 9s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 10s
CI / migrations-mysql (push) Failing after 7s

Ten routes shipped over the last few commits without reaching the inventory,
which is the file everything else is generated from - openapi.json, the /api/docs
browser, and the per-site build. An endpoint absent from it is invisible to
anyone integrating against the API even though it answers requests.

The auth notes matter more than the paths. API-REFERENCE tells a deployment
reviewer that essentially every GET is optional-auth; two of these are not, and
an exception buried in a table of 372 operations is one nobody reads. The
installer download and the warranty proof are called out with why: one is
licensed vendor software, the other carries pricing and a service tag. The
application image is listed with the fully public endpoints, since tiles render
before login.
This commit is contained in:
cproudlock
2026-08-12 12:06:25 -04:00
parent ea35a134fe
commit a64796f060
4 changed files with 302 additions and 1 deletions

View File

@@ -96,6 +96,18 @@ Two auth patterns dominate the reads:
Every mutating endpoint (POST / PUT / PATCH / DELETE) requires a JWT and is
gated by `require_role` or `require_permission`; none are public.
Two GETs deliberately break the optional-auth convention and require a
permission, because what they return is not asset metadata but a file that
carries value or liability of its own:
| Endpoint | Requires | Why |
|---|---|---|
| `GET /api/applications/package/<filename>` | `applications.view` | Licensed vendor installers. An anonymous URL is a redistribution channel. |
| `GET /api/warranty/proof/<filename>` | `warranty.view` | Invoices and certificates carry pricing and a service tag. |
Their sibling upload/delete routes are `applications.edit` / `warranty.edit`
like any other mutation.
### Fully public endpoints (auth = none)
| Endpoint | Purpose |
@@ -107,6 +119,7 @@ gated by `require_role` or `require_permission`; none are public.
| `GET /api/settings/branding/<filename>` | Serve site branding assets (logo, etc.). |
| `GET /api/settings` and `GET /api/settings/<key>` | Read-only, and only the public allowlist: the `branding` and `map` categories, a few named site keys, plus any key a plugin declares `public` in `get_settings_defaults` (e.g. `printedparts_label_prefix`, which the logged-out parts kiosk renders). Every other key answers 404 to an anonymous caller. |
| `GET /api/models/image/<filename>` | Serve a model image. |
| `GET /api/applications/image/<filename>` | Serve an application image (tiles render before login). |
| `GET /api/dashboard/navigation` | Public navigation tree. |
| `GET /api/dashboard/health` | Liveness / health probe. |
| `GET /api/plugins/enabled` | List enabled plugins (no claims used). |

View File

@@ -1437,6 +1437,54 @@
"auth": "permission:applications.edit",
"params": "body: appversionid",
"example": "curl -X PUT http://localhost:5001/api/applications/machines/8/15 -H \"Authorization: Bearer $TOK\" -H 'Content-Type: application/json' -d '{\"appversionid\":3}'"
},
{
"method": "POST",
"path": "/api/applications/<app_id>/image",
"auth": "jwt + applications.edit",
"params": "multipart/form-data: file=<image> (png/jpg/gif/webp/svg/ico)",
"purpose": "Upload or replace an application's image; sets application.image to the served URL",
"example": "curl -X POST -H 'Authorization: Bearer <token>' -F file=@logo.png http://localhost:5001/api/applications/5/image"
},
{
"method": "GET",
"path": "/api/applications/image/<filename>",
"auth": "none",
"params": "none",
"purpose": "Serve an uploaded application image. Public: application tiles render before login",
"example": "curl http://localhost:5001/api/applications/image/application-5.png"
},
{
"method": "DELETE",
"path": "/api/applications/<app_id>/image",
"auth": "jwt + applications.edit",
"params": "none",
"purpose": "Remove an application's image and clear the field",
"example": "curl -X DELETE -H 'Authorization: Bearer <token>' http://localhost:5001/api/applications/5/image"
},
{
"method": "POST",
"path": "/api/applications/<app_id>/package",
"auth": "jwt + applications.edit",
"params": "multipart/form-data: file=<installer> (exe/msi/msp/zip/7z/cab/iso/appx/msix/ps1/bat/txt/pdf), max 500MB",
"purpose": "Upload or replace the installer; sets installpath to the download URL",
"example": "curl -X POST -H 'Authorization: Bearer <token>' -F file=@setup.msi http://localhost:5001/api/applications/5/package"
},
{
"method": "GET",
"path": "/api/applications/package/<filename>",
"auth": "jwt + applications.view",
"params": "none",
"purpose": "Download an uploaded installer as an attachment. Authenticated: licensed vendor software",
"example": "curl -OJ -H 'Authorization: Bearer <token>' http://localhost:5001/api/applications/package/application-5.msi"
},
{
"method": "DELETE",
"path": "/api/applications/<app_id>/package",
"auth": "jwt + applications.edit",
"params": "none",
"purpose": "Remove an uploaded installer; clears installpath only when it pointed at the upload",
"example": "curl -X DELETE -H 'Authorization: Bearer <token>' http://localhost:5001/api/applications/5/package"
}
]
},
@@ -2895,6 +2943,14 @@
"params": "path: modelsupplyid",
"purpose": "Hard-delete a model supply mapping.",
"example": "curl -X DELETE -H \"Authorization: Bearer $TOKEN\" http://localhost:5001/api/printers/supplies/44"
},
{
"method": "GET",
"path": "/api/printers/supplies/forecast",
"auth": "jwt-optional",
"params": "days (1-365, default 90)",
"purpose": "Days-to-empty per printer from Zabbix history, soonest first, with cartridge replacement counts; printers with no honest estimate are returned separately with the reason. available=false when Zabbix is unreachable",
"example": "curl 'http://localhost:5001/api/printers/supplies/forecast?days=90'"
}
]
},
@@ -2972,6 +3028,30 @@
"auth": "jwt-optional",
"params": "none",
"example": "curl -H \"Authorization: Bearer $TOKEN\" http://localhost:5001/api/warranty/report"
},
{
"method": "POST",
"path": "/api/warranty/<warrantyid>/proof",
"auth": "jwt + warranty.edit",
"params": "multipart/form-data: file=<document> (pdf/png/jpg/gif/webp/tif/msg/eml/doc/docx/xls/xlsx), max 25MB",
"purpose": "Upload or replace the proof-of-cover document (invoice, certificate)",
"example": "curl -X POST -H 'Authorization: Bearer <token>' -F file=@invoice.pdf http://localhost:5001/api/warranty/12/proof"
},
{
"method": "GET",
"path": "/api/warranty/proof/<filename>",
"auth": "jwt + warranty.view",
"params": "none",
"purpose": "Download a proof document under the vendor's original filename. Authenticated: carries pricing and a service tag",
"example": "curl -OJ -H 'Authorization: Bearer <token>' http://localhost:5001/api/warranty/proof/warranty-12.pdf"
},
{
"method": "DELETE",
"path": "/api/warranty/<warrantyid>/proof",
"auth": "jwt + warranty.edit",
"params": "none",
"purpose": "Remove a proof document and clear both columns",
"example": "curl -X DELETE -H 'Authorization: Bearer <token>' http://localhost:5001/api/warranty/12/proof"
}
]
}

View File

@@ -2,7 +2,7 @@
"openapi": "3.1.0",
"info": {
"title": "ShopDB Flask API",
"version": "0.8.1",
"version": "0.9.0",
"description": "Asset-management API (core + plugins). Responses use a `success_response` envelope: `{status, data, meta}`. Auth: Bearer JWT (login or a managed PAT) for `jwt`/`admin`/`permission:*`; `X-API-Key` for collector/managed-token endpoints; public endpoints need neither."
},
"servers": [
@@ -3271,6 +3271,112 @@
}
}
},
"/api/applications/{app_id}/image": {
"post": {
"tags": [
"core-catalog"
],
"summary": "Upload or replace an application's image; sets application.image to the served URL",
"description": "Upload or replace an application's image; sets application.image to the served URL\n\n**Auth:** jwt + applications.edit\n\n**Params:** multipart/form-data: file=<image> (png/jpg/gif/webp/svg/ico)\n\n**Example:**\n```\ncurl -X POST -H 'Authorization: Bearer <token>' -F file=@logo.png http://localhost:5001/api/applications/5/image\n```",
"security": [
{
"bearerAuth": []
}
],
"responses": {
"200": {
"description": "Success (success_response envelope)"
}
}
},
"delete": {
"tags": [
"core-catalog"
],
"summary": "Remove an application's image and clear the field",
"description": "Remove an application's image and clear the field\n\n**Auth:** jwt + applications.edit\n\n**Params:** none\n\n**Example:**\n```\ncurl -X DELETE -H 'Authorization: Bearer <token>' http://localhost:5001/api/applications/5/image\n```",
"security": [
{
"bearerAuth": []
}
],
"responses": {
"200": {
"description": "Success (success_response envelope)"
}
}
}
},
"/api/applications/image/{filename}": {
"get": {
"tags": [
"core-catalog"
],
"summary": "Serve an uploaded application image. Public: application tiles render before login",
"description": "Serve an uploaded application image. Public: application tiles render before login\n\n**Auth:** none\n\n**Params:** none\n\n**Example:**\n```\ncurl http://localhost:5001/api/applications/image/application-5.png\n```",
"security": [],
"responses": {
"200": {
"description": "Success (success_response envelope)"
}
}
}
},
"/api/applications/{app_id}/package": {
"post": {
"tags": [
"core-catalog"
],
"summary": "Upload or replace the installer; sets installpath to the download URL",
"description": "Upload or replace the installer; sets installpath to the download URL\n\n**Auth:** jwt + applications.edit\n\n**Params:** multipart/form-data: file=<installer> (exe/msi/msp/zip/7z/cab/iso/appx/msix/ps1/bat/txt/pdf), max 500MB\n\n**Example:**\n```\ncurl -X POST -H 'Authorization: Bearer <token>' -F file=@setup.msi http://localhost:5001/api/applications/5/package\n```",
"security": [
{
"bearerAuth": []
}
],
"responses": {
"200": {
"description": "Success (success_response envelope)"
}
}
},
"delete": {
"tags": [
"core-catalog"
],
"summary": "Remove an uploaded installer; clears installpath only when it pointed at the upload",
"description": "Remove an uploaded installer; clears installpath only when it pointed at the upload\n\n**Auth:** jwt + applications.edit\n\n**Params:** none\n\n**Example:**\n```\ncurl -X DELETE -H 'Authorization: Bearer <token>' http://localhost:5001/api/applications/5/package\n```",
"security": [
{
"bearerAuth": []
}
],
"responses": {
"200": {
"description": "Success (success_response envelope)"
}
}
}
},
"/api/applications/package/{filename}": {
"get": {
"tags": [
"core-catalog"
],
"summary": "Download an uploaded installer as an attachment. Authenticated: licensed vendor software",
"description": "Download an uploaded installer as an attachment. Authenticated: licensed vendor software\n\n**Auth:** jwt + applications.view\n\n**Params:** none\n\n**Example:**\n```\ncurl -OJ -H 'Authorization: Bearer <token>' http://localhost:5001/api/applications/package/application-5.msi\n```",
"security": [
{
"bearerAuth": []
}
],
"responses": {
"200": {
"description": "Success (success_response envelope)"
}
}
}
},
"/api/computers/types": {
"get": {
"tags": [
@@ -6451,6 +6557,25 @@
}
}
},
"/api/printers/supplies/forecast": {
"get": {
"tags": [
"plugin-printers"
],
"summary": "Days-to-empty per printer from Zabbix history, soonest first, with cartridge replacement counts; printers with no honest",
"description": "Days-to-empty per printer from Zabbix history, soonest first, with cartridge replacement counts; printers with no honest estimate are returned 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)"
}
}
}
},
"/api/warranty": {
"get": {
"tags": [
@@ -6615,6 +6740,61 @@
}
}
}
},
"/api/warranty/{warrantyid}/proof": {
"post": {
"tags": [
"plugin-warranty"
],
"summary": "Upload or replace the proof-of-cover document (invoice, certificate)",
"description": "Upload or replace the proof-of-cover document (invoice, certificate)\n\n**Auth:** jwt + warranty.edit\n\n**Params:** multipart/form-data: file=<document> (pdf/png/jpg/gif/webp/tif/msg/eml/doc/docx/xls/xlsx), max 25MB\n\n**Example:**\n```\ncurl -X POST -H 'Authorization: Bearer <token>' -F file=@invoice.pdf http://localhost:5001/api/warranty/12/proof\n```",
"security": [
{
"bearerAuth": []
}
],
"responses": {
"200": {
"description": "Success (success_response envelope)"
}
}
},
"delete": {
"tags": [
"plugin-warranty"
],
"summary": "Remove a proof document and clear both columns",
"description": "Remove a proof document and clear both columns\n\n**Auth:** jwt + warranty.edit\n\n**Params:** none\n\n**Example:**\n```\ncurl -X DELETE -H 'Authorization: Bearer <token>' http://localhost:5001/api/warranty/12/proof\n```",
"security": [
{
"bearerAuth": []
}
],
"responses": {
"200": {
"description": "Success (success_response envelope)"
}
}
}
},
"/api/warranty/proof/{filename}": {
"get": {
"tags": [
"plugin-warranty"
],
"summary": "Download a proof document under the vendor's original filename. Authenticated: carries pricing and a service tag",
"description": "Download a proof document under the vendor's original filename. Authenticated: carries pricing and a service tag\n\n**Auth:** jwt + warranty.view\n\n**Params:** none\n\n**Example:**\n```\ncurl -OJ -H 'Authorization: Bearer <token>' http://localhost:5001/api/warranty/proof/warranty-12.pdf\n```",
"security": [
{
"bearerAuth": []
}
],
"responses": {
"200": {
"description": "Success (success_response envelope)"
}
}
}
}
}
}