Skip to content

Apps API

List Apps

Parameters

NameInTypeRequiredDescription
hostqueryinteger | nullno
qquerystring | nullno
statusquerystring | nullno

Responses

StatusDescriptionSchema
200Successful Responseany
422Validation ErrorHTTPValidationError

Uninstall App

RBAC: admin. Entitlement: apps.uninstall.

Remove an app, either by destroying its CT or by forgetting it.

Doc 01’s apps-only model means one app is exactly one LXC container, so “uninstall” is “destroy that container”. keep_ct is the escape hatch for the operator who wants Proxploy out of the way without losing the workload, and it is the inverse of adopt rather than a softer delete.

Returns 200, not 202, for both outcomes, unlike every other job-returning route in this API. keep_ct: true returns {removed: true, ct_kept: true} synchronously and leaves the container running on PVE. The default (destroy) path returns {job} (job kind app.uninstall), which is async despite the 200.

Parameters

NameInTypeRequiredDescription
app_idpathintegeryes

Request body, application/json, UninstallIn

FieldTypeRequiredDescription
confirmstring | nullno
keep_ctbooleanno

Responses

StatusDescriptionSchema
200Successful Responseany
422Validation ErrorHTTPValidationError
404app not found
409confirm_required: confirm must equal the app’s current name

App Detail

Parameters

NameInTypeRequiredDescription
app_idpathintegeryes

Responses

StatusDescriptionSchema
200Successful Responseany
422Validation ErrorHTTPValidationError

Reconfigure App

RBAC: operator. Entitlement: apps.reconfigure.

Resize a CT and/or edit how Proxploy presents the app.

Resource changes go straight to PVE rather than through a job: an lxc config write is synchronous there (see guest_config_update), so there is no task to track and reporting one would be theatre.

Disk size is deliberately not here. Growing a CT’s root volume is a different PVE endpoint and is one-way (PVE cannot shrink), which makes it its own feature with its own confirmation rather than a field on a PATCH.

cores/memory/swap are VM.Config.CPU/Memory, lifecycle privileges, so the client below asks for “lifecycle” explicitly: this call site defaulted to whatever client_for_host resolved before per-capability tokens existed, which worked only because the one token in play was over-scoped. Found during the sweep (host-token-privileges-step-one- report.md), same class of gap as Sys.PowerMgmt.

Parameters

NameInTypeRequiredDescription
app_idpathintegeryes

Request body, application/json, ReconfigureIn

FieldTypeRequiredDescription
coresinteger | nullno
memory_mbinteger | nullno
namestring | nullno
swap_mbinteger | nullno
web_pathstring | nullno
web_portinteger | nullno
web_protocolstring | nullno

Responses

StatusDescriptionSchema
200Successful Responseany
422Validation ErrorHTTPValidationError
404app not found
409app has no host
422validation error, or nothing to change
502pve_error

App Lifecycle

Parameters

NameInTypeRequiredDescription
app_idpathintegeryes
actionpathstringyes

Request body, application/json, LifecycleIn

FieldTypeRequiredDescription
confirmstring | nullno

Responses

StatusDescriptionSchema
202Successful Responseany
422Validation ErrorHTTPValidationError

App Logs

Doc 05: ‘Recent CT log lines (journal tail via pct exec / console channel)’. No such exec/journal channel exists anywhere in this codebase yet; services/lifecycle.py and executor/ only ever run install/update scripts over SSH on the HOST, never a command inside a guest CT, and ProxmoxClient has no pct-exec-equivalent call. Rather than fabricate log lines, this is a real, deliberate 501 so the frontend can render an honest gap (see AppLogs) instead of silently polling a 404 forever.

Parameters

NameInTypeRequiredDescription
app_idpathintegeryes

Responses

StatusDescriptionSchema
200Successful Responseany
422Validation ErrorHTTPValidationError

Migrate App Route

