App Environment Job Runs API
The Job Runs API lets customers run finite workloads in Quave ONE. A Job is a first-class workload type, separate from long-running Apps, Databases & Services, and Functions. Each run creates a visible JobRun with status, logs, timing, and exit-code metadata.
Make sure to read the Get Started document to understand how the API works.
Note: These endpoints accept user tokens. Environment tokens are also accepted when the token is scoped to the target app environment.
Create a Job app
Create a Job app with the Apps API by setting dockerPreset to JOB. Job apps do not require port, hosts, HTTP probes, or a long-running deployment. They do require a runnable image or source/build configuration before a run can start.
Example:
curl -X POST \
-H 'Authorization: YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"name": "Rails migrations",
"accountId": "ACCOUNT_ID",
"dockerPreset": "JOB",
"useImage": true,
"image": "ghcr.io/acme/my-rails-app:2026-06-19",
"jobConfig": {
"command": "bin/rails db:migrate",
"shell": true,
"timeoutSeconds": 1800,
"ttlSecondsAfterFinished": 21600,
"backoffLimit": 0
},
"isCliDeployment": true
}' \
https://api.quave.cloud/api/public/v1/app
Then create an app environment for that Job app with the App Environments API. You can set or override jobConfig at the environment level.
Job Config object
jobConfig can be stored on the Job app or overridden on the Job environment. Safe execution fields can also be provided for one run, and those per-run values are snapshotted into the JobRun so history stays stable even if the app config changes later.
| Field | Type | Description |
|---|---|---|
command | String | Command to execute. Required before a run starts. |
args | Array of strings | Optional argument list. If shell is true, you can also include arguments directly in command. |
shell | Boolean | Runs command through /bin/sh -lc when true. Default: true. |
workingDir | String | Optional working directory inside the container. |
timeoutSeconds | Integer | Active deadline for the run. Default: 1800. |
ttlSecondsAfterFinished | Integer | Kubernetes TTL after a run finishes. Default: 21600; minimum: 300. |
backoffLimit | Integer | Kubernetes Job retry backoff limit. Default: 0. |
maxConcurrency | Integer | Maximum active runs for this Job environment. Default: 1 unless concurrent runs are allowed. |
allowConcurrentRuns | Boolean | Allows unlimited active runs when true and maxConcurrency is omitted. Default: false. |
schedule | String | Reserved for scheduled Jobs. |
scheduleTimeZone | String | Reserved for scheduled Jobs. |
Per-run overrides only accept
command,args,shell,workingDir,timeoutSeconds,ttlSecondsAfterFinished, andbackoffLimit. Configure concurrency and schedule fields on the Job app or environment, not in a run request.
To edit saved environment-level Job settings after creation, use:
PATCH /api/public/v1/app-env/job-config
This saves the Job environment override as a pending deploy change. Default future runs continue to use the previously applied Job config until you apply the change. It does not mutate historical JobRun snapshots. See the App Environments API.
Config hierarchy and Apply Changes
Default JobRuns use the applied deployment snapshot for the environment. The snapshot is captured from the app and environment settings when a Job image/build is deployed or when pending Job settings are applied.
The saved settings are layered into the applied snapshot:
- App-level
jobConfigdefaults. - Environment-level
jobConfigoverrides. - Applied deployment snapshot for the current content.
- Per-run
jobConfigoverride from the run request.
When an applied deployment snapshot exists, a default run uses that snapshot. If no applied snapshot exists yet, Quave ONE falls back to the current app and environment settings. Editing app-level or environment-level jobConfig creates pending deploy changes and does not change default JobRuns until you:
- call
POST /api/public/v1/app-env/apply-changes, - call the MCP tool
apply-app-env-changes, - click Apply changes in the dashboard, or
- pass
applyImmediately: trueto an update endpoint that supports it.
If you want to test a new command without applying it as the saved default, pass a per-run jobConfig in the POST /api/public/v1/app-env/job-runs request. That override is stored only on the new JobRun.
Run a Job now
Send a POST request to /api/public/v1/app-env/job-runs.
You must provide either appEnvId or envName. You can optionally provide contentId to run a specific image/build version and any jobConfig override for this run.
If you omit jobConfig, the run uses the currently applied Job config snapshot, or the current saved app and environment settings if no applied snapshot exists yet. If you include jobConfig, those values override the applied snapshot for this run only.
curl -X POST \
-H 'Authorization: YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"appEnvId": "APP_ENV_ID",
"jobConfig": {
"command": "bin/rails db:migrate"
}
}' \
https://api.quave.cloud/api/public/v1/app-env/job-runs
Example response:
{
"success": true,
"jobRun": {
"jobRunId": "JOB_RUN_ID",
"accountId": "ACCOUNT_ID",
"appId": "APP_ID",
"appEnvId": "APP_ENV_ID",
"contentId": "CONTENT_ID",
"triggerType": "API",
"command": "bin/rails db:migrate",
"args": [],
"shell": true,
"statusLabel": "Queued",
"isInProgress": true,
"isTerminal": false,
"isSuccess": false,
"isFailed": false,
"status": "QUEUED",
"region": "us-5",
"timeoutSeconds": 1800,
"ttlSecondsAfterFinished": 21600,
"backoffLimit": 0,
"createdAt": "2026-06-19T18:00:00.000Z"
}
}
List Job runs
Send a GET request to /api/public/v1/app-env/job-runs.
| Query parameter | Type | Description |
|---|---|---|
appEnvId | String | App environment ID. Required unless envName is provided. |
envName | String | CLI environment name. Required unless appEnvId is provided. |
limit | Integer | Max runs to return. Default: 50. Maximum: 100. |
curl -X GET \
-H 'Authorization: YOUR_TOKEN' \
'https://api.quave.cloud/api/public/v1/app-env/job-runs?appEnvId=APP_ENV_ID&limit=20'
Get one Job run
Send a GET request to /api/public/v1/app-env/job-run.
| Query parameter | Type | Description |
|---|---|---|
jobRunId | String | Job run ID. |
appEnvId | String | Optional app environment ID. Required when authenticating with an environment token. |
envName | String | Optional CLI environment name. Can be used instead of appEnvId for environment-token context. |
curl -X GET \
-H 'Authorization: YOUR_TOKEN' \
'https://api.quave.cloud/api/public/v1/app-env/job-run?jobRunId=JOB_RUN_ID'
Cancel a Job run
Send a POST request to /api/public/v1/app-env/job-run/cancel.
curl -X POST \
-H 'Authorization: YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"jobRunId": "JOB_RUN_ID",
"appEnvId": "APP_ENV_ID"
}' \
https://api.quave.cloud/api/public/v1/app-env/job-run/cancel
Cancel deletes the Kubernetes Job for that run when it already started, then marks the JobRun as CANCELED.
When authenticating with an environment token, include appEnvId or envName in the request body so Quave ONE can verify the token belongs to the run's environment.
Rerun a Job
Send a POST request to /api/public/v1/app-env/job-run/rerun.
By default, rerun uses the previous run's immutable config snapshot and content. You can override contentId or jobConfig for the new run.
When authenticating with an environment token, include appEnvId or envName in the request body so Quave ONE can verify the token belongs to the previous run's environment.
curl -X POST \
-H 'Authorization: YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"jobRunId": "PREVIOUS_JOB_RUN_ID",
"jobConfig": {
"timeoutSeconds": 3600
}
}' \
https://api.quave.cloud/api/public/v1/app-env/job-run/rerun
Get Job run logs
Send a GET request to /api/public/v1/app-env/job-run/logs.
| Query parameter | Type | Description |
|---|---|---|
jobRunId | String | Job run ID. |
appEnvId | String | Optional app environment ID. Required when authenticating with an environment token. |
envName | String | Optional CLI environment name. Can be used instead of appEnvId for environment-token context. |
stream | String | Optional stream filter: INFO or ERROR. |
search | String | Optional search text. |
size | Integer | Number of log entries. |
from | String | Start timestamp in ISO 8601 format. |
to | String | End timestamp in ISO 8601 format. |
isLive | Boolean | Whether to retrieve recent live logs. |
scrollId | String | Scroll ID for pagination. |
callScroll | Boolean | Continue an existing scroll. |
newScroll | Boolean | Start a new scroll. |
curl -X GET \
-H 'Authorization: YOUR_TOKEN' \
'https://api.quave.cloud/api/public/v1/app-env/job-run/logs?jobRunId=JOB_RUN_ID&stream=INFO'
Job statuses
| Status | Description |
|---|---|
QUEUED | Run was recorded and the internal executor is queued. |
STARTING | Quave ONE is creating the Kubernetes Job. |
RUNNING | Kubernetes reports an active Job pod. |
SUCCEEDED | Job completed successfully. |
FAILED | Job failed or exited with a non-zero status. |
CANCELED | Run was canceled by the user or the Kubernetes Job was deleted. |
TIMED_OUT | Active deadline or Quave ONE polling deadline was exceeded. |
NOT_FOUND | Kubernetes Job was not found; it may have been deleted or garbage-collected before Quave ONE persisted the terminal state. |
JobRun responses also include helper fields for release automation:
| Field | Description |
|---|---|
statusLabel | Human-readable label for the status. |
isInProgress | True for QUEUED, STARTING, and RUNNING. |
isTerminal | True when no more status changes are expected. |
isSuccess | True only for SUCCEEDED. |
isFailed | True for terminal statuses that should block a deploy, including FAILED, CANCELED, TIMED_OUT, and NOT_FOUND. |
Rails migration pattern
For Rails migrations, create a Job app using the same image as the app version you want to migrate, then set the Rails command that exists inside your container, usually bin/rails db:migrate for apps with Rails binstubs or bundle exec rails db:migrate:
{
"command": "bin/rails db:migrate",
"shell": true,
"timeoutSeconds": 1800,
"backoffLimit": 0
}
This keeps the migration visible to the customer as a normal JobRun with status, logs, billing, and audit history instead of hiding it as an internal release step.
Recommended flow:
- Create the Job app with
dockerPreset: "JOB". - Create the Job environment.
- Deploy or build the image for that environment.
- Apply pending Job setting changes if the command was edited after deployment.
- Create a
JobRun. - Poll the run until it reaches a terminal status.
- Fetch the JobRun logs.
Poll by JobRun ID before app deploy
The create response includes jobRun.jobRunId. Use it to poll the run:
JOB_RUN_ID="JOB_RUN_ID_FROM_CREATE_RESPONSE"
curl -X GET \
-H 'Authorization: YOUR_TOKEN' \
"https://api.quave.cloud/api/public/v1/app-env/job-run?jobRunId=${JOB_RUN_ID}"
Only deploy the web app when the response has status: "SUCCEEDED" or isSuccess: true. Stop the release and fetch logs when isFailed is true:
curl -X GET \
-H 'Authorization: YOUR_TOKEN' \
"https://api.quave.cloud/api/public/v1/app-env/job-run/logs?jobRunId=${JOB_RUN_ID}&stream=INFO"
For CLI-based pipelines, the same deploy gate is:
IMAGE="ghcr.io/acme/rails-app:${GITHUB_SHA}"
quaveone job run \
--env acme-rails-migration-prod \
--image "$IMAGE" \
--command "bin/rails db:migrate" \
--wait \
--timeout-seconds 1800 \
--backoff-limit 0 \
--logs-on-failure
quaveone deploy \
--env acme-rails-web-prod \
--image "$IMAGE" \
--wait
For the full customer guide, see Jobs.