Skip to content

Jobs

Checked against the API code 3 November 2026

Some actions take longer than one request should wait: restarts, reinstalls, backups, restores and bulk domain changes. They answer at once with 202 Accepted and a job. The job runs in the background. You poll it until it ends.

The examples on this page restart a VPS with POST /api/v2/vps/{id}/actions/restart. Running them restarts that server.

export VPS_ID="vps_…" # your VPS: List VPS instances
POST /api/v2/vps/{id}/actions/restart
curl -X POST "https://cloud.hostup.se/api/v2/vps/$VPS_ID/actions/restart" \
-H "Authorization: Bearer $HOSTUP_API_KEY" \
-H "Content-Type: application/json"
Response 202
{
"operation": {
"status": "queued",
"jobId": "job_01hxa3b4c5d6e7f8g9h0j1k2m3",
"pollUrl": "/api/jobs/job_01hxa3b4c5d6e7f8g9h0j1k2m3"
}
}
export JOB_ID="job_…" # operation.jobId above
  • jobId names the job. Most job IDs start with job_. Bulk domain changes return dbj_ IDs.
  • pollUrl is the path to poll. Put https://cloud.hostup.se in front of it. For most actions it is the job endpoint below. Jobs polled at another endpoint lists the others.

When pollUrl is null, there is nothing to poll. Most often the action has already ended. A paid IP change through Change the network setup of a VPS waits for its invoice instead. pendingChange in that response names the invoice to pay and the new address.

Use pollUrl and jobId exactly as you got them. Never build a job ID from a VPS, domain or order ID.

GET /api/jobs/{jobId} returns the job. It needs no particular scope. The key must belong to the account that started the job.

GET /api/jobs/{jobId}
curl "https://cloud.hostup.se/api/jobs/$JOB_ID" \
-H "Authorization: Bearer $HOSTUP_API_KEY"
Response 200
{
"success": true,
"timestamp": "2026-11-02T10:15:20.000Z",
"requestId": "3f1c2a9e-8d4b-4c7a-9e21-5b6f0d2c7a10",
"data": {
"jobId": "job_01hxa3b4c5d6e7f8g9h0j1k2m3",
"queueType": "vm",
"type": "restart",
"state": "active",
"status": "running",
"progress": 70,
"progressText": "Restarting VM...",
"statusKey": "instance.jobs.restarting_vm",
"progressDetailText": null,
"progressDetails": null,
"result": null,
"error": null,
"isCompleted": false,
"isFailed": false,
"isRunning": true,
"isWaiting": false,
"createdAt": "2026-11-02T10:15:02.000Z",
"finishedAt": null
}
}

This endpoint is older than v2. The job sits inside data, next to success, timestamp and requestId. Its errors use a plain JSON shape, not Problem Details. See Errors from the job endpoint.

The fields you use:

FieldWhat it tells you
data.statuswaiting, running, completed or failed. Branch on this one.
data.progressHow far the job has come, from 0 to 100.
data.progressTextA short English line about the current step. Show it to a person. Do not parse it.
data.statusKeyA stable key for the same step, for clients that show their own translated text.
data.progressDetailsProgress details when the job reports them. Bulk domain changes give current, total and currentDomain. Orders and bulk renewals give items, one row per line. null when the job reports none.
data.resultWhat the job returned. null until it has finished. The shape depends on data.type.
data.errorA short message when the job failed. A job that is retrying a step can also show Job reported an error. here. So can a job that completed after a retry. Decide on data.status, not on this field.
data.createdAt, data.finishedAtWhen the job was created and when it ended. finishedAt is null while it runs.
data.stateA lower-level state, such as delayed or active. Branch on data.status instead.

The 202 says pending, queued or in_progress. The job endpoint says waiting or running. Both end in completed or failed.

A job can go back from running to waiting. That happens when a step is retried. It also happens when a VPS start waits for free capacity. Keep polling until the status is completed or failed.

completed and failed are the only final states. Stop polling when you see one of them.

Completed. data.result holds what the job returned. A job that finished with a warning is still completed. The warning is in data.result.warning. data.statusKey is then instance.jobs.completed_with_warnings.

Failed. data.result is null. data.error is a short message, most often Job failed. An ISO download says what was wrong with the link instead. data.progressText explains the failure when we know more, for example when a VPS could not start for lack of capacity. Show progressText to the person who started the action.

A failed job: the data object
{
"status": "failed",
"progressText": "Operation failed",
"error": "Job failed.",
"result": null,
"finishedAt": "2026-11-02T10:16:01.000Z",
"…": "…"
}

A job from an order can end as completed although the order was refused. Jobs from orders shows how to tell.

Save the script below as wait-for-job.sh. It needs curl, jq and your key in $HOSTUP_API_KEY (Quickstart). Run it with the pollUrl from the 202:

chmod +x wait-for-job.sh
./wait-for-job.sh "/api/jobs/$JOB_ID"