Params handed to the job are ONLY app_id/target_host_id: strategy, target ctid and shared storage all come from a FRESH preflight the handler itself runs, never from this route’s own preflight call below; state (host connectivity, storage, capacity) can change in the gap between this request and the job actually running (Task 15 interfaces note).

Parameters

NameInTypeRequiredDescription
app_idpathintegeryes

Request body (required), application/json, MigrateIn

FieldTypeRequiredDescription
confirmstring | nullno
storagestring | nullno
target_host_idintegeryes

Responses

StatusDescriptionSchema
202Successful Responseany
422Validation ErrorHTTPValidationError

POST /api/v1/apps/{app_id}/migrate/preflight

Section titled “POST /api/v1/apps/{app_id}/migrate/preflight”

Migrate Preflight

Parameters

NameInTypeRequiredDescription
app_idpathintegeryes

Request body (required), application/json, MigratePreflightIn

FieldTypeRequiredDescription
storagestring | nullno
target_host_idintegeryes

Responses

StatusDescriptionSchema
200Successful Responseany
422Validation ErrorHTTPValidationError

App Network

Parameters

NameInTypeRequiredDescription
app_idpathintegeryes

Responses

StatusDescriptionSchema
200Successful Responseany
422Validation ErrorHTTPValidationError

App Network Update

Parameters

NameInTypeRequiredDescription
app_idpathintegeryes
ifacepathstringyes

Request body (required), application/json, NicIn

FieldTypeRequiredDescription
bridgestring | nullno
firewallboolean | nullno
gwstring | nullno
gw6string | nullno
ipstring | nullno
ip6string | nullno
link_downboolean | nullno
mtuinteger | nullno
ratenumber | nullno
taginteger | nullno

Responses

StatusDescriptionSchema
200Successful Responseany
422Validation ErrorHTTPValidationError

App Ports

Parameters

NameInTypeRequiredDescription
app_idpathintegeryes

Responses

StatusDescriptionSchema
200Successful Response

Get App Script

Parameters

NameInTypeRequiredDescription
app_idpathintegeryes

Responses

StatusDescriptionSchema
200Successful Responseany
422Validation ErrorHTTPValidationError

Put App Script

Parameters

NameInTypeRequiredDescription
app_idpathintegeryes

Request body (required), application/json, ScriptIn

FieldTypeRequiredDescription
contentstringyes

Responses

StatusDescriptionSchema
200Successful Responseany
422Validation ErrorHTTPValidationError

Revert App Script

Task 5 review found a dead end: put_app_script above always writes source="edited", and nothing else ever writes source="upstream" except the install/update job handlers, so once an app’s script is edited, services/appstore.py::_resolve_update’s edited-script guard blocks app.update FOREVER, even if the operator pastes the exact upstream text back (there was no way to re-mark a row “upstream”). This route is that way back: pin a NEW version to the catalog’s CURRENT install_script, sourced “upstream”, so pinned_ref reads the catalog sha again and the guard clears.

Never mutates or deletes the edited row being reverted from: the version history is the record, same rule put_app_script already follows.

Parameters

NameInTypeRequiredDescription
app_idpathintegeryes

Responses

StatusDescriptionSchema
200Successful Responseany
422Validation ErrorHTTPValidationError

List App Script Versions

Parameters

NameInTypeRequiredDescription
app_idpathintegeryes

Responses

StatusDescriptionSchema
200Successful Responseany
422Validation ErrorHTTPValidationError

Get App Update

What an update would do: which commit to which, and the script diff.

Doc 10 Phase 7 requires the same diff/consent surface install has, so the diff shown here is the SAME _diff_vs_upstream the Config tab renders: one implementation, one answer, no chance of the two disagreeing about what is about to run.

Unlike the Config tab’s GET /script (which always shows drift, including the rare case where a catalog refresh moves raw.install_script without the pinned commit changing), this route only surfaces a diff when there is an update TO show. A caller here is asking “what would POST .../update do”, and the honest answer when the app is already on the catalog’s commit is “nothing”, not a diff sourced from unrelated content drift.

