Deployment API

Creating, changing, moving and deleting single instances, and the jobs that carry out those changes and deployments: list, follow, cancel and retry. This page lists 10 operations.

Set variables in the API overview. Download OpenAPI.

Operations

OperationWhat it does
POST /api/v1/instancesQueues creation of an instance.
DELETE /api/v1/instances/{project}/{name}Queues deletion of an Apelora instance (stopped first if running).
GET /api/v1/instances/{project}/{name}/moveDry run of moving an instance to another member: whether it keeps running, what it copies, what blocks it.
POST /api/v1/instances/{project}/{name}/moveMoves an Apelora instance to another member, after the check runs again and its name is typed: live when it can, otherwise only with allowStop, copying its volumes there.
POST /api/v1/instances/{project}/{name}/stateQueues starting or stopping an Apelora instance.
GET /api/v1/jobsJobs newest first, filtered by status, kind and application and paged with after.
GET /api/v1/jobs/{id}A queued job and its progress.
POST /api/v1/jobs/{id}/cancelCancels a job.
GET /api/v1/jobs/{id}/detailA job with its attempts (worker, timings, outcome, error and the Incus operations each started), who queued it, its request id, and the jobs linked to it (retries, and other jobs the same request queued).
POST /api/v1/jobs/{id}/retryQueues a new job that repeats a failed one, linked to it (retryOf); the failed job is kept unchanged as evidence.

Details

POST /api/v1/instances

Queues creation of an instance. Needs deploy. Apelora marks it as its own; only such instances can later be stopped or deleted through Apelora.

Operation ID: create_instance

Needs: deploy.

ParameterInTypeRequiredDefaultMeaning
Idempotency-Keyheaderstringno—Makes the request safe to repeat

Request body (JSON):

FieldTypeRequiredMeaning
architectureslist of stringnoArchitectures the image is known to run on, as Incus names them (x86_64, aarch64, …); empty allows any. Checked against the cluster before creating and against the instance Incus created.
configobjectnoExtra instance config; keys under user.apelora. are reserved.
config.<key>stringnoAny key.
kindone of container, virtual-machine, unknownyescontainer (system or OCI) or virtual-machine.
namestringyes—
networkobject or nullno—
network.aclstringyes—
network.networkstringyes—
network.uplinkstringyesUplink network the application network is created on.
ownerobject or nullno—
owner.applicationstringyes—
owner.modulestringyes—
owner.replicaintegerno—
profileslist of stringnoProfiles to apply, in order; empty applies the project's default profile.
projectstringyes—
rootPoolstring or nullnoThe pool of the root disk when it is not the one the project's profile gives: the shared pool for a module that restarts on another member after a failure. Set by the control plane from the module's volume classes, never by a caller.
sourceobjectyesWhere an instance's root image comes from.
source.aliasstringyesAlias such as debian/13 or, for OCI, repository:tag. Pin an OCI image by digest: repository@sha256:<64 hex digits>.
source.fingerprintstring or nullnoPins a simplestreams image: the full SHA-256 fingerprint Incus gives it. The instance must then be built from exactly this image.
source.protocolone of simplestreams, ociyesHow Incus fetches an image.
source.serverstringyesImage server URL, e.g. a simplestreams server or an OCI registry.
startbooleannoStart the instance once created.
volumeslist of objectnoPersistent volumes, each created for this instance on the member it runs on.
volumes[].classone of data-local, data-replicatednoThe policy a volume is created under.
volumes[].contentone of filesystem, blocknoWhat the volume holds: a mounted filesystem or a raw disk (virtual machines only).
volumes[].namestringyesName in the design, e.g. data: lowercase letters, digits and -, at most 16.
volumes[].pathstring or nullnoWhere a filesystem volume is mounted inside the instance; none for a block volume.
volumes[].poolstring or nullnoThe pool the class resolves to. Set by the control plane, never by a caller.
volumes[].sizeMibintegeryes—
{
  "project": "apelora",
  "name": "tools-01",
  "kind": "container",
  "start": true,
  "source": {
    "protocol": "simplestreams",
    "server": "https://images.linuxcontainers.org",
    "alias": "debian/13"
  },
  "architectures": [
    "x86_64"
  ],
  "config": {
    "limits.cpu": "1",
    "limits.memory": "1GiB"
  }
}

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/instances" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -H "Content-Type: application/json" \
  --data-raw '{"project":"apelora","name":"tools-01","kind":"container","start":true,"source":{"protocol":"simplestreams","server":"https://images.linuxcontainers.org","alias":"debian/13"},"architectures":["x86_64"],"config":{"limits.cpu":"1","limits.memory":"1GiB"}}'

