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
| Operation | What it does |
|---|---|
POST /api/v1/instances | Queues 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}/move | Dry run of moving an instance to another member: whether it keeps running, what it copies, what blocks it. |
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. |
POST /api/v1/instances/{project}/{name}/state | Queues starting or stopping an Apelora instance. |
GET /api/v1/jobs | Jobs 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}/cancel | Cancels a job. |
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). |
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. |
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.
| Parameter | In | Type | Required | Default | Meaning |
|---|---|---|---|---|---|
Idempotency-Key | header | string | no | — | Makes the request safe to repeat |
Request body (JSON):
| Field | Type | Required | Meaning |
|---|---|---|---|
architectures | list of string | no | Architectures 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. |
config | object | no | Extra instance config; keys under user.apelora. are reserved. |
config.<key> | string | no | Any key. |
kind | one of container, virtual-machine, unknown | yes | container (system or OCI) or virtual-machine. |
name | string | yes | — |
network | object or null | no | — |
network.acl | string | yes | — |
network.network | string | yes | — |
network.uplink | string | yes | Uplink network the application network is created on. |
owner | object or null | no | — |
owner.application | string | yes | — |
owner.module | string | yes | — |
owner.replica | integer | no | — |
profiles | list of string | no | Profiles to apply, in order; empty applies the project's default profile. |
project | string | yes | — |
rootPool | string or null | no | The 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. |
source | object | yes | Where an instance's root image comes from. |
source.alias | string | yes | Alias such as debian/13 or, for OCI, repository:tag. Pin an OCI image by digest: repository@sha256:<64 hex digits>. |
source.fingerprint | string or null | no | Pins a simplestreams image: the full SHA-256 fingerprint Incus gives it. The instance must then be built from exactly this image. |
source.protocol | one of simplestreams, oci | yes | How Incus fetches an image. |
source.server | string | yes | Image server URL, e.g. a simplestreams server or an OCI registry. |
start | boolean | no | Start the instance once created. |
volumes | list of object | no | Persistent volumes, each created for this instance on the member it runs on. |
volumes[].class | one of data-local, data-replicated | no | The policy a volume is created under. |
volumes[].content | one of filesystem, block | no | What the volume holds: a mounted filesystem or a raw disk (virtual machines only). |
volumes[].name | string | yes | Name in the design, e.g. data: lowercase letters, digits and -, at most 16. |
volumes[].path | string or null | no | Where a filesystem volume is mounted inside the instance; none for a block volume. |
volumes[].pool | string or null | no | The pool the class resolves to. Set by the control plane, never by a caller. |
volumes[].sizeMib | integer | yes | — |
{
"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.
| Status | Codes |
|---|---|
400 | invalid_request |
401 | unauthenticated, invalid_token |
403 | project_not_allowed, forbidden, mfa_required |
404 | not_found |
409 | incus_project_exists, quota_exceeded |
429 | rate_limited |
503 | incus_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.
| Parameter | In | Type | Required | Default | Meaning |
|---|---|---|---|---|---|
project | path | string | yes | — | Incus project. |
name | path | string | yes | — | Instance name. |
Idempotency-Key | header | string | no | — | 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.
| Status | Codes |
|---|---|
400 | invalid_request |
401 | unauthenticated, invalid_token |
403 | project_not_allowed, forbidden, mfa_required |
429 | rate_limited |
503 | database_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).
| Parameter | In | Type | Required | Default | Meaning |
|---|---|---|---|---|---|
project | path | string | yes | — | Incus project. |
name | path | string | yes | — | Instance name. |
target | query | string | yes | — | The member to move to. |
allowStop | query | boolean | no | — | 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.
| Status | Codes |
|---|---|
400 | invalid_request |
401 | unauthenticated, invalid_token |
403 | project_not_allowed, forbidden, mfa_required |
404 | not_found |
429 | rate_limited |
502 | an incus_… code |
503 | database_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.
| Parameter | In | Type | Required | Default | Meaning |
|---|---|---|---|---|---|
project | path | string | yes | — | Incus project. |
name | path | string | yes | — | Instance name. |
Idempotency-Key | header | string | no | — | Makes the request safe to repeat |
Request body (JSON):
| Field | Type | Required | Meaning |
|---|---|---|---|
allowStop | boolean | no | — |
confirm | string | yes | The instance's name again, typed by the person moving it. |
target | string | yes | — |
{
"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.
| Status | Codes |
|---|---|
400 | confirmation_mismatch, invalid_request |
401 | unauthenticated, invalid_token |
403 | project_not_allowed, forbidden, mfa_required |
404 | not_found |
409 | move_blocked |
429 | rate_limited |
502 | an incus_… code |
503 | database_not_configured |
POST /api/v1/instances/{project}/{name}/state
Queues starting or stopping an Apelora instance. Needs deploy.
Operation ID: change_state
Needs: deploy.
| Parameter | In | Type | Required | Default | Meaning |
|---|---|---|---|---|---|
project | path | string | yes | — | Incus project. |
name | path | string | yes | — | Instance name. |
Idempotency-Key | header | string | no | — | Makes the request safe to repeat |
Request body (JSON):
| Field | Type | Required | Meaning |
|---|---|---|---|
action | one of start, stop, restart | yes | A 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.
| Status | Codes |
|---|---|
400 | invalid_request |
401 | unauthenticated, invalid_token |
403 | project_not_allowed, forbidden, mfa_required |
429 | rate_limited |
503 | database_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.
| Parameter | In | Type | Required | Default | Meaning |
|---|---|---|---|---|---|
status | query | string | no | — | pending, running, succeeded, failed or cancelled. |
kind | query | string | no | — | Only kinds starting with this, e.g. instance. or volume.restore. |
application | query | string | no | — | Only the jobs of this application. |
after | query | string | no | — | Paging: the next of the previous page. |
limit | query | integer | no | 100 | Jobs 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.
| Status | Codes |
|---|---|
400 | invalid_request |
401 | unauthenticated, invalid_token |
403 | forbidden, mfa_required |
429 | rate_limited |
503 | database_not_configured |
GET /api/v1/jobs/{id}
A queued job and its progress. Needs read-inventory.
Operation ID: job
Needs: read-inventory.
| Parameter | In | Type | Required | Default | Meaning |
|---|---|---|---|---|---|
id | path | string (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.
| Status | Codes |
|---|---|
401 | unauthenticated, invalid_token |
403 | forbidden, mfa_required |
404 | not_found |
429 | rate_limited |
503 | database_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.
| Parameter | In | Type | Required | Default | Meaning |
|---|---|---|---|---|---|
id | path | string (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.
| Status | Codes |
|---|---|
400 | invalid_request |
401 | unauthenticated, invalid_token |
403 | project_not_allowed, forbidden, mfa_required |
404 | not_found |
409 | job_finished |
429 | rate_limited |
503 | database_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.
| Parameter | In | Type | Required | Default | Meaning |
|---|---|---|---|---|---|
id | path | string (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.
| Status | Codes |
|---|---|
401 | unauthenticated, invalid_token |
403 | forbidden, mfa_required |
404 | not_found |
429 | rate_limited |
503 | database_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.
| Parameter | In | Type | Required | Default | Meaning |
|---|---|---|---|---|---|
id | path | string (uuid) | yes | — | Job id |
Idempotency-Key | header | string | no | — | 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.
| Status | Codes |
|---|---|
400 | invalid_request |
401 | unauthenticated, invalid_token |
403 | project_not_allowed, forbidden, mfa_required |
404 | not_found |
409 | already_retried, not_failed, not_retryable, superseded |
429 | rate_limited |
503 | database_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.