An edited newest script (script_source == "edited") is reported as no update available at all, never a diff: upstream_ref is NULL on that row, so POST will refuse regardless of the catalog state (see update_app), and showing a populated diff_vs_upstream/update_available here would advertise an action POST is about to reject.

Parameters

NameInTypeRequiredDescription
app_idpathintegeryes

Responses

StatusDescriptionSchema
200Successful Responseany
422Validation ErrorHTTPValidationError

Update App

Root-consent gated, exactly like install (api/catalog.py::install_catalog_entry): this re-runs a community script as root on the node, and brief §8 says the honest thing is to make the operator say so out loud. Unlike install (admin-only), doc 05 grants this to operator; a lower bar than the catalog table above intentionally accepts, not an oversight to fix here.

Parameters

NameInTypeRequiredDescription
app_idpathintegeryes

Request body (required), application/json, proxploy__api__apps__UpdateIn

FieldTypeRequiredDescription
consentbooleanno

Responses

StatusDescriptionSchema
202Successful Responseany
422Validation ErrorHTTPValidationError

Resolve Web URL

Parameters

NameInTypeRequiredDescription
app_idpathintegeryes

Responses

StatusDescriptionSchema
200Successful Responseany
422Validation ErrorHTTPValidationError

Adopt Apps

Bulk-adopt pre-existing/discovered CTs as tracked apps (doc 05, Phase 4).

One commit for the whole batch: a mid-batch ux_apps_host_ctid conflict rolls back everything flushed so far in this request (nothing partially lands), and a single audit row covers the whole batch rather than one per item.

Request body (required), application/json, AdoptIn

FieldTypeRequiredDescription
itemsAdoptItem[]yes

Responses

StatusDescriptionSchema
200Successful Responseany
422Validation ErrorHTTPValidationError

Discovered

Pre-existing CTs not yet adopted (doc 05). Read-only until Phase 4.

Two Hosts can be two nodes of the SAME cluster; cluster_resources() returns the whole cluster from either one, so every host’s snapshot lists the same unadopted CT, each carrying node, the CT’s real owning node (already correct in the payload; see pollers/init.py). Deduped here by (cluster, ctid): a ctid is only unique WITHIN a cluster, so two different clusters (or two standalone hosts, see cluster_scope) can legitimately both have a CT 101 and both must be offered, and attributed to the Host actually registered at that node, not whichever host happened to poll it. An already-tracked App’s own poll cycle only checks its own host_id (mapped_ctids is host-scoped), so a CT adopted on one host still shows up as discovered in another host’s snapshot of the SAME cluster; checking every App row here, scoped the same way, is what keeps it from being offered for adoption twice.

Responses

StatusDescriptionSchema
200Successful Responseany

Update All Apps

One app.update job per stale app (doc 05: “per-app results”).

No new queue machinery: JobBackend.MAX_CONCURRENT already runs four at a time and genuinely queues the rest, and each job carries its own status, transcript and result, which is what “per-app results” means.

skipped is not decoration. A bare “0 jobs started” is indistinguishable from a broken endpoint, so every app that did not get a job says why.

Reuses _update_state and mirrors POST /{app_id}/update’s own skip order exactly, so a bulk run and a single-app run never disagree about why a given app didn’t get a job:

  1. Edited script first: an edited row’s upstream_ref is NULL, so checking “no pinned script” before “edited” would misreport an edited app as having no upstream at all. Enqueueing anyway would spray a guaranteed-JobFailed job (services/appstore.py:: _resolve_update refuses to discard local edits), so this is skipped, not enqueued-to-fail.
  2. No catalog entry / no upstream_sha / no pinned script at all.
  3. Already on the catalog’s current commit.

Request body (required), application/json, proxploy__api__apps__UpdateIn

FieldTypeRequiredDescription
consentbooleanno

Responses

StatusDescriptionSchema
202Successful Responseany
422Validation ErrorHTTPValidationError