TypeScript:

const result = await client.request("create_instance", { 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.

{
  "jobId": "a8407376-6874-4871-b0e8-eee6ccc57889",
  "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
409incus_project_exists, quota_exceeded
429rate_limited
503incus_project_not_ready, quota_unverifiable, database_not_configured

DELETE /api/v1/instances/{project}/{name}

Queues deletion of an Apelora instance (stopped first if running). Needs deploy.

Operation ID: delete_instance

Needs: deploy.

ParameterInTypeRequiredDefaultMeaning
projectpathstringyes—Incus project.
namepathstringyes—Instance 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 DELETE "$APELORA_URL/api/v1/instances/apelora/tools-01" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY"

TypeScript:

const result = await client.request("delete_instance", { params: { project: "apelora", name: "tools-01" }, 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": "7185279f-0f37-4439-be42-90387c9c5f6b",
  "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
429rate_limited
503database_not_configured

GET /api/v1/instances/{project}/{name}/move

Dry run of moving an instance to another member: whether it keeps running, what it copies, what blocks it. Changes nothing. Needs deploy (or instance.manage).

Operation ID: preview_instance_move

Needs: deploy (or instance.manage).

ParameterInTypeRequiredDefaultMeaning
projectpathstringyes—Incus project.
namepathstringyes—Instance name.
targetquerystringyes—The member to move to.
allowStopquerybooleanno—Whether a short stop is acceptable when it cannot move while running.

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/instances/default/mock-web-01/move?target=mock-member-03" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json"

With options (target, allowStop):

curl --fail-with-body -sS -X GET "$APELORA_URL/api/v1/instances/default/mock-vm-01/move?target=mock-member-03&allowStop=true" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json"

TypeScript:

const result = await client.request("preview_instance_move", { params: { project: "default", name: "mock-web-01" }, query: { target: "mock-member-03", allowStop: 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.

{
  "source": {
    "provider": "mock",
    "observedAt": "2026-10-28T23:26:50.488645Z"
  },
  "plan": {
    "instance": "mock-web-01",
    "from": "mock-member-01",
    "to": "mock-member-03",
    "live": false,
    "stops": false,
    "volumes": [],
    "copyMib": 0,
    "blocked": [
      "mock-web-01 was not created by Apelora",
      "it would have to stop: a container cannot move while running; allow a short stop to move it"
    ],
    "notes": []
  }
}

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
429rate_limited
502an incus_… code
503database_not_configured

POST /api/v1/instances/{project}/{name}/move

Moves an Apelora instance to another member, after the check runs again and its name is typed: live when it can, otherwise only with allowStop, copying its volumes there. Queues an instance.move job. Needs deploy (or instance.manage); audited.

Operation ID: move_instance

Needs: deploy (or instance.manage); audited.

ParameterInTypeRequiredDefaultMeaning
projectpathstringyes—Incus project.
namepathstringyes—Instance name.
Idempotency-Keyheaderstringno—Makes the request safe to repeat

Request body (JSON):

FieldTypeRequiredMeaning
allowStopbooleanno—
confirmstringyesThe instance's name again, typed by the person moving it.
targetstringyes—
{
  "target": "mock-member-03",
  "confirm": "shop-web-01",
  "allowStop": 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/instances/shop-prod/shop-web-01/move" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -H "Content-Type: application/json" \
  --data-raw '{"target":"mock-member-03","confirm":"shop-web-01","allowStop":true}'

TypeScript:

const result = await client.request("move_instance", { params: { project: "shop-prod", name: "shop-web-01" }, body: { target: "mock-member-03", confirm: "shop-web-01", allowStop: 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": "3b55e3b3-9777-4d5d-8c75-ffbca2954983",
  "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
403project_not_allowed, forbidden, mfa_required
404not_found
409move_blocked
429rate_limited
502an incus_… code
503database_not_configured

POST /api/v1/instances/{project}/{name}/state

Queues starting or stopping an Apelora instance. Needs deploy.

Operation ID: change_state

Needs: deploy.

ParameterInTypeRequiredDefaultMeaning
projectpathstringyes—Incus project.
namepathstringyes—Instance name.
Idempotency-Keyheaderstringno—Makes the request safe to repeat

Request body (JSON):

FieldTypeRequiredMeaning
actionone of start, stop, restartyesA state change Apelora can request for one of its instances.
{
  "action": "restart"
}

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/instances/apelora/tools-01/state" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -H "Content-Type: application/json" \
  --data-raw '{"action":"restart"}'

TypeScript:

const result = await client.request("change_state", { params: { project: "apelora", name: "tools-01" }, body: { action: "restart" }, 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": "49a8ef9d-0647-4a24-aca3-54b89bf1f01c",
  "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
429rate_limited
503database_not_configured

GET /api/v1/jobs

Jobs newest first, filtered by status, kind and application and paged with after. Whole platform readers (read-inventory) see every job; anyone else the jobs of applications they may view.

Operation ID: jobs

Needs: a signed-in caller; the server decides what that caller may see.

ParameterInTypeRequiredDefaultMeaning
statusquerystringno—pending, running, succeeded, failed or cancelled.
kindquerystringno—Only kinds starting with this, e.g. instance. or volume.restore.
applicationquerystringno—Only the jobs of this application.
afterquerystringno—Paging: the next of the previous page.
limitqueryintegerno100Jobs per page, 1 to 200 (default 100).

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/jobs" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json"

With options (status, application, limit):

curl --fail-with-body -sS -X GET "$APELORA_URL/api/v1/jobs?status=pending&application=shop&limit=1" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json"

Next page: repeat the request with after set to the response's next. The last page has no next.

TypeScript:

const result = await client.request("jobs", { query: { status: "pending", application: "shop", limit: 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. Long lists are cut to their first items.

{
  "items": [
    {
      "id": "2fd2f28c-1b95-42ea-96d0-e93e6b265cf6",
      "kind": "instance.create",
      "status": "pending",
      "attempts": 0,
      "maxAttempts": 5,
      "lastError": null,
      "incusOperation": null,
      "incusOperations": [],
      "requestId": "61a2c794-bb69-44b8-92f8-b9d22d318604",
      "request": {
        "architectures": [
          "x86_64",
          "aarch64"
        ],
        "config": {
          "environment.APELORA_SERVICE_CACHE_01": "shop-cache-01",
          "environment.APELORA_SERVICE_DB_01": "shop-db-01",
          "limits.cpu": "1",
          "limits.memory": "512MiB"
        },
        "kind": "container",
        "name": "shop-web-01",
        "owner": {
          "application": "shop",
          "module": "web-01",
          "replica": 1
        },
        "profiles": [],
        "project": "shop-prod",
        "source": {
          "alias": "library/nginx@sha256:eb1c896deef834f4e056829fd6dadac8305501f16cc71ba65fb252c8d0aa168e",
          "protocol": "oci",
          "server": "https://docker.io"
        },
        "start": true
      },
      "createdAt": "2026-10-28T23:26:50.028242Z",
      "updatedAt": "2026-10-28T23:26:50.028242Z",
      "queuedBy": "token:docs-verifier",
      "retryOf": null,
      "retriedBy": null,
      "cancelRequestedAt": null,
      "cancelRequestedBy": null,
      "actions": {
        "retry": {
          "allowed": false,
          "reason": "Only failed jobs can be retried."
        },
        "cancel": {
          "allowed": true,
          "reason": null
        }
      }
    },
    {
      "id": "549fc55a-b786-4704-884c-a3f2d1e542c8",
      "kind": "instance.create",
      "status": "pending",
      "attempts": 0,
      "maxAttempts": 5,
      "lastError": null,
      "incusOperation": null,
      "incusOperations": [],
      "requestId": "61a2c794-bb69-44b8-92f8-b9d22d318604",
      "request": {
        "architectures": [
          "x86_64",
          "aarch64"
        ],
        "config": {
          "limits.cpu": "1",
          "limits.memory": "1GiB"
        },
        "kind": "container",
        "name": "shop-cache-01",
        "owner": {
          "application": "shop",
          "module": "cache-01",
          "replica": 1
        },
        "profiles": [],
        "project": "shop-prod",
        "source": {
          "alias": "debian/13",
          "protocol": "simplestreams",
          "server": "https://images.linuxcontainers.org"
        },
        "start": true,
        "volumes": [
          {
            "class": "data-local",
            "content": "filesystem",
            "name": "cache",
            "path": "/var/cache/shop",
            "pool": "mock-local",
            "sizeMib": 2048
          }
        ]
      },
      "createdAt": "2026-10-28T23:26:50.028242Z",
      "updatedAt": "2026-10-28T23:26:50.028242Z",
      "queuedBy": "token:docs-verifier",
      "retryOf": null,
      "retriedBy": null,
      "cancelRequestedAt": null,
      "cancelRequestedBy": null,
      "actions": {
        "retry": {
          "allowed": false,
          "reason": "Only failed jobs can be retried."
        },
        "cancel": {
          "allowed": true,
          "reason": null
        }
      }
    }
  ],
  "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
429rate_limited
503database_not_configured

GET /api/v1/jobs/{id}

A queued job and its progress. Needs read-inventory.

Operation ID: job

Needs: read-inventory.

ParameterInTypeRequiredDefaultMeaning
idpathstring (uuid)yes—Job 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/jobs/2fd2f28c-1b95-42ea-96d0-e93e6b265cf6" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json"

TypeScript:

const result = await client.request("job", { params: { id: "2fd2f28c-1b95-42ea-96d0-e93e6b265cf6" } });

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": "2fd2f28c-1b95-42ea-96d0-e93e6b265cf6",
  "kind": "instance.create",
  "status": "succeeded",
  "attempts": 1,
  "maxAttempts": 5,
  "lastError": null,
  "incusOperation": null,
  "incusOperations": [
    "00000000-0000-4000-8000-000000000002"
  ],
  "requestId": "61a2c794-bb69-44b8-92f8-b9d22d318604",
  "request": {
    "architectures": [
      "x86_64",
      "aarch64"
    ],
    "config": {
      "environment.APELORA_SERVICE_CACHE_01": "shop-cache-01",
      "environment.APELORA_SERVICE_DB_01": "shop-db-01",
      "limits.cpu": "1",
      "limits.memory": "512MiB"
    },
    "kind": "container",
    "name": "shop-web-01",
    "owner": {
      "application": "shop",
      "module": "web-01",
      "replica": 1
    },
    "profiles": [],
    "project": "shop-prod",
    "source": {
      "alias": "library/nginx@sha256:eb1c896deef834f4e056829fd6dadac8305501f16cc71ba65fb252c8d0aa168e",
      "protocol": "oci",
      "server": "https://docker.io"
    },
    "start": true
  },
  "createdAt": "2026-10-28T23:26:50.028242Z",
  "updatedAt": "2026-10-28T23:26:51.631175Z",
  "queuedBy": "token:docs-verifier",
  "retryOf": null,
  "retriedBy": null,
  "cancelRequestedAt": null,
  "cancelRequestedBy": null,
  "actions": {
    "retry": {
      "allowed": false,
      "reason": "Only failed jobs can be retried."
    },
    "cancel": {
      "allowed": false,
      "reason": "Only waiting or running jobs can be cancelled."
    }
  }
}

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

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

POST /api/v1/jobs/{id}/cancel

Cancels a job. A waiting job with nothing in flight is cancelled at once (cancelled); otherwise the request is recorded (requested) and the worker stops at the job kind's next safe point, never in the middle of an Incus change (see safePoints on the job's detail). Repeating the request changes nothing. Refused (409 job_finished) for a finished job. Needs the permission that queues that kind of job on its target; audited.

Operation ID: cancel_job

Needs: a signed-in caller; the server decides what that caller may see.

ParameterInTypeRequiredDefaultMeaning
idpathstring (uuid)yes—Job id

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/jobs/e39e0d32-8564-44b5-9b19-5f4916d08a15/cancel" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json"

TypeScript:

const result = await client.request("cancel_job", { params: { id: "e39e0d32-8564-44b5-9b19-5f4916d08a15" } });

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

Content-Type: application/json.

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

{
  "outcome": "cancelled",
  "job": {
    "id": "e39e0d32-8564-44b5-9b19-5f4916d08a15",
    "kind": "instance.create",
    "status": "cancelled",
    "attempts": 0,
    "maxAttempts": 5,
    "lastError": null,
    "incusOperation": null,
    "incusOperations": [],
    "requestId": "98c2e367-24d5-4770-8526-5e59751984ca",
    "request": {
      "architectures": [
        "x86_64"
      ],
      "config": {
        "limits.cpu": "1",
        "limits.memory": "1GiB"
      },
      "kind": "container",
      "name": "scratch-01",
      "profiles": [],
      "project": "apelora",
      "source": {
        "alias": "debian/13",
        "protocol": "simplestreams",
        "server": "https://images.linuxcontainers.org"
      },
      "start": true
    },
    "createdAt": "2026-10-28T23:26:50.31165Z",
    "updatedAt": "2026-10-28T23:26:50.343069Z",
    "queuedBy": "token:docs-verifier",
    "retryOf": null,
    "retriedBy": null,
    "cancelRequestedAt": "2026-10-28T23:26:50.343069Z",
    "cancelRequestedBy": "token:docs-verifier",
    "actions": {
      "retry": {
        "allowed": false,
        "reason": "Only failed jobs can be retried."
      },
      "cancel": {
        "allowed": false,
        "reason": "Only waiting or running jobs can be cancelled."
      }
    }
  }
}

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
409job_finished
429rate_limited
503database_not_configured

GET /api/v1/jobs/{id}/detail

A job with its attempts (worker, timings, outcome, error and the Incus operations each started), who queued it, its request id, and the jobs linked to it (retries, and other jobs the same request queued). Same visibility as GET /api/v1/jobs/{id}.

Operation ID: job_detail

Needs: a signed-in caller; the server decides what that caller may see.

ParameterInTypeRequiredDefaultMeaning
idpathstring (uuid)yes—Job 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/jobs/2fd2f28c-1b95-42ea-96d0-e93e6b265cf6/detail" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json"

TypeScript:

const result = await client.request("job_detail", { params: { id: "2fd2f28c-1b95-42ea-96d0-e93e6b265cf6" } });

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

Content-Type: application/json.

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

{
  "job": {
    "id": "2fd2f28c-1b95-42ea-96d0-e93e6b265cf6",
    "kind": "instance.create",
    "status": "succeeded",
    "attempts": 1,
    "maxAttempts": 5,
    "lastError": null,
    "incusOperation": null,
    "incusOperations": [
      "00000000-0000-4000-8000-000000000002"
    ],
    "requestId": "61a2c794-bb69-44b8-92f8-b9d22d318604",
    "request": {
      "architectures": [
        "x86_64",
        "aarch64"
      ],
      "config": {
        "environment.APELORA_SERVICE_CACHE_01": "shop-cache-01",
        "environment.APELORA_SERVICE_DB_01": "shop-db-01",
        "limits.cpu": "1",
        "limits.memory": "512MiB"
      },
      "kind": "container",
      "name": "shop-web-01",
      "owner": {
        "application": "shop",
        "module": "web-01",
        "replica": 1
      },
      "profiles": [],
      "project": "shop-prod",
      "source": {
        "alias": "library/nginx@sha256:eb1c896deef834f4e056829fd6dadac8305501f16cc71ba65fb252c8d0aa168e",
        "protocol": "oci",
        "server": "https://docker.io"
      },
      "start": true
    },
    "createdAt": "2026-10-28T23:26:50.028242Z",
    "updatedAt": "2026-10-28T23:26:51.631175Z",
    "queuedBy": "token:docs-verifier",
    "retryOf": null,
    "retriedBy": null,
    "cancelRequestedAt": null,
    "cancelRequestedBy": null,
    "actions": {
      "retry": {
        "allowed": false,
        "reason": "Only failed jobs can be retried."
      },
      "cancel": {
        "allowed": false,
        "reason": "Only waiting or running jobs can be cancelled."
      }
    }
  },
  "application": "shop",
  "target": "shop-prod/shop-web-01",
  "startedAt": "2026-10-28T23:26:51.535621Z",
  "finishedAt": "2026-10-28T23:26:51.631175Z",
  "attempts": [
    {
      "attempt": 1,
      "worker": "docs-worker",
      "startedAt": "2026-10-28T23:26:51.535621Z",
      "endedAt": "2026-10-28T23:26:51.631175Z",
      "outcome": "succeeded",
      "error": null,
      "incusOperations": [
        "00000000-0000-4000-8000-000000000002"
      ]
    }
  ],
  "linked": [
    {
      "id": "549fc55a-b786-4704-884c-a3f2d1e542c8",
      "kind": "instance.create",
      "status": "succeeded",
      "relation": "same-request",
      "target": "shop-prod/shop-cache-01",
      "createdAt": "2026-10-28T23:26:50.028242Z"
    },
    {
      "id": "78f29588-65a5-4735-b3db-16e4361d8ebd",
      "kind": "instance.create",
      "status": "succeeded",
      "relation": "same-request",
      "target": "shop-prod/shop-db-01",
      "createdAt": "2026-10-28T23:26:50.028242Z"
    }
  ],
  "safePoints": "Only before the instance is created. Once it exists, its volumes are attached and it is started, so a cancelled create never leaves a half-built instance."
}

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

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

POST /api/v1/jobs/{id}/retry

Queues a new job that repeats a failed one, linked to it (retryOf); the failed job is kept unchanged as evidence. Needs the permission that queues that kind of job on its target. Refused (409) when the job did not fail (not_failed), was retried already (already_retried), a newer job for the same target was queued since (superseded), or it is an application operation (not_retryable: start it again from the application). Audited.

Operation ID: retry_job

Needs: a signed-in caller; the server decides what that caller may see.

ParameterInTypeRequiredDefaultMeaning
idpathstring (uuid)yes—Job id
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/jobs/2c06a8c4-5671-49b6-8420-b50e028ed1e8/retry" \
  -H "Authorization: Bearer $APELORA_TOKEN" \
  -H "Accept: application/json" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY"

TypeScript:

const result = await client.request("retry_job", { params: { id: "2c06a8c4-5671-49b6-8420-b50e028ed1e8" }, 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": "3ef0e04e-e208-4f24-abd9-6f5c40b267d9",
  "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
409already_retried, not_failed, not_retryable, superseded
429rate_limited
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.