Applications API

Applications: drafts and published versions, planning and deploying a version, start, shut down, stop, restart and migrate, status and events, networks, and volumes with their snapshots, backups and schedules. This page lists 32 operations.

Set variables in the API overview. Download OpenAPI.

Operations

OperationWhat it does
GET /api/v1/applicationsApplications the caller may see, each with design status, derived runtime status, replica health, and the actions they may take now.
POST /api/v1/applicationsCreates an application in an environment, optionally with its first draft.
GET /api/v1/applications/{name}One application with its modules (desired and observed), versions and recent operations.
PATCH /api/v1/applications/{name}Changes an application's description or icon (its name cannot change).
POST /api/v1/applications/{name}/archiveArchives an application that is not running; it leaves the default list and keeps its data.
POST /api/v1/applications/{name}/deploymentsDeploys the design: saves it as a new revision and queues the planned changes.
GET /api/v1/applications/{name}/draftThe application's working document.
PUT /api/v1/applications/{name}/draftSaves the draft as the next version.
GET /api/v1/applications/{name}/eventsWhat happened to the application: creation, saves, versions, deployments, operations, refusals.
GET /api/v1/applications/{name}/migrationDry run of migrating the application to another server: for each instance whether it keeps running, what is copied and what blocks it.
GET /api/v1/applications/{name}/operationsRecent operations of the application, newest first.
POST /api/v1/applications/{name}/operationsStarts, shuts down (graceful), stops (immediate), restarts or migrates the application's instances, in dependency order where that matters, as an asynchronous operation.
GET /api/v1/applications/{name}/operations/{id}One operation with the progress of each phase and instance.
POST /api/v1/applications/{name}/planWhat deploying the design would change, without changing anything.
POST /api/v1/applications/{name}/restoreBrings an archived application back.
GET /api/v1/applications/{name}/statusThe application's instances as Incus reports them, by module.
GET /api/v1/applications/{name}/versionsVersions of the application, newest first.
POST /api/v1/applications/{name}/versionsReleases the current draft as the next immutable published version.
GET /api/v1/applications/{name}/versions/{revision}The document of one version.
GET /api/v1/applications/{name}/volumesThe application's volumes, with what Incus says about each now.
PATCH /api/v1/applications/{name}/volumes/{volume}Grows one of the application's volumes, also while its instance runs.
DELETE /api/v1/applications/{name}/volumes/{volume}Deletes one of the application's volumes and its snapshots, for good.
GET /api/v1/applications/{name}/volumes/{volume}/backupsThe volume's backups, oldest first, whether each still exists, and its schedules.
POST /api/v1/applications/{name}/volumes/{volume}/backupsBacks the volume up now: a copy, through a short-lived snapshot, into the backup pool on another member.
DELETE /api/v1/applications/{name}/volumes/{volume}/backups/{backup}Deletes one backup copy.
GET /api/v1/applications/{name}/volumes/{volume}/backups/{backup}/restorationDry run of restoring the volume from one of its backups: what is replaced (data written since the backup, the volume's snapshots), which instances must be stopped, where the safety backup goes, and the steps.
POST /api/v1/applications/{name}/volumes/{volume}/backups/{backup}/restoreRestores the volume from one of its backups, after the dry run passes again and the volume's name is typed: a safety backup of the current data first (unless declined), then the backup is copied over the volume.
PUT /api/v1/applications/{name}/volumes/{volume}/schedulesSets or turns off the volume's snapshot and backup schedules.
GET /api/v1/applications/{name}/volumes/{volume}/snapshotsThe volume's snapshots, oldest first.
POST /api/v1/applications/{name}/volumes/{volume}/snapshotsTakes a snapshot of the volume, named after the time (snap-YYYYMMDD-HHMMSS).
DELETE /api/v1/applications/{name}/volumes/{volume}/snapshots/{snapshot}Deletes one snapshot of the volume.
POST /api/v1/applications/{name}/volumes/{volume}/snapshots/{snapshot}/restorePuts the volume back to a snapshot: everything written since is lost.

Details

GET /api/v1/applications

Applications the caller may see, each with design status, derived runtime status, replica health, and the actions they may take now. Needs application.view.

Operation ID: applications

Needs: application.view.

ParameterInTypeRequiredDefaultMeaning
includeArchivedquerybooleannofalseAlso list archived applications.

Example request. Replace sample names, IDs and body values with resources and settings from your installation.

curl --fail-with-body -sS -X GET "$APELORA_URL/api/v1/applications" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json"

With options (includeArchived):

curl --fail-with-body -sS -X GET "$APELORA_URL/api/v1/applications?includeArchived=true" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json"

TypeScript:

const result = await client.request("applications", { query: { includeArchived: true } });

Response 200 — Done; the response holds the result..

Content-Type: application/json.

Example response from a test installation; your IDs, names and times differ.

{
  "items": [
    {
      "name": "blog",
      "latestRevision": null,
      "createdAt": "2026-10-28T23:26:49.745226Z",
      "updatedAt": "2026-10-28T23:26:49.745226Z",
      "description": "Company blog",
      "icon": "box",
      "project": {
        "id": "deff5baa-28d1-4534-b980-108a88f9fa09",
        "slug": "shop",
        "name": "Online shop",
        "organizationId": "14c169c3-7c7f-4898-9428-a78d1da537fe",
        "organizationSlug": "acme"
      },
      "environment": {
        "id": "203fa176-c365-4d3f-92c3-7ae48b5be87d",
        "slug": "staging",
        "name": "Staging",
        "kind": "staging",
        "incusProject": "acme-shop-staging"
      },
      "archived": false,
      "design": {
        "status": "draft",
        "draftVersion": null,
        "validationErrors": 0,
        "publishedVersion": null,
        "hasUnpublishedChanges": false,
        "updatedAt": null
      },
      "deployment": {
        "status": "not-deployed",
        "observedAt": "2026-10-28T23:26:50.061133Z",
        "stale": false,
        "source": "mock",
        "deployedVersion": null,
        "modules": 0,
        "replicas": {
          "desired": 0,
          "running": 0,
          "healthy": 0,
          "unhealthy": 0
        },
        "lastActivityAt": null,
        "operation": null
      },
      "actions": {
        "start": {
          "allowed": false,
          "reason": "deploy a published version first"
        },
        "stop": {
          "allowed": false,
          "reason": "deploy a published version first"
        },
        "shutdown": {
          "allowed": false,
          "reason": "deploy a published version first"
        },
        "restart": {
          "allowed": false,
          "reason": "deploy a published version first"
        },
        "migrate": {
          "allowed": false,
          "reason": "deploy a published version first"
        }
      }
    },
    {
      "name": "shop",
      "latestRevision": 1,
      "createdAt": "2026-10-28T23:26:49.677497Z",
      "updatedAt": "2026-10-28T23:26:49.853282Z",
      "description": "Storefront, API and order database",
      "icon": "cart",
      "project": {
        "id": "deff5baa-28d1-4534-b980-108a88f9fa09",
        "slug": "shop",
        "name": "Online shop",
        "organizationId": "14c169c3-7c7f-4898-9428-a78d1da537fe",
        "organizationSlug": "acme"
      },
      "environment": {
        "id": "897a4582-0cc0-4132-b2e6-523daef9858f",
        "slug": "production",
        "name": "Production",
        "kind": "production",
        "incusProject": "shop-prod"
      },
      "archived": false,
      "design": {
        "status": "published",
        "draftVersion": 1,
        "validationErrors": 0,
        "publishedVersion": 1,
        "hasUnpublishedChanges": false,
        "updatedAt": "2026-10-28T23:26:49.853282Z"
      },
      "deployment": {
        "status": "degraded",
        "observedAt": "2026-10-28T23:26:50.061133Z",
        "stale": false,
        "source": "mock",
        "deployedVersion": 1,
        "modules": 3,
        "replicas": {
          "desired": 3,
          "running": 0,
          "healthy": 0,
          "unhealthy": 0
        },
        "lastActivityAt": "2026-10-28T23:26:50.028242Z",
        "operation": null
      },
      "actions": {
        "start": {
          "allowed": true,
          "reason": null
        },
        "stop": {
          "allowed": false,
          "reason": "nothing is running"
        },
        "shutdown": {
          "allowed": false,
          "reason": "nothing is running"
        },
        "restart": {
          "allowed": false,
          "reason": "nothing is running"
        },
        "migrate": {
          "allowed": true,
          "reason": null
        }
      }
    }
  ],
  "observedAt": "2026-10-28T23:26:50.061133Z",
  "stale": false
}

Errors. The body is {"error": {"code": …, "message": …}}; see error codes for the codes every operation shares.

StatusCodes
401unauthenticated, invalid_token
403forbidden, mfa_required
429rate_limited
503database_not_configured

POST /api/v1/applications

Creates an application in an environment, optionally with its first draft. Needs application.create on the environment's project.

Operation ID: create_application

Needs: application.create on the environment's project.

Request body (JSON):

FieldTypeRequiredMeaning
descriptionstringno—
documentobject or nullnoThe first draft (a Studio document), if any.
environmentIdstring (uuid)yesThe environment (and so the project) the application lives in.
iconstring or nullnoIcon key; box when omitted.
namestringyesLowercase letters, digits and hyphens, starting with a letter; at most 40 characters. Fixed for the life of the application (instances are named after it).
{
  "name": "shop",
  "environmentId": "897a4582-0cc0-4132-b2e6-523daef9858f",
  "description": "Storefront and order database",
  "icon": "cart"
}

Example request. Replace sample names, IDs and body values with resources and settings from your installation.

This request can change state. Check permissions, prerequisites and any required approval before running it.

curl --fail-with-body -sS -X POST "$APELORA_URL/api/v1/applications" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  --data-raw '{"name":"shop","environmentId":"897a4582-0cc0-4132-b2e6-523daef9858f","description":"Storefront and order database","icon":"cart"}'

TypeScript:

const result = await client.request("create_application", { body /* the request body above */ });

Response 201 — Created; the response describes what was created..

Content-Type: application/json.

Example response from a test installation; your IDs, names and times differ.

{
  "name": "shop",
  "latestRevision": null,
  "createdAt": "2026-10-28T23:26:49.677497Z",
  "updatedAt": "2026-10-28T23:26:49.677497Z",
  "description": "Storefront and order database",
  "icon": "cart",
  "project": {
    "id": "deff5baa-28d1-4534-b980-108a88f9fa09",
    "slug": "shop",
    "name": "Online shop",
    "organizationId": "14c169c3-7c7f-4898-9428-a78d1da537fe",
    "organizationSlug": "acme"
  },
  "environment": {
    "id": "897a4582-0cc0-4132-b2e6-523daef9858f",
    "slug": "production",
    "name": "Production",
    "kind": "production",
    "incusProject": "shop-prod"
  },
  "archived": false,
  "design": {
    "status": "draft",
    "draftVersion": null,
    "validationErrors": 0,
    "publishedVersion": null,
    "hasUnpublishedChanges": false,
    "updatedAt": null
  },
  "deployment": {
    "status": "not-deployed",
    "observedAt": "2026-10-28T23:26:49.709117Z",
    "stale": false,
    "source": "mock",
    "deployedVersion": null,
    "modules": 0,
    "replicas": {
      "desired": 0,
      "running": 0,
      "healthy": 0,
      "unhealthy": 0
    },
    "lastActivityAt": null,
    "operation": null
  },
  "actions": {
    "start": {
      "allowed": false,
      "reason": "deploy a published version first"
    },
    "stop": {
      "allowed": false,
      "reason": "deploy a published version first"
    },
    "shutdown": {
      "allowed": false,
      "reason": "deploy a published version first"
    },
    "restart": {
      "allowed": false,
      "reason": "deploy a published version first"
    },
    "migrate": {
      "allowed": false,
      "reason": "deploy a published version first"
    }
  }
}

Errors. The body is {"error": {"code": …, "message": …}}; see error codes for the codes every operation shares.

StatusCodes
400invalid_request
401unauthenticated, invalid_token
403forbidden, mfa_required
404not_found
409draft_conflict
429rate_limited
503database_not_configured

GET /api/v1/applications/{name}

One application with its modules (desired and observed), versions and recent operations. Needs application.view.

Operation ID: application

Needs: application.view.

ParameterInTypeRequiredDefaultMeaning
namepathstringyes—Application name

Example request. Replace sample names, IDs and body values with resources and settings from your installation.

curl --fail-with-body -sS -X GET "$APELORA_URL/api/v1/applications/shop" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json"

TypeScript:

const result = await client.request("application", { params: { name: "shop" } });

Response 200 — Done; the response holds the result..

Content-Type: application/json.

Example response from a test installation; your IDs, names and times differ. Long lists are cut to their first items.

{
  "application": {
    "name": "shop",
    "latestRevision": 1,
    "createdAt": "2026-10-28T23:26:49.677497Z",
    "updatedAt": "2026-10-28T23:26:49.853282Z",
    "description": "Storefront, API and order database",
    "icon": "cart",
    "project": {
      "id": "deff5baa-28d1-4534-b980-108a88f9fa09",
      "slug": "shop",
      "name": "Online shop",
      "organizationId": "14c169c3-7c7f-4898-9428-a78d1da537fe",
      "organizationSlug": "acme"
    },
    "environment": {
      "id": "897a4582-0cc0-4132-b2e6-523daef9858f",
      "slug": "production",
      "name": "Production",
      "kind": "production",
      "incusProject": "shop-prod"
    },
    "archived": false,
    "design": {
      "status": "published",
      "draftVersion": 1,
      "validationErrors": 0,
      "publishedVersion": 1,
      "hasUnpublishedChanges": false,
      "updatedAt": "2026-10-28T23:26:49.853282Z"
    },
    "deployment": {
      "status": "degraded",
      "observedAt": "2026-10-28T23:26:50.130786Z",
      "stale": false,
      "source": "mock",
      "deployedVersion": 1,
      "modules": 3,
      "replicas": {
        "desired": 3,
        "running": 0,
        "healthy": 0,
        "unhealthy": 0
      },
      "lastActivityAt": "2026-10-28T23:26:50.028242Z",
      "operation": null
    },
    "actions": {
      "start": {
        "allowed": true,
        "reason": null
      },
      "stop": {
        "allowed": false,
        "reason": "nothing is running"
      },
      "shutdown": {
        "allowed": false,
        "reason": "nothing is running"
      },
      "restart": {
        "allowed": false,
        "reason": "nothing is running"
      },
      "migrate": {
        "allowed": true,
        "reason": null
      }
    }
  },
  "modules": [
    {
      "module": "web-01",
      "replicas": 1,
      "availability": "stateless",
      "instances": []
    },
    {
      "module": "db-01",
      "replicas": 1,
      "availability": "stateful",
      "instances": []
    }
  ],
  "versions": [
    {
      "revision": 1,
      "published": true,
      "label": "First release",
      "draftVersion": 1,
      "createdBy": "token:docs-verifier",
      "createdAt": "2026-10-28T23:26:49.891746Z"
    }
  ],
  "operations": []
}

Errors. The body is {"error": {"code": …, "message": …}}; see error codes for the codes every operation shares.

StatusCodes
400invalid_request
401unauthenticated, invalid_token
403forbidden, mfa_required
404not_found
429rate_limited
503database_not_configured

PATCH /api/v1/applications/{name}

Changes an application's description or icon (its name cannot change). Needs application.design.

Operation ID: update_application

Needs: application.design.

ParameterInTypeRequiredDefaultMeaning
namepathstringyes—Application name

Request body (JSON):

FieldTypeRequiredMeaning
descriptionstring or nullno—
iconstring or nullno—
{
  "description": "Storefront, API and order database"
}

Example request. Replace sample names, IDs and body values with resources and settings from your installation.

This request can change state. Check permissions, prerequisites and any required approval before running it.

curl --fail-with-body -sS -X PATCH "$APELORA_URL/api/v1/applications/shop" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  --data-raw '{"description":"Storefront, API and order database"}'

TypeScript:

const result = await client.request("update_application", { params: { name: "shop" }, body: { description: "Storefront, API and order database" } });

Response 200 — Done; the response holds the result..

Content-Type: application/json.

Example response from a test installation; your IDs, names and times differ.

{
  "name": "shop",
  "latestRevision": null,
  "createdAt": "2026-10-28T23:26:49.677497Z",
  "updatedAt": "2026-10-28T23:26:49.790589Z",
  "description": "Storefront, API and order database",
  "icon": "cart",
  "project": {
    "id": "deff5baa-28d1-4534-b980-108a88f9fa09",
    "slug": "shop",
    "name": "Online shop",
    "organizationId": "14c169c3-7c7f-4898-9428-a78d1da537fe",
    "organizationSlug": "acme"
  },
  "environment": {
    "id": "897a4582-0cc0-4132-b2e6-523daef9858f",
    "slug": "production",
    "name": "Production",
    "kind": "production",
    "incusProject": "shop-prod"
  },
  "archived": false,
  "design": {
    "status": "draft",
    "draftVersion": null,
    "validationErrors": 0,
    "publishedVersion": null,
    "hasUnpublishedChanges": false,
    "updatedAt": null
  },
  "deployment": {
    "status": "not-deployed",
    "observedAt": "2026-10-28T23:26:49.807414Z",
    "stale": false,
    "source": "mock",
    "deployedVersion": null,
    "modules": 0,
    "replicas": {
      "desired": 0,
      "running": 0,
      "healthy": 0,
      "unhealthy": 0
    },
    "lastActivityAt": null,
    "operation": null
  },
  "actions": {
    "start": {
      "allowed": false,
      "reason": "deploy a published version first"
    },
    "stop": {
      "allowed": false,
      "reason": "deploy a published version first"
    },
    "shutdown": {
      "allowed": false,
      "reason": "deploy a published version first"
    },
    "restart": {
      "allowed": false,
      "reason": "deploy a published version first"
    },
    "migrate": {
      "allowed": false,
      "reason": "deploy a published version first"
    }
  }
}

Errors. The body is {"error": {"code": …, "message": …}}; see error codes for the codes every operation shares.

StatusCodes
400invalid_request
401unauthenticated, invalid_token
403forbidden, mfa_required
404not_found
429rate_limited
503database_not_configured

POST /api/v1/applications/{name}/archive

Archives an application that is not running; it leaves the default list and keeps its data. Needs application.delete.

Operation ID: archive_application

Needs: application.delete.

ParameterInTypeRequiredDefaultMeaning
namepathstringyes—Application name

Example request. Replace sample names, IDs and body values with resources and settings from your installation.

This request can change state. Check permissions, prerequisites and any required approval before running it.

curl --fail-with-body -sS -X POST "$APELORA_URL/api/v1/applications/blog/archive" \
  -H "Authorization: Bearer $APELORA_TOKEN"

TypeScript:

await client.request("archive_application", { params: { name: "blog" } });

Response 204 — Done; nothing is returned., with no body.

Errors. The body is {"error": {"code": …, "message": …}}; see error codes for the codes every operation shares.

StatusCodes
400invalid_request
401unauthenticated, invalid_token
403forbidden, mfa_required
404not_found
409still_running
429rate_limited
503database_not_configured

POST /api/v1/applications/{name}/deployments

Deploys the design: saves it as a new revision and queues the planned changes. Refuses the whole deployment if any module conflicts with an existing instance. Needs deploy.

Operation ID: deploy_application

Needs: deploy.

ParameterInTypeRequiredDefaultMeaning
namepathstringyes—Application name
Idempotency-Keyheaderstringno—Makes the request safe to repeat

Request body (JSON):

FieldTypeRequiredMeaning
confirmDisruptionbooleannoRequired when the plan rebuilds a singleton or stateful module (rebuild): it acknowledges that the module is stopped while it is rebuilt. Data on volumes is kept.
connectionslist of object or nullnoThe design's connections; enforced when the control plane has a network uplink.
connections[].fromstringyes—
connections[].portinteger or nullnoNone: any port of the transport (the protocol has no well-known port).
connections[].tostringyes—
connections[].transportstringyestcp or udp.
documentobjectyesThe Studio document, saved as an immutable revision. Ignored when version is given.
moduleslist of objectyes—
modules[].availabilityone of stateless, singleton, statefulnoHow a module may be run and recovered (from its catalog class).
modules[].dnsResolverbooleannoThis module answers service names for the application. Absent means it does not: a resolver is never added unless the design asks for one.
modules[].guestOsone of linux, windowsnoThe operating system inside (from the class): decides which sessions it offers.
modules[].internetone of none, web, anynoWhat it may reach outside the application (enforced with the connections).
modules[].modulestringyesThe module's stable id in the design (e.g. nginx-01).
modules[].readinessobject or nullno—
modules[].replicasintegernoHow many instances (1–9); more than one only for stateless modules.
modules[].specobjectyesImage, kind and settings; the control plane sets the name, project and owner.
projectstringyes—
publishedlist of objectnoPorts reachable from outside the application (needs connections to be enforced).
published[].modulestringyes—
published[].portintegeryes—
published[].transportstringyestcp or udp.
removeMissingbooleannoAlso delete the application's instances that are no longer in the design.
versioninteger or nullnoDeploy this published version (its stored document) instead of saving document as a new revision. Drafts can never be deployed.
{
  "project": "shop-prod",
  "document": {
    "apiVersion": "fabric.apelora/v1alpha1",
    "kind": "ApplicationAssembly",
    "metadata": {
      "name": "shop",
      "uid": "assembly-shop"
    },
    "spec": {
      "modules": [],
      "connections": []
    }
  },
  "modules": [
    {
      "module": "web-01",
      "replicas": 1,
      "availability": "stateless",
      "spec": {
        "project": "shop-prod",
        "name": "shop-web-01",
        "kind": "container",
        "start": true,
        "source": {
          "protocol": "oci",
          "server": "https://docker.io",
          "alias": "library/nginx@sha256:eb1c896deef834f4e056829fd6dadac8305501f16cc71ba65fb252c8d0aa168e"
        },
        "architectures": [
          "x86_64",
          "aarch64"
        ],
        "config": {
          "limits.cpu": "1",
          "limits.memory": "512MiB"
        }
      }
    },
    {
      "module": "db-01",
      "replicas": 1,
      "availability": "stateful",
      "spec": {
        "project": "shop-prod",
        "name": "shop-db-01",
        "kind": "container",
        "start": true,
        "source": {
          "protocol": "simplestreams",
          "server": "https://images.linuxcontainers.org",
          "alias": "debian/13"
        },
        "architectures": [
          "x86_64",
          "aarch64"
        ],
        "config": {
          "limits.cpu": "2",
          "limits.memory": "2GiB"
        },
        "volumes": [
          {
            "name": "data",
            "sizeMib": 10240,
            "path": "/var/lib/data"
          }
        ]
      }
    },
    {
      "module": "cache-01",
      "replicas": 1,
      "availability": "stateful",
      "spec": {
        "project": "shop-prod",
        "name": "shop-cache-01",
        "kind": "container",
        "start": true,
        "source": {
          "protocol": "simplestreams",
          "server": "https://images.linuxcontainers.org",
          "alias": "debian/13"
        },
        "architectures": [
          "x86_64",
          "aarch64"
        ],
        "config": {
          "limits.cpu": "1",
          "limits.memory": "1GiB"
        },
        "volumes": [
          {
            "name": "cache",
            "sizeMib": 2048,
            "path": "/var/cache/shop"
          }
        ]
      }
    }
  ],
  "connections": [
    {
      "from": "web-01",
      "to": "db-01",
      "transport": "tcp",
      "port": 5432
    },
    {
      "from": "web-01",
      "to": "cache-01",
      "transport": "tcp",
      "port": 6379
    }
  ],
  "published": [
    {
      "module": "web-01",
      "transport": "tcp",
      "port": 80
    }
  ],
  "removeMissing": false,
  "version": 1
}

Example request. Replace sample names, IDs and body values with resources and settings from your installation.

This request can change state. Check permissions, prerequisites and any required approval before running it.

# Generate once per distinct change; keep this value when retrying.
IDEMPOTENCY_KEY=$(uuidgen)
curl --fail-with-body -sS -X POST "$APELORA_URL/api/v1/applications/shop/deployments" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -H "Content-Type: application/json" \
  --data-raw '{"project":"shop-prod","document":{"apiVersion":"fabric.apelora/v1alpha1","kind":"ApplicationAssembly","metadata":{"name":"shop","uid":"assembly-shop"},"spec":{"modules":[],"connections":[]}},"modules":[{"module":"web-01","replicas":1,"availability":"stateless","spec":{"project":"shop-prod","name":"shop-web-01","kind":"container","start":true,"source":{"protocol":"oci","server":"https://docker.io","alias":"library/nginx@sha256:eb1c896deef834f4e056829fd6dadac8305501f16cc71ba65fb252c8d0aa168e"},"architectures":["x86_64","aarch64"],"config":{"limits.cpu":"1","limits.memory":"512MiB"}}},{"module":"db-01","replicas":1,"availability":"stateful","spec":{"project":"shop-prod","name":"shop-db-01","kind":"container","start":true,"source":{"protocol":"simplestreams","server":"https://images.linuxcontainers.org","alias":"debian/13"},"architectures":["x86_64","aarch64"],"config":{"limits.cpu":"2","limits.memory":"2GiB"},"volumes":[{"name":"data","sizeMib":10240,"path":"/var/lib/data"}]}},{"module":"cache-01","replicas":1,"availability":"stateful","spec":{"project":"shop-prod","name":"shop-cache-01","kind":"container","start":true,"source":{"protocol":"simplestreams","server":"https://images.linuxcontainers.org","alias":"debian/13"},"architectures":["x86_64","aarch64"],"config":{"limits.cpu":"1","limits.memory":"1GiB"},"volumes":[{"name":"cache","sizeMib":2048,"path":"/var/cache/shop"}]}}],"connections":[{"from":"web-01","to":"db-01","transport":"tcp","port":5432},{"from":"web-01","to":"cache-01","transport":"tcp","port":6379}],"published":[{"module":"web-01","transport":"tcp","port":80}],"removeMissing":false,"version":1}'

TypeScript:

const result = await client.request("deploy_application", { params: { name: "shop" }, body /* the request body above */, idempotencyKey: newIdempotencyKey() });

Response 202 — Accepted: the work is queued; the response says how to follow it..

Content-Type: application/json.

Example response from a test installation; your IDs, names and times differ. Long lists are cut to their first items.

{
  "application": "shop",
  "revision": 1,
  "plan": {
    "items": [
      {
        "module": "web-01",
        "replica": 1,
        "instance": "shop-web-01",
        "action": "create",
        "reason": "not deployed yet"
      },
      {
        "module": "db-01",
        "replica": 1,
        "instance": "shop-db-01",
        "action": "create",
        "reason": "not deployed yet"
      }
    ],
    "warnings": [
      "single points of failure: cache-01, db-01, web-01 run as one instance",
      "not published: web-01 (publishing needs enforced connections and APELORA_SERVICE_ADDRESSES)",
      "connections are not enforced: the control plane has no network uplink (APELORA_NETWORK_UPLINK); instances share the project's network"
    ],
    "quota": {
      "allowed": true,
      "adding": {
        "instances": 3,
        "cpu": 4,
        "memoryBytes": 3758096384,
        "diskBytes": 45097156608
      },
      "scopes": [
        {
          "kind": "project",
          "id": "deff5baa-28d1-4534-b980-108a88f9fa09",
          "name": "Online shop",
          "dimensions": [
            {
              "dimension": "instances",
              "used": 0,
              "adding": 3,
              "limit": 20,
              "exceeded": false
            },
            {
              "dimension": "cpu",
              "used": 0,
              "adding": 4,
              "limit": 16,
              "exceeded": false
            }
          ]
        }
      ]
    }
  },
  "jobs": [
    {
      "module": "web-01",
      "instance": "shop-web-01",
      "action": "create",
      "jobId": "2fd2f28c-1b95-42ea-96d0-e93e6b265cf6"
    },
    {
      "module": "db-01",
      "instance": "shop-db-01",
      "action": "create",
      "jobId": "78f29588-65a5-4735-b3db-16e4361d8ebd"
    }
  ],
  "networkJob": null
}

Errors. The body is {"error": {"code": …, "message": …}}; see error codes for the codes every operation shares.

StatusCodes
400invalid_request
401unauthenticated, invalid_token
403project_not_allowed, forbidden, mfa_required
404not_found
409confirmation_required, incus_project_exists, not_published, operation_in_progress, plan_conflict, project_mismatch, quota_exceeded, service_range_exhausted
429rate_limited
502an incus_… code
503incus_project_not_ready, quota_unverifiable, database_not_configured

GET /api/v1/applications/{name}/draft

The application's working document. 404 when it has none yet. Needs application.view.

Operation ID: application_draft

Needs: application.view.

ParameterInTypeRequiredDefaultMeaning
namepathstringyes—Application name

Example request. Replace sample names, IDs and body values with resources and settings from your installation.

curl --fail-with-body -sS -X GET "$APELORA_URL/api/v1/applications/shop/draft" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json"

TypeScript:

const result = await client.request("application_draft", { params: { name: "shop" } });

Response 200 — Done; the response holds the result..

Content-Type: application/json.

Example response from a test installation; your IDs, names and times differ.

{
  "version": 1,
  "document": {
    "apiVersion": "fabric.apelora/v1alpha1",
    "kind": "ApplicationAssembly",
    "metadata": {
      "name": "shop",
      "uid": "assembly-shop"
    },
    "spec": {
      "connections": [],
      "modules": []
    }
  },
  "validationErrors": [],
  "updatedBy": "token:docs-verifier",
  "updatedAt": "2026-10-28T23:26:49.853282Z"
}

Errors. The body is {"error": {"code": …, "message": …}}; see error codes for the codes every operation shares.

StatusCodes
400invalid_request
401unauthenticated, invalid_token
403forbidden, mfa_required
404no_draft, not_found
429rate_limited
503database_not_configured

PUT /api/v1/applications/{name}/draft

Saves the draft as the next version. Refused with 409 draft_conflict (and the current version) when someone saved since baseVersion; nothing is overwritten. Needs application.design.

Operation ID: save_application_draft

Needs: application.design.

ParameterInTypeRequiredDefaultMeaning
namepathstringyes—Application name

Request body (JSON):

FieldTypeRequiredMeaning
baseVersioninteger or nullnoThe draft version this edit started from; omit only for the first save.
documentobjectyesA Studio document (the application assembly format the Studio imports and exports).
{
  "document": {
    "apiVersion": "fabric.apelora/v1alpha1",
    "kind": "ApplicationAssembly",
    "metadata": {
      "name": "shop",
      "uid": "assembly-shop"
    },
    "spec": {
      "modules": [],
      "connections": []
    }
  }
}

Example request. Replace sample names, IDs and body values with resources and settings from your installation.

This request can change state. Check permissions, prerequisites and any required approval before running it.

curl --fail-with-body -sS -X PUT "$APELORA_URL/api/v1/applications/shop/draft" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  --data-raw '{"document":{"apiVersion":"fabric.apelora/v1alpha1","kind":"ApplicationAssembly","metadata":{"name":"shop","uid":"assembly-shop"},"spec":{"modules":[],"connections":[]}}}'

TypeScript:

const result = await client.request("save_application_draft", { params: { name: "shop" }, body /* the request body above */ });

Response 200 — Done; the response holds the result..

Content-Type: application/json.

Example response from a test installation; your IDs, names and times differ.

{
  "version": 1,
  "document": {
    "apiVersion": "fabric.apelora/v1alpha1",
    "kind": "ApplicationAssembly",
    "metadata": {
      "name": "shop",
      "uid": "assembly-shop"
    },
    "spec": {
      "connections": [],
      "modules": []
    }
  },
  "validationErrors": [],
  "updatedBy": "token:docs-verifier",
  "updatedAt": "2026-10-28T23:26:49.853282Z"
}

Errors. The body is {"error": {"code": …, "message": …}}; see error codes for the codes every operation shares.

StatusCodes
400invalid_request
401unauthenticated, invalid_token
403forbidden, mfa_required
404not_found
409—
429rate_limited
503database_not_configured

GET /api/v1/applications/{name}/events

What happened to the application: creation, saves, versions, deployments, operations, refusals. Needs application.view.

Operation ID: application_events

Needs: application.view.

ParameterInTypeRequiredDefaultMeaning
namepathstringyes—Application name

Example request. Replace sample names, IDs and body values with resources and settings from your installation.

curl --fail-with-body -sS -X GET "$APELORA_URL/api/v1/applications/shop/events" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json"

TypeScript:

const result = await client.request("application_events", { params: { name: "shop" } });

Response 200 — Done; the response holds the result..

Content-Type: application/json.

Example response from a test installation; your IDs, names and times differ. Long lists are cut to their first items.

{
  "items": [
    {
      "id": 77,
      "at": "2026-10-28T23:26:50.028242Z",
      "actor": "token:docs-verifier",
      "actorType": "token",
      "actorName": "docs-verifier",
      "action": "application.deploy.requested",
      "target": "shop-prod/shop",
      "outcome": "success",
      "scope": {
        "application": "ba2bc39d-a895-44cf-88df-95f994325364",
        "organization": "14c169c3-7c7f-4898-9428-a78d1da537fe",
        "project": "deff5baa-28d1-4534-b980-108a88f9fa09"
      },
      "detail": {
        "confirmedDisruption": false,
        "create": 3,
        "keep": 0,
        "name": "docs-verifier",
        "operation": null,
        "rebuild": 0,
        "remove": 0,
        "replace": 0,
        "requestId": "61a2c794-bb69-44b8-92f8-b9d22d318604",
        "resize": 0,
        "revision": 1,
        "role": "fabric-admin"
      }
    },
    {
      "id": 76,
      "at": "2026-10-28T23:26:49.891746Z",
      "actor": "token:docs-verifier",
      "actorType": "token",
      "actorName": "docs-verifier",
      "action": "application.version.publish",
      "target": "shop",
      "outcome": "success",
      "scope": {
        "application": "ba2bc39d-a895-44cf-88df-95f994325364",
        "organization": "14c169c3-7c7f-4898-9428-a78d1da537fe",
        "project": "deff5baa-28d1-4534-b980-108a88f9fa09"
      },
      "detail": {
        "draftVersion": 1,
        "name": "docs-verifier",
        "requestId": "d98b557d-78a4-4ef0-b5da-ce8424b0305d",
        "revision": 1,
        "role": "fabric-admin"
      }
    }
  ],
  "next": null
}

Errors. The body is {"error": {"code": …, "message": …}}; see error codes for the codes every operation shares.

StatusCodes
400invalid_request
401unauthenticated, invalid_token
403forbidden, mfa_required
404not_found
429rate_limited
503database_not_configured

GET /api/v1/applications/{name}/migration

Dry run of migrating the application to another server: for each instance whether it keeps running, what is copied and what blocks it. Changes nothing. Needs application.operate.

Operation ID: preview_application_migration

Needs: application.operate.

ParameterInTypeRequiredDefaultMeaning
namepathstringyes—Application name
targetquerystringyes—The server to move the application to.

Example request. Replace sample names, IDs and body values with resources and settings from your installation.

curl --fail-with-body -sS -X GET "$APELORA_URL/api/v1/applications/shop/migration?target=mock-member-02" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json"

TypeScript:

const result = await client.request("preview_application_migration", { params: { name: "shop" }, query: { target: "mock-member-02" } });

Response 200 — Done; the response holds the result..

Content-Type: application/json.

Example response from a test installation; your IDs, names and times differ. Long lists are cut to their first items.

{
  "source": {
    "provider": "mock",
    "observedAt": "2026-10-28T23:26:52.711843Z"
  },
  "target": "mock-member-02",
  "items": [
    {
      "module": "cache-01",
      "instance": "shop-cache-01",
      "from": "mock-member-01",
      "alreadyThere": false,
      "live": false,
      "stops": true,
      "copyMib": 2048,
      "blocked": [],
      "notes": [
        "it stops for the move (a container cannot move while running) and starts again on mock-member-02",
        "1 volume (2048 MiB) copied to mock-member-02; the old copies are deleted once it runs there"
      ]
    },
    {
      "module": "db-01",
      "instance": "shop-db-01",
      "from": "mock-member-01",
      "alreadyThere": false,
      "live": false,
      "stops": true,
      "copyMib": 10240,
      "blocked": [],
      "notes": [
        "it stops for the move (a container cannot move while running) and starts again on mock-member-02",
        "1 volume (10240 MiB) copied to mock-member-02; the old copies are deleted once it runs there"
      ]
    }
  ],
  "possible": true,
  "stops": [
    "shop-cache-01",
    "shop-db-01",
    "shop-web-01"
  ]
}

Errors. The body is {"error": {"code": …, "message": …}}; see error codes for the codes every operation shares.

StatusCodes
400invalid_request
401unauthenticated, invalid_token
403project_not_allowed, forbidden, mfa_required
404not_found
409not_deployed
429rate_limited
502an incus_… code
503database_not_configured

GET /api/v1/applications/{name}/operations

Recent operations of the application, newest first. Needs application.view.

Operation ID: application_operations

Needs: application.view.

ParameterInTypeRequiredDefaultMeaning
namepathstringyes—Application name

Example request. Replace sample names, IDs and body values with resources and settings from your installation.

curl --fail-with-body -sS -X GET "$APELORA_URL/api/v1/applications/shop/operations" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json"

TypeScript:

const result = await client.request("application_operations", { params: { name: "shop" } });

Response 200 — Done; the response holds the result..

Content-Type: application/json.

Example response from a test installation; your IDs, names and times differ.

{
  "items": []
}

Errors. The body is {"error": {"code": …, "message": …}}; see error codes for the codes every operation shares.

StatusCodes
400invalid_request
401unauthenticated, invalid_token
403forbidden, mfa_required
404not_found
429rate_limited
503database_not_configured

POST /api/v1/applications/{name}/operations

Starts, shuts down (graceful), stops (immediate), restarts or migrates the application's instances, in dependency order where that matters, as an asynchronous operation. Succeeds only when Incus confirms each instance's state (and, for a migration, its new server). Needs application.operate on the application. It only touches instances this application owns; networks, volumes, data and other applications are left alone.

Operation ID: operate_application

Needs: application.operate on the application.

ParameterInTypeRequiredDefaultMeaning
namepathstringyes—Application name
Idempotency-Keyheaderstringno—Makes the request safe to repeat

Request body (JSON):

FieldTypeRequiredMeaning
actionone of start, stop, shutdown, restart, migrate, updateyes—
confirmDisruptionbooleannoRequired to stop or restart an application that has single-instance or stateful modules: it acknowledges that their clients lose service meanwhile.
targetstring or nullnoThe server to move the application to. Needed for migrate, ignored otherwise.
{
  "action": "stop",
  "confirmDisruption": true
}

Example request. Replace sample names, IDs and body values with resources and settings from your installation.

This request can change state. Check permissions, prerequisites and any required approval before running it.

# Generate once per distinct change; keep this value when retrying.
IDEMPOTENCY_KEY=$(uuidgen)
curl --fail-with-body -sS -X POST "$APELORA_URL/api/v1/applications/shop/operations" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -H "Content-Type: application/json" \
  --data-raw '{"action":"stop","confirmDisruption":true}'

TypeScript:

const result = await client.request("operate_application", { params: { name: "shop" }, body: { action: "stop", confirmDisruption: true }, idempotencyKey: newIdempotencyKey() });

Response 202 — Accepted: the work is queued; the response says how to follow it..

Content-Type: application/json.

Example response from a test installation; your IDs, names and times differ.

{
  "operation": {
    "id": "6fc89203-9d65-44b9-8008-03adef010024",
    "kind": "stop",
    "state": "pending",
    "requestedBy": "token:docs-verifier",
    "createdAt": "2026-10-28T23:26:54.431921Z",
    "startedAt": null,
    "finishedAt": null,
    "error": null,
    "phases": [
      {
        "action": "poweroff",
        "state": "pending",
        "steps": [
          {
            "instance": "shop-web-01",
            "module": "web-01",
            "state": "pending",
            "error": null
          }
        ]
      },
      {
        "action": "poweroff",
        "state": "pending",
        "steps": [
          {
            "instance": "shop-cache-01",
            "module": "cache-01",
            "state": "pending",
            "error": null
          },
          {
            "instance": "shop-db-01",
            "module": "db-01",
            "state": "pending",
            "error": null
          }
        ]
      }
    ],
    "stepsDone": 0,
    "stepsTotal": 3
  },
  "created": true
}

Errors. The body is {"error": {"code": …, "message": …}}; see error codes for the codes every operation shares.

StatusCodes
400invalid_request
401unauthenticated, invalid_token
403project_not_allowed, forbidden, mfa_required
404not_found
409action_not_available, already_running, already_stopped, confirmation_required, member_unreachable, migration_blocked, not_deployed, nothing_deployed, nothing_to_do, operation_in_progress
429rate_limited
502incus_unreachable
503database_not_configured

GET /api/v1/applications/{name}/operations/{id}

One operation with the progress of each phase and instance. Needs application.view.

Operation ID: application_operation

Needs: application.view.

ParameterInTypeRequiredDefaultMeaning
namepathstringyes—Application name
idpathstring (uuid)yes—Operation id

Example request. Replace sample names, IDs and body values with resources and settings from your installation.

curl --fail-with-body -sS -X GET "$APELORA_URL/api/v1/applications/shop/operations/6fc89203-9d65-44b9-8008-03adef010024" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json"

TypeScript:

const result = await client.request("application_operation", { params: { name: "shop", id: "6fc89203-9d65-44b9-8008-03adef010024" } });

Response 200 — Done; the response holds the result..

Content-Type: application/json.

Example response from a test installation; your IDs, names and times differ.

{
  "id": "6fc89203-9d65-44b9-8008-03adef010024",
  "kind": "stop",
  "state": "succeeded",
  "requestedBy": "token:docs-verifier",
  "createdAt": "2026-10-28T23:26:54.431921Z",
  "startedAt": "2026-10-28T23:26:54.482794Z",
  "finishedAt": "2026-10-28T23:27:00.760832Z",
  "error": null,
  "phases": [
    {
      "action": "poweroff",
      "state": "succeeded",
      "steps": [
        {
          "instance": "shop-web-01",
          "module": "web-01",
          "state": "succeeded",
          "error": null
        }
      ]
    },
    {
      "action": "poweroff",
      "state": "succeeded",
      "steps": [
        {
          "instance": "shop-cache-01",
          "module": "cache-01",
          "state": "succeeded",
          "error": null
        },
        {
          "instance": "shop-db-01",
          "module": "db-01",
          "state": "succeeded",
          "error": null
        }
      ]
    }
  ],
  "stepsDone": 3,
  "stepsTotal": 3
}

Errors. The body is {"error": {"code": …, "message": …}}; see error codes for the codes every operation shares.

StatusCodes
400invalid_request
401unauthenticated, invalid_token
403forbidden, mfa_required
404not_found
429rate_limited
503database_not_configured

POST /api/v1/applications/{name}/plan

What deploying the design would change, without changing anything. Needs deploy.

Operation ID: plan_application

Needs: deploy.

ParameterInTypeRequiredDefaultMeaning
namepathstringyes—Application name

Request body (JSON):

FieldTypeRequiredMeaning
connectionslist of object or nullnoThe design's connections; enforced when the control plane has a network uplink.
connections[].fromstringyes—
connections[].portinteger or nullnoNone: any port of the transport (the protocol has no well-known port).
connections[].tostringyes—
connections[].transportstringyestcp or udp.
moduleslist of objectyes—
modules[].availabilityone of stateless, singleton, statefulnoHow a module may be run and recovered (from its catalog class).
modules[].dnsResolverbooleannoThis module answers service names for the application. Absent means it does not: a resolver is never added unless the design asks for one.
modules[].guestOsone of linux, windowsnoThe operating system inside (from the class): decides which sessions it offers.
modules[].internetone of none, web, anynoWhat it may reach outside the application (enforced with the connections).
modules[].modulestringyesThe module's stable id in the design (e.g. nginx-01).
modules[].readinessobject or nullno—
modules[].replicasintegernoHow many instances (1–9); more than one only for stateless modules.
modules[].specobjectyesImage, kind and settings; the control plane sets the name, project and owner.
projectstringyes—
publishedlist of objectnoPorts reachable from outside the application (needs connections to be enforced).
published[].modulestringyes—
published[].portintegeryes—
published[].transportstringyestcp or udp.
{
  "project": "shop-prod",
  "modules": [
    {
      "module": "web-01",
      "replicas": 1,
      "availability": "stateless",
      "spec": {
        "project": "shop-prod",
        "name": "shop-web-01",
        "kind": "container",
        "start": true,
        "source": {
          "protocol": "oci",
          "server": "https://docker.io",
          "alias": "library/nginx@sha256:eb1c896deef834f4e056829fd6dadac8305501f16cc71ba65fb252c8d0aa168e"
        },
        "architectures": [
          "x86_64",
          "aarch64"
        ],
        "config": {
          "limits.cpu": "1",
          "limits.memory": "512MiB"
        }
      }
    },
    {
      "module": "db-01",
      "replicas": 1,
      "availability": "stateful",
      "spec": {
        "project": "shop-prod",
        "name": "shop-db-01",
        "kind": "container",
        "start": true,
        "source": {
          "protocol": "simplestreams",
          "server": "https://images.linuxcontainers.org",
          "alias": "debian/13"
        },
        "architectures": [
          "x86_64",
          "aarch64"
        ],
        "config": {
          "limits.cpu": "2",
          "limits.memory": "2GiB"
        },
        "volumes": [
          {
            "name": "data",
            "sizeMib": 10240,
            "path": "/var/lib/data"
          }
        ]
      }
    },
    {
      "module": "cache-01",
      "replicas": 1,
      "availability": "stateful",
      "spec": {
        "project": "shop-prod",
        "name": "shop-cache-01",
        "kind": "container",
        "start": true,
        "source": {
          "protocol": "simplestreams",
          "server": "https://images.linuxcontainers.org",
          "alias": "debian/13"
        },
        "architectures": [
          "x86_64",
          "aarch64"
        ],
        "config": {
          "limits.cpu": "1",
          "limits.memory": "1GiB"
        },
        "volumes": [
          {
            "name": "cache",
            "sizeMib": 2048,
            "path": "/var/cache/shop"
          }
        ]
      }
    }
  ],
  "connections": [
    {
      "from": "web-01",
      "to": "db-01",
      "transport": "tcp",
      "port": 5432
    },
    {
      "from": "web-01",
      "to": "cache-01",
      "transport": "tcp",
      "port": 6379
    }
  ],
  "published": [
    {
      "module": "web-01",
      "transport": "tcp",
      "port": 80
    }
  ]
}

Example request. Replace sample names, IDs and body values with resources and settings from your installation.

This request can change state. Check permissions, prerequisites and any required approval before running it.

curl --fail-with-body -sS -X POST "$APELORA_URL/api/v1/applications/shop/plan" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  --data-raw '{"project":"shop-prod","modules":[{"module":"web-01","replicas":1,"availability":"stateless","spec":{"project":"shop-prod","name":"shop-web-01","kind":"container","start":true,"source":{"protocol":"oci","server":"https://docker.io","alias":"library/nginx@sha256:eb1c896deef834f4e056829fd6dadac8305501f16cc71ba65fb252c8d0aa168e"},"architectures":["x86_64","aarch64"],"config":{"limits.cpu":"1","limits.memory":"512MiB"}}},{"module":"db-01","replicas":1,"availability":"stateful","spec":{"project":"shop-prod","name":"shop-db-01","kind":"container","start":true,"source":{"protocol":"simplestreams","server":"https://images.linuxcontainers.org","alias":"debian/13"},"architectures":["x86_64","aarch64"],"config":{"limits.cpu":"2","limits.memory":"2GiB"},"volumes":[{"name":"data","sizeMib":10240,"path":"/var/lib/data"}]}},{"module":"cache-01","replicas":1,"availability":"stateful","spec":{"project":"shop-prod","name":"shop-cache-01","kind":"container","start":true,"source":{"protocol":"simplestreams","server":"https://images.linuxcontainers.org","alias":"debian/13"},"architectures":["x86_64","aarch64"],"config":{"limits.cpu":"1","limits.memory":"1GiB"},"volumes":[{"name":"cache","sizeMib":2048,"path":"/var/cache/shop"}]}}],"connections":[{"from":"web-01","to":"db-01","transport":"tcp","port":5432},{"from":"web-01","to":"cache-01","transport":"tcp","port":6379}],"published":[{"module":"web-01","transport":"tcp","port":80}]}'

TypeScript:

const result = await client.request("plan_application", { params: { name: "shop" }, body /* the request body above */ });

Response 200 — Done; the response holds the result..

Content-Type: application/json.

Example response from a test installation; your IDs, names and times differ. Long lists are cut to their first items.

{
  "items": [
    {
      "module": "web-01",
      "replica": 1,
      "instance": "shop-web-01",
      "action": "create",
      "reason": "not deployed yet"
    },
    {
      "module": "db-01",
      "replica": 1,
      "instance": "shop-db-01",
      "action": "create",
      "reason": "not deployed yet"
    }
  ],
  "warnings": [
    "single points of failure: cache-01, db-01, web-01 run as one instance",
    "not published: web-01 (publishing needs enforced connections and APELORA_SERVICE_ADDRESSES)",
    "connections are not enforced: the control plane has no network uplink (APELORA_NETWORK_UPLINK); instances share the project's network"
  ],
  "quota": {
    "allowed": true,
    "adding": {
      "instances": 3,
      "cpu": 4,
      "memoryBytes": 3758096384,
      "diskBytes": 45097156608
    },
    "scopes": [
      {
        "kind": "project",
        "id": "deff5baa-28d1-4534-b980-108a88f9fa09",
        "name": "Online shop",
        "dimensions": [
          {
            "dimension": "instances",
            "used": 0,
            "adding": 3,
            "limit": 20,
            "exceeded": false
          },
          {
            "dimension": "cpu",
            "used": 0,
            "adding": 4,
            "limit": 16,
            "exceeded": false
          }
        ]
      }
    ]
  }
}

Errors. The body is {"error": {"code": …, "message": …}}; see error codes for the codes every operation shares.

StatusCodes
400invalid_request
401unauthenticated, invalid_token
403project_not_allowed, forbidden, mfa_required
409project_mismatch
429rate_limited
502an incus_… code
503quota_unverifiable, database_not_configured, store_unavailable

POST /api/v1/applications/{name}/restore

Brings an archived application back. Needs application.delete.

Operation ID: restore_application

Needs: application.delete.

ParameterInTypeRequiredDefaultMeaning
namepathstringyes—Application name

Example request. Replace sample names, IDs and body values with resources and settings from your installation.

This request can change state. Check permissions, prerequisites and any required approval before running it.

curl --fail-with-body -sS -X POST "$APELORA_URL/api/v1/applications/blog/restore" \
  -H "Authorization: Bearer $APELORA_TOKEN"

TypeScript:

await client.request("restore_application", { params: { name: "blog" } });

Response 204 — Done; nothing is returned., with no body.

Errors. The body is {"error": {"code": …, "message": …}}; see error codes for the codes every operation shares.

StatusCodes
400invalid_request
401unauthenticated, invalid_token
403forbidden, mfa_required
404not_found
409still_running
429rate_limited
503database_not_configured

GET /api/v1/applications/{name}/status

The application's instances as Incus reports them, by module. Needs read-inventory.

Operation ID: application_status

Needs: read-inventory.

ParameterInTypeRequiredDefaultMeaning
namepathstringyes—Application name
projectquerystringno—Only this Incus project.

Example request. Replace sample names, IDs and body values with resources and settings from your installation.

curl --fail-with-body -sS -X GET "$APELORA_URL/api/v1/applications/shop/status" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json"

With options (project):

curl --fail-with-body -sS -X GET "$APELORA_URL/api/v1/applications/shop/status?project=shop-prod" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json"

TypeScript:

const result = await client.request("application_status", { params: { name: "shop" }, query: { project: "shop-prod" } });

Response 200 — Done; the response holds the result..

Content-Type: application/json.

Example response from a test installation; your IDs, names and times differ. Long lists are cut to their first items.

{
  "source": {
    "provider": "mock",
    "observedAt": "2026-10-28T23:26:52.777921Z"
  },
  "items": [
    {
      "module": "web-01",
      "instance": "shop-web-01",
      "project": "shop-prod",
      "status": "Running",
      "location": "mock-member-01"
    },
    {
      "module": "db-01",
      "instance": "shop-db-01",
      "project": "shop-prod",
      "status": "Running",
      "location": "mock-member-01"
    }
  ],
  "services": []
}

Errors. The body is {"error": {"code": …, "message": …}}; see error codes for the codes every operation shares.

StatusCodes
400invalid_request
401unauthenticated, invalid_token
403forbidden, mfa_required
404not_found
429rate_limited
502an incus_… code

GET /api/v1/applications/{name}/versions

Versions of the application, newest first. Needs application.view.

Operation ID: application_versions

Needs: application.view.

ParameterInTypeRequiredDefaultMeaning
namepathstringyes—Application name

Example request. Replace sample names, IDs and body values with resources and settings from your installation.

curl --fail-with-body -sS -X GET "$APELORA_URL/api/v1/applications/shop/versions" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json"

TypeScript:

const result = await client.request("application_versions", { params: { name: "shop" } });

Response 200 — Done; the response holds the result..

Content-Type: application/json.

Example response from a test installation; your IDs, names and times differ.

{
  "items": [
    {
      "revision": 1,
      "published": true,
      "label": "First release",
      "draftVersion": 1,
      "createdBy": "token:docs-verifier",
      "createdAt": "2026-10-28T23:26:49.891746Z"
    }
  ]
}

Errors. The body is {"error": {"code": …, "message": …}}; see error codes for the codes every operation shares.

StatusCodes
400invalid_request
401unauthenticated, invalid_token
403forbidden, mfa_required
404not_found
429rate_limited
503database_not_configured

POST /api/v1/applications/{name}/versions

Releases the current draft as the next immutable published version. Refused while the draft is invalid or when this draft version is already published. Needs application.design.

Operation ID: publish_application

Needs: application.design.

ParameterInTypeRequiredDefaultMeaning
namepathstringyes—Application name

Request body (JSON):

FieldTypeRequiredMeaning
labelstring or nullnoA short name for the version, e.g. after load test.
{
  "label": "First release"
}

Example request. Replace sample names, IDs and body values with resources and settings from your installation.

This request can change state. Check permissions, prerequisites and any required approval before running it.

curl --fail-with-body -sS -X POST "$APELORA_URL/api/v1/applications/shop/versions" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  --data-raw '{"label":"First release"}'

TypeScript:

const result = await client.request("publish_application", { params: { name: "shop" }, body: { label: "First release" } });

Response 201 — Created; the response describes what was created..

Content-Type: application/json.

Example response from a test installation; your IDs, names and times differ.

{
  "revision": 1,
  "published": true,
  "label": "First release",
  "draftVersion": 1,
  "createdBy": "token:docs-verifier",
  "createdAt": "2026-10-28T23:26:49.891746Z"
}

Errors. The body is {"error": {"code": …, "message": …}}; see error codes for the codes every operation shares.

StatusCodes
400invalid_request
401unauthenticated, invalid_token
403forbidden, mfa_required
404no_draft, not_found
409already_published
422draft_invalid
429rate_limited
503database_not_configured

GET /api/v1/applications/{name}/versions/{revision}

The document of one version. Needs application.view.

Operation ID: application_version

Needs: application.view.

ParameterInTypeRequiredDefaultMeaning
namepathstringyes—Application name
revisionpathintegeryes—Version number

Example request. Replace sample names, IDs and body values with resources and settings from your installation.

curl --fail-with-body -sS -X GET "$APELORA_URL/api/v1/applications/shop/versions/1" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json"

TypeScript:

const result = await client.request("application_version", { params: { name: "shop", revision: "1" } });

Response 200 — Done; the response holds the result..

Content-Type: application/json.

Example response from a test installation; your IDs, names and times differ.

{
  "document": {
    "apiVersion": "fabric.apelora/v1alpha1",
    "kind": "ApplicationAssembly",
    "metadata": {
      "name": "shop",
      "uid": "assembly-shop"
    },
    "spec": {
      "connections": [],
      "modules": []
    }
  },
  "published": true
}

Errors. The body is {"error": {"code": …, "message": …}}; see error codes for the codes every operation shares.

StatusCodes
400invalid_request
401unauthenticated, invalid_token
403forbidden, mfa_required
404not_found
429rate_limited
503database_not_configured

GET /api/v1/applications/{name}/volumes

The application's volumes, with what Incus says about each now. Needs application.view.

Operation ID: application_volumes

Needs: application.view.

ParameterInTypeRequiredDefaultMeaning
namepathstringyes—Application name

Example request. Replace sample names, IDs and body values with resources and settings from your installation.

curl --fail-with-body -sS -X GET "$APELORA_URL/api/v1/applications/shop/volumes" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json"

TypeScript:

const result = await client.request("application_volumes", { params: { name: "shop" } });

Response 200 — Done; the response holds the result..

Content-Type: application/json.

Example response from a test installation; your IDs, names and times differ.

{
  "source": {
    "provider": "mock",
    "observedAt": "2026-10-28T23:26:52.826654Z"
  },
  "items": [
    {
      "module": "cache-01",
      "instance": "shop-cache-01",
      "name": "cache",
      "incusName": "shop-cache-01-cache",
      "pool": "mock-local",
      "member": "mock-member-01",
      "sizeMib": 2048,
      "content": "filesystem",
      "class": "data-local",
      "state": "attached",
      "usedBy": [
        "shop-cache-01"
      ],
      "createdAt": "2026-10-28T23:26:51.762114Z"
    },
    {
      "module": "db-01",
      "instance": "shop-db-01",
      "name": "data",
      "incusName": "shop-db-01-data",
      "pool": "mock-local",
      "member": "mock-member-01",
      "sizeMib": 10240,
      "content": "filesystem",
      "class": "data-local",
      "state": "attached",
      "usedBy": [
        "shop-db-01"
      ],
      "createdAt": "2026-10-28T23:26:51.685333Z"
    }
  ]
}

Errors. The body is {"error": {"code": …, "message": …}}; see error codes for the codes every operation shares.

StatusCodes
400invalid_request
401unauthenticated, invalid_token
403forbidden, mfa_required
404not_found
429rate_limited
502an incus_… code
503database_not_configured

PATCH /api/v1/applications/{name}/volumes/{volume}

Grows one of the application's volumes, also while its instance runs. Volumes only grow: a smaller size is refused. Needs application.deploy; audited.

Operation ID: grow_application_volume

Needs: application.deploy; audited.

ParameterInTypeRequiredDefaultMeaning
namepathstringyes—Application name
volumepathstringyes—The volume's Incus name

Request body (JSON):

FieldTypeRequiredMeaning
sizeMibintegeryesThe new size in MiB; at least the current one (volumes only grow).
{
  "sizeMib": 20480
}

Example request. Replace sample names, IDs and body values with resources and settings from your installation.

This request can change state. Check permissions, prerequisites and any required approval before running it.

curl --fail-with-body -sS -X PATCH "$APELORA_URL/api/v1/applications/shop/volumes/shop-db-01-data" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Content-Type: application/json" \
  --data-raw '{"sizeMib":20480}'

TypeScript:

await client.request("grow_application_volume", { params: { name: "shop", volume: "shop-db-01-data" }, body: { sizeMib: 20480 } });

Response 204 — Done; nothing is returned., with no body.

Errors. The body is {"error": {"code": …, "message": …}}; see error codes for the codes every operation shares.

StatusCodes
400invalid_request
401unauthenticated, invalid_token
403forbidden, mfa_required
404not_found
409—
429rate_limited
502an incus_… code
503database_not_configured

DELETE /api/v1/applications/{name}/volumes/{volume}

Deletes one of the application's volumes and its snapshots, for good. The volume's Incus name must be typed again; refused while an instance uses it. Needs application.delete (and an approval where the policy asks for one); audited.

Operation ID: delete_application_volume

Needs: application.delete (and an approval where the policy asks for one); audited.

ParameterInTypeRequiredDefaultMeaning
namepathstringyes—Application name
volumepathstringyes—The volume's Incus name
confirmquerystringyes—The volume's Incus name again, typed by the person deleting it.

Example request. Replace sample names, IDs and body values with resources and settings from your installation.

This request can change state. Check permissions, prerequisites and any required approval before running it.

curl --fail-with-body -sS -X DELETE "$APELORA_URL/api/v1/applications/shop/volumes/shop-cache-01-cache?confirm=shop-cache-01-cache" \
  -H "Authorization: Bearer $APELORA_TOKEN"

TypeScript:

await client.request("delete_application_volume", { params: { name: "shop", volume: "shop-cache-01-cache" }, query: { confirm: "shop-cache-01-cache" } });

Response 204 — Done; nothing is returned., with no body.

Errors. The body is {"error": {"code": …, "message": …}}; see error codes for the codes every operation shares.

StatusCodes
400confirmation_mismatch, invalid_request
401unauthenticated, invalid_token
403forbidden, mfa_required
404not_found
409approval_invalid, approval_required, volume_in_use
429rate_limited
502an incus_… code
503database_not_configured

GET /api/v1/applications/{name}/volumes/{volume}/backups

The volume's backups, oldest first, whether each still exists, and its schedules. Needs application.view.

Operation ID: volume_backups

Needs: application.view.

ParameterInTypeRequiredDefaultMeaning
namepathstringyes—Application name
volumepathstringyes—The volume's Incus name

Example request. Replace sample names, IDs and body values with resources and settings from your installation.

curl --fail-with-body -sS -X GET "$APELORA_URL/api/v1/applications/shop/volumes/shop-db-01-data/backups" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json"

TypeScript:

const result = await client.request("volume_backups", { params: { name: "shop", volume: "shop-db-01-data" } });

Response 200 — Done; the response holds the result..

Content-Type: application/json.

Example response from a test installation; your IDs, names and times differ.

{
  "source": {
    "provider": "mock",
    "observedAt": "2026-10-28T23:26:54.27928Z"
  },
  "backupPool": "mock-local",
  "schedules": {
    "snapshots": {
      "everyMinutes": 60,
      "keep": 24,
      "lastRunAt": null,
      "error": null
    },
    "backups": {
      "everyMinutes": 1440,
      "keep": 7,
      "lastRunAt": null,
      "error": null
    }
  },
  "items": [
    {
      "name": "shop-db-01-data-bk-20261028232653",
      "pool": "mock-local",
      "member": "mock-member-02",
      "sizeMib": 20480,
      "takenAt": "2026-10-28T23:26:53.058945Z",
      "scheduled": false,
      "beforeRestore": null,
      "present": true
    },
    {
      "name": "shop-db-01-data-bk-20261028232654",
      "pool": "mock-local",
      "member": "mock-member-02",
      "sizeMib": 20480,
      "takenAt": "2026-10-28T23:26:54.191902Z",
      "scheduled": false,
      "beforeRestore": null,
      "present": true
    }
  ]
}

Errors. The body is {"error": {"code": …, "message": …}}; see error codes for the codes every operation shares.

StatusCodes
400invalid_request
401unauthenticated, invalid_token
403forbidden, mfa_required
404not_found
429rate_limited
503database_not_configured

POST /api/v1/applications/{name}/volumes/{volume}/backups

Backs the volume up now: a copy, through a short-lived snapshot, into the backup pool on another member. Kept until someone deletes it (delete_backup); scheduled backups are pruned to what the schedule keeps. Queues a volume.backup job. Needs application.operate; audited.

Operation ID: back_up_volume

Needs: application.operate; audited.

ParameterInTypeRequiredDefaultMeaning
namepathstringyes—Application name
volumepathstringyes—The volume's Incus name
Idempotency-Keyheaderstringno—Makes the request safe to repeat

Example request. Replace sample names, IDs and body values with resources and settings from your installation.

This request can change state. Check permissions, prerequisites and any required approval before running it.

# Generate once per distinct change; keep this value when retrying.
IDEMPOTENCY_KEY=$(uuidgen)
curl --fail-with-body -sS -X POST "$APELORA_URL/api/v1/applications/shop/volumes/shop-db-01-data/backups" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY"

TypeScript:

const result = await client.request("back_up_volume", { params: { name: "shop", volume: "shop-db-01-data" }, idempotencyKey: newIdempotencyKey() });

Response 202 — Accepted: the work is queued; the response says how to follow it..

Content-Type: application/json.

Example response from a test installation; your IDs, names and times differ.

{
  "jobId": "17409417-49c9-4f4c-97fd-45a6d9a992a1",
  "created": true
}

Errors. The body is {"error": {"code": …, "message": …}}; see error codes for the codes every operation shares.

StatusCodes
400invalid_request
401unauthenticated, invalid_token
403forbidden, mfa_required
404not_found
409backup_pool_missing
429rate_limited
503database_not_configured

DELETE /api/v1/applications/{name}/volumes/{volume}/backups/{backup}

Deletes one backup copy. The volume it was taken from is left as it is. The backup's Incus name must be typed again. A copy Incus no longer has is forgotten anyway, so a record of a missing copy can be cleared. Needs application.delete (and an approval where a policy asks for one); audited.

Operation ID: delete_volume_backup

Needs: application.delete (and an approval where a policy asks for one); audited.

ParameterInTypeRequiredDefaultMeaning
namepathstringyes—Application name.
volumepathstringyes—The volume's Incus name.
backuppathstringyes—The backup's Incus name.
confirmquerystringyes—The backup's Incus name again, typed by the person deleting it.

Example request. Replace sample names, IDs and body values with resources and settings from your installation.

This request can change state. Check permissions, prerequisites and any required approval before running it.

curl --fail-with-body -sS -X DELETE "$APELORA_URL/api/v1/applications/shop/volumes/shop-db-01-data/backups/shop-db-01-data-bk-20261028232654?confirm=shop-db-01-data-bk-20261028232654" \
  -H "Authorization: Bearer $APELORA_TOKEN"

TypeScript:

await client.request("delete_volume_backup", { params: { name: "shop", volume: "shop-db-01-data", backup: "shop-db-01-data-bk-20261028232654" }, query: { confirm: "shop-db-01-data-bk-20261028232654" } });

Response 204 — Done; nothing is returned., with no body.

Errors. The body is {"error": {"code": …, "message": …}}; see error codes for the codes every operation shares.

StatusCodes
400confirmation_mismatch, invalid_request
401unauthenticated, invalid_token
403forbidden, mfa_required
404not_found
409approval_invalid, approval_required
429rate_limited
502an incus_… code
503database_not_configured

GET /api/v1/applications/{name}/volumes/{volume}/backups/{backup}/restoration

Dry run of restoring the volume from one of its backups: what is replaced (data written since the backup, the volume's snapshots), which instances must be stopped, where the safety backup goes, and the steps. Changes nothing. Needs application.operate.

Operation ID: preview_volume_restore

Needs: application.operate.

ParameterInTypeRequiredDefaultMeaning
namepathstringyes—Application name.
volumepathstringyes—The volume's Incus name.
backuppathstringyes—The backup's Incus name.
safetyBackupquerybooleannotrueTake a safety backup of the current data first (default true).

Example request. Replace sample names, IDs and body values with resources and settings from your installation.

curl --fail-with-body -sS -X GET "$APELORA_URL/api/v1/applications/shop/volumes/shop-db-01-data/backups/shop-db-01-data-bk-20261028232653/restoration" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json"

With options (safetyBackup):

curl --fail-with-body -sS -X GET "$APELORA_URL/api/v1/applications/shop/volumes/shop-db-01-data/backups/shop-db-01-data-bk-20261028232653/restoration?safetyBackup=false" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json"

TypeScript:

const result = await client.request("preview_volume_restore", { params: { name: "shop", volume: "shop-db-01-data", backup: "shop-db-01-data-bk-20261028232653" }, query: { safetyBackup: false } });

Response 200 — Done; the response holds the result..

Content-Type: application/json.

Example response from a test installation; your IDs, names and times differ.

{
  "source": {
    "provider": "mock",
    "observedAt": "2026-10-28T23:26:54.316051Z"
  },
  "plan": {
    "volume": "shop-db-01-data",
    "pool": "mock-local",
    "member": "mock-member-01",
    "sizeMib": 20480,
    "backup": "shop-db-01-data-bk-20261028232653",
    "backupPool": "mock-local",
    "backupMember": "mock-member-02",
    "backupTakenAt": "2026-10-28T23:26:53.058945Z",
    "snapshotsDeleted": [
      "snap-20261028-232652"
    ],
    "usedBy": [
      "shop-db-01"
    ],
    "running": [
      "shop-db-01"
    ],
    "safetyBackupPool": "mock-local",
    "blocked": [
      "stop shop-db-01 first: a volume is never overwritten under a running instance"
    ],
    "steps": [
      "Copy the volume as it is now into pool mock-local (a safety backup, listed with the others)",
      "Copy backup shop-db-01-data-bk-20261028232653 over volume shop-db-01-data on mock-member-01",
      "Incus deletes the volume's 1 snapshot while copying",
      "Leave shop-db-01 stopped: start it again once the data is checked"
    ]
  }
}

Errors. The body is {"error": {"code": …, "message": …}}; see error codes for the codes every operation shares.

StatusCodes
400invalid_request
401unauthenticated, invalid_token
403forbidden, mfa_required
404not_found
429rate_limited
502an incus_… code
503database_not_configured

POST /api/v1/applications/{name}/volumes/{volume}/backups/{backup}/restore

Restores the volume from one of its backups, after the dry run passes again and the volume's name is typed: a safety backup of the current data first (unless declined), then the backup is copied over the volume. Every instance using it must be stopped; they stay stopped. Queues a volume.restore job. Needs application.operate (and an approval where a policy asks); audited with the job.

Operation ID: restore_volume_backup

Needs: application.operate (and an approval where a policy asks); audited with the job.

ParameterInTypeRequiredDefaultMeaning
namepathstringyes—Application name.
volumepathstringyes—The volume's Incus name.
backuppathstringyes—The backup's Incus name.
Idempotency-Keyheaderstringno—Makes the request safe to repeat

Request body (JSON):

FieldTypeRequiredMeaning
confirmstringyesThe volume's Incus name again, typed by the person restoring it.
safetyBackupbooleannoTake a safety backup of the current data first (default true).
{
  "confirm": "shop-db-01-data",
  "safetyBackup": true
}

Example request. Replace sample names, IDs and body values with resources and settings from your installation.

This request can change state. Check permissions, prerequisites and any required approval before running it.

# Generate once per distinct change; keep this value when retrying.
IDEMPOTENCY_KEY=$(uuidgen)
curl --fail-with-body -sS -X POST "$APELORA_URL/api/v1/applications/shop/volumes/shop-db-01-data/backups/shop-db-01-data-bk-20261028232653/restore" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -H "Content-Type: application/json" \
  --data-raw '{"confirm":"shop-db-01-data","safetyBackup":true}'

TypeScript:

const result = await client.request("restore_volume_backup", { params: { name: "shop", volume: "shop-db-01-data", backup: "shop-db-01-data-bk-20261028232653" }, body: { confirm: "shop-db-01-data", safetyBackup: true }, idempotencyKey: newIdempotencyKey() });

Response 202 — Accepted: the work is queued; the response says how to follow it..

Content-Type: application/json.

Example response from a test installation; your IDs, names and times differ.

{
  "jobId": "5765cd15-0799-483f-9948-7f32b9421456",
  "created": true
}

Errors. The body is {"error": {"code": …, "message": …}}; see error codes for the codes every operation shares.

StatusCodes
400confirmation_mismatch, invalid_request
401unauthenticated, invalid_token
403forbidden, mfa_required
404not_found
409approval_invalid, approval_required, restore_blocked
429rate_limited
502an incus_… code
503database_not_configured

PUT /api/v1/applications/{name}/volumes/{volume}/schedules

Sets or turns off the volume's snapshot and backup schedules. A new or changed schedule first runs at the worker's next pass (within a minute). Needs application.deploy; audited.

Operation ID: set_volume_schedules

Needs: application.deploy; audited.

ParameterInTypeRequiredDefaultMeaning
namepathstringyes—Application name
volumepathstringyes—The volume's Incus name

Request body (JSON):

FieldTypeRequiredMeaning
backupsobject or nullno—
backups.everyMinutesintegeryesMinutes between two runs: 15 minutes to one week.
backups.keepintegeryesHow many of the scheduled results are kept: 1 to 50.
snapshotsobject or nullno—
snapshots.everyMinutesintegeryesMinutes between two runs: 15 minutes to one week.
snapshots.keepintegeryesHow many of the scheduled results are kept: 1 to 50.
{
  "snapshots": {
    "everyMinutes": 60,
    "keep": 24
  },
  "backups": {
    "everyMinutes": 1440,
    "keep": 7
  }
}

Example request. Replace sample names, IDs and body values with resources and settings from your installation.

This request can change state. Check permissions, prerequisites and any required approval before running it.

curl --fail-with-body -sS -X PUT "$APELORA_URL/api/v1/applications/shop/volumes/shop-db-01-data/schedules" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Content-Type: application/json" \
  --data-raw '{"snapshots":{"everyMinutes":60,"keep":24},"backups":{"everyMinutes":1440,"keep":7}}'

TypeScript:

await client.request("set_volume_schedules", { params: { name: "shop", volume: "shop-db-01-data" }, body: { snapshots: { everyMinutes: 60, keep: 24 }, backups: { everyMinutes: 1440, keep: 7 } } });

Response 204 — Done; nothing is returned., with no body.

Errors. The body is {"error": {"code": …, "message": …}}; see error codes for the codes every operation shares.

StatusCodes
400invalid_request
401unauthenticated, invalid_token
403forbidden, mfa_required
404not_found
409backup_pool_missing
429rate_limited
503database_not_configured

GET /api/v1/applications/{name}/volumes/{volume}/snapshots

The volume's snapshots, oldest first. Needs application.view.

Operation ID: volume_snapshots

Needs: application.view.

ParameterInTypeRequiredDefaultMeaning
namepathstringyes—Application name
volumepathstringyes—The volume's Incus name

Example request. Replace sample names, IDs and body values with resources and settings from your installation.

curl --fail-with-body -sS -X GET "$APELORA_URL/api/v1/applications/shop/volumes/shop-db-01-data/snapshots" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json"

TypeScript:

const result = await client.request("volume_snapshots", { params: { name: "shop", volume: "shop-db-01-data" } });

Response 200 — Done; the response holds the result..

Content-Type: application/json.

Example response from a test installation; your IDs, names and times differ.

{
  "source": {
    "provider": "mock",
    "observedAt": "2026-10-28T23:26:52.901814Z"
  },
  "items": []
}

Errors. The body is {"error": {"code": …, "message": …}}; see error codes for the codes every operation shares.

StatusCodes
400invalid_request
401unauthenticated, invalid_token
403forbidden, mfa_required
404not_found
429rate_limited
502an incus_… code
503database_not_configured

POST /api/v1/applications/{name}/volumes/{volume}/snapshots

Takes a snapshot of the volume, named after the time (snap-YYYYMMDD-HHMMSS). While an instance runs the snapshot is only crash-consistent; the response says which. Snapshots are not backups: they live in the same pool. Needs application.operate; audited.

Operation ID: take_volume_snapshot

Needs: application.operate; audited.

ParameterInTypeRequiredDefaultMeaning
namepathstringyes—Application name
volumepathstringyes—The volume's Incus name

Example request. Replace sample names, IDs and body values with resources and settings from your installation.

This request can change state. Check permissions, prerequisites and any required approval before running it.

curl --fail-with-body -sS -X POST "$APELORA_URL/api/v1/applications/shop/volumes/shop-db-01-data/snapshots" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json"

TypeScript:

const result = await client.request("take_volume_snapshot", { params: { name: "shop", volume: "shop-db-01-data" } });

Response 201 — Created; the response describes what was created..

Content-Type: application/json.

Example response from a test installation; your IDs, names and times differ.

{
  "name": "snap-20261028-232652",
  "consistency": "crash-consistent"
}

Errors. The body is {"error": {"code": …, "message": …}}; see error codes for the codes every operation shares.

StatusCodes
400invalid_request
401unauthenticated, invalid_token
403forbidden, mfa_required
404not_found
409snapshots_not_allowed, volume_missing
429rate_limited
502an incus_… code
503database_not_configured

DELETE /api/v1/applications/{name}/volumes/{volume}/snapshots/{snapshot}

Deletes one snapshot of the volume. Needs application.delete; audited.

Operation ID: delete_volume_snapshot

Needs: application.delete; audited.

ParameterInTypeRequiredDefaultMeaning
namepathstringyes—Application name.
volumepathstringyes—The volume's Incus name.
snapshotpathstringyes—Snapshot name.

Example request. Replace sample names, IDs and body values with resources and settings from your installation.

This request can change state. Check permissions, prerequisites and any required approval before running it.

curl --fail-with-body -sS -X DELETE "$APELORA_URL/api/v1/applications/shop/volumes/shop-db-01-data/snapshots/snap-20261028-232652" \
  -H "Authorization: Bearer $APELORA_TOKEN"

TypeScript:

await client.request("delete_volume_snapshot", { params: { name: "shop", volume: "shop-db-01-data", snapshot: "snap-20261028-232652" } });

Response 204 — Done; nothing is returned., with no body.

Errors. The body is {"error": {"code": …, "message": …}}; see error codes for the codes every operation shares.

StatusCodes
400invalid_request
401unauthenticated, invalid_token
403forbidden, mfa_required
404not_found
429rate_limited
502an incus_… code
503database_not_configured

POST /api/v1/applications/{name}/volumes/{volume}/snapshots/{snapshot}/restore

Puts the volume back to a snapshot: everything written since is lost. The snapshot's name must be typed again, and every instance using the volume must be stopped. Needs application.operate (and an approval where the policy asks for one); audited.

Operation ID: restore_volume_snapshot

Needs: application.operate (and an approval where the policy asks for one); audited.

ParameterInTypeRequiredDefaultMeaning
namepathstringyes—Application name.
volumepathstringyes—The volume's Incus name.
snapshotpathstringyes—Snapshot name.

Request body (JSON):

FieldTypeRequiredMeaning
confirmstringyesThe snapshot's name again, typed by the person restoring it.
{
  "confirm": "snap-20261028-232652"
}

Example request. Replace sample names, IDs and body values with resources and settings from your installation.

This request can change state. Check permissions, prerequisites and any required approval before running it.

curl --fail-with-body -sS -X POST "$APELORA_URL/api/v1/applications/shop/volumes/shop-db-01-data/snapshots/snap-20261028-232652/restore" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Content-Type: application/json" \
  --data-raw '{"confirm":"snap-20261028-232652"}'

TypeScript:

await client.request("restore_volume_snapshot", { params: { name: "shop", volume: "shop-db-01-data", snapshot: "snap-20261028-232652" }, body: { confirm: "snap-20261028-232652" } });

Response 204 — Done; nothing is returned., with no body.

Errors. The body is {"error": {"code": …, "message": …}}; see error codes for the codes every operation shares.

StatusCodes
400confirmation_mismatch, invalid_request
401unauthenticated, invalid_token
403forbidden, mfa_required
404not_found
409approval_invalid, approval_required, volume_in_use, volume_missing
429rate_limited
502an incus_… code
503database_not_configured

This page is a copy of packages/api-client/openapi.json in the repository, which is the source of truth. Suggest a change.