It polls every 4 seconds and stops on completed or failed. It waits longer when the API is busy. When the job completes, it prints data.result. A refused order counts as a failure.

wait-for-job.sh
#!/usr/bin/env bash
# Usage: ./wait-for-job.sh "/api/jobs/$JOB_ID"
poll_url="$1"
case "$poll_url" in
/api/jobs/*) ;;
*) echo "Pass a pollUrl that starts with /api/jobs/."; exit 1 ;;
esac
for attempt in $(seq 1 150); do # 150 polls about 4 s apart: 10 minutes
code=$(curl -s -o job.json -w '%{http_code}' "https://cloud.hostup.se$poll_url" \
-H "Authorization: Bearer $HOSTUP_API_KEY")
case "$code" in
200) ;;
429|5??|000) sleep 15; continue ;; # busy, server error or no connection: retry
*) echo "Polling stopped with HTTP $code:"; cat job.json; exit 1 ;;
esac
status=$(jq -r '.data.status' job.json)
progress=$(jq -r '.data.progress' job.json)
text=$(jq -r '.data.progressText // ""' job.json)
echo "$status $progress% $text"
case "$status" in
completed)
if [ "$(jq '.data.result.status | numbers >= 400' job.json)" = true ]; then
echo "Refused:"; jq '.data.result' job.json; exit 1
fi
jq '.data.result' job.json; exit 0 ;;
failed) jq -r '.data.error, .data.progressText' job.json; exit 1 ;;
waiting|running) ;;
*) echo "No job status in the answer:"; cat job.json; exit 1 ;;
esac
sleep 4
done
echo "The job has not ended after 10 minutes."
exit 1

Poll every 3 to 5 seconds. Every poll counts toward the key’s rate limit. A key may make 300 requests per five minutes. One job polled every 4 seconds uses 75 of them. Follow at most three jobs at that rate. To follow more, poll each one less often. Raise the 150 for jobs that run long, such as backups, reinstalls and new orders.

A completed job is kept for at most 24 hours. It can be removed much sooner, when many newer jobs have finished since. A failed job is kept for up to 7 days. After that the job endpoint answers 404 for it. Read the outcome while you poll. Store what you need from result.

GET /api/v2/vps/{id}/active-jobs lists the jobs that are queued or running on a server right now. Check it before you start a power, backup, reinstall or disk action. Then you do not start work that clashes. It also finds a running job whose ID you lost.

GET /api/v2/vps/{id}/active-jobs
curl "https://cloud.hostup.se/api/v2/vps/$VPS_ID/active-jobs" \
-H "Authorization: Bearer $HOSTUP_API_KEY"
Response 200
{
"jobs": [
{
"id": "job_01hxa3b4c5d6e7f8g9h0j1k2m3",
"type": "backup",
"status": "in_progress",
"progress": 45,
"…": "…"
}
],
"hasActiveBackup": true,
"hasActiveOperation": true,
"actions": {
"canPerformActions": {
"allowed": false,
"reason": "A backup is currently running. Try again after it finishes."
}
}
}

Poll a listed job with its id at /api/jobs/{jobId}. An empty jobs list means nothing is running.

Two kinds of jobs are not followed at the job endpoint. It answers 404 for their IDs.

Domain availability checks return a dcheck_… ID. Poll it with Get availability check results.

Web hosting restores return a job_… ID like other jobs. Restore a web hosting backup and Restore a database backup give a pollUrl for Get the status of a web hosting restore. That endpoint answers in another shape. status (queued, running, completed or failed) and progressPercent are at the top level, not inside data. The polling loop above does not work for these jobs. Poll their pollUrl and read status.

POST /api/v2/orders answers at once with the order and no job. Only a body with "async": true answers 202 with a job. That job works like the others on this page. An order that restores a backup onto a new VPS is always answered at once.

A job from an order ends as completed also when the order was refused. Look at data.result:

  • An order that went through has the order in result, with its ord_ ID in result.id.
  • A refused order has a numeric result.status of 400 or higher. Its result.code names the reason, for example domain_conflict. The rest of result is the same error document the order endpoint would have answered with. progressText can still say the order is complete.

The polling script above treats a refused order as a failure.

The job endpoint answers errors as plain JSON with error, message, code, timestamp and requestId. The code values are upper case.

StatusCodeMeaning
400BAD_REQUESTThe job ID has characters other than letters, digits, _, - and :.
401UNAUTHORIZEDNo valid API key was sent.
403FORBIDDENThe job belongs to another account or to a service the key cannot see.
404NOT_FOUNDNo job has this ID. A job is also gone some time after it ends. See How long jobs are kept.
429API_KEY_RATE_LIMIT_EXCEEDED or noneToo many requests. When Retry-After is there, wait that many seconds. The key’s own limit sends no Retry-After. Wait up to five minutes. Then poll less often.
500INTERNAL_ERRORSomething went wrong on our side. Poll again a little later.

Next: Errors and retries