Skip to content

Take a snapshot and roll back to it

5 steps · Checked against the API code 3 November 2026

A guide does one task from start to finish. Every endpoint, with all its fields and cases, is in the API reference.

A snapshot saves the state of a VPS’s disk at one moment. Take one before an upgrade, a migration or a big configuration change. If the change goes wrong, roll the VPS back to the snapshot. Hostup deletes each snapshot about a day after it was taken. For a copy you need to keep longer, use a backup (Snapshot or backup).

export VPS_ID="vps_…" # your VPS: List VPS instances

GET /api/v2/vps/{id}/snapshots lists the snapshots of the VPS. It also says whether you can take one now.

GET /api/v2/vps/{id}/snapshots
curl "https://cloud.hostup.se/api/v2/vps/$VPS_ID/snapshots" \
-H "Authorization: Bearer $HOSTUP_API_KEY"
Response 200
{
"data": [],
"available": true,
"reason": null,
"limits": {
"totalSnapshots": 2,
"usedSnapshots": 0
},
"actions": {
"canCreate": { "allowed": true, "reason": null },
"canRestore": { "allowed": true, "reason": null }
}
}

actions.canCreate.allowed must be true. limits shows how many snapshot slots the VPS has and how many are in use. When allowed is false, reason says why:

reasonWhat to do
“Snapshots are not available on your current plan.”The VPS has no snapshot slots. available is false as well. Add snapshot slots.
“Maximum snapshots reached (2). Delete old snapshots to create more.”Every slot is in use. A rollback is still allowed. See If every slot is used.
“Another snapshot operation is currently running.”Wait until it has finished. Then list the snapshots again.
“Snapshot job status could not be confirmed. Try again shortly.”List the snapshots again a little later.
“This action requires an active service.”The VPS is not active, for example suspended.
“You do not have permission to create snapshots.”The key lacks backup:vm. Use a key with that scope. In canRestore it reads “You do not have permission to restore snapshots.”

Delete a snapshot you no longer need, as in If the change worked in step 5. Or give the VPS more slots: Add snapshot slots.

Give the snapshot a name. Later calls find the snapshot by it. The rule: a letter first, then letters a–z and A–Z, digits, _ or -, at most 40 characters in all. It must not be in the list yet. Don’t use current: the list never shows a snapshot with that name. description is optional. Text longer than 1024 characters is cut.

POST /api/v2/vps/{id}/snapshots
curl -X POST "https://cloud.hostup.se/api/v2/vps/$VPS_ID/snapshots" \
-H "Authorization: Bearer $HOSTUP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "before-upgrade", "description": "Before the PHP upgrade"}'
Response 202
{
"operation": {
"status": "queued",
"jobId": "job_01hxa3b4c5d6e7f8g9h0j1k2m3",
"pollUrl": "/api/jobs/job_01hxa3b4c5d6e7f8g9h0j1k2m3"
}
}
export JOB_ID="job_…" # operation.jobId above

The VPS keeps running while the snapshot is taken.

Poll the job until data.status is completed or failed. While it runs, it is waiting or running.

GET /api/jobs/{jobId}
curl "https://cloud.hostup.se/api/jobs/$JOB_ID" \
-H "Authorization: Bearer $HOSTUP_API_KEY"
Response 200
{
"success": true,
"data": {
"jobId": "job_01hxa3b4c5d6e7f8g9h0j1k2m3",
"type": "snapshot_create",
"status": "completed",
"progress": 100,
"…": "…"
},
"…": "…"
}

To poll until it ends, run the script from Jobs:

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

If it ends failed, data.progressText says why when it is known.

When the job has completed, list the snapshots again. The new one is in data. Don’t start your change until it is there.

GET /api/v2/vps/{id}/snapshots
curl "https://cloud.hostup.se/api/v2/vps/$VPS_ID/snapshots" \
-H "Authorization: Bearer $HOSTUP_API_KEY"
Response 200
{
"data": [
{
"name": "before-upgrade",
"description": "Before the PHP upgrade",
"createdAt": "2026-11-02T10:00:12.000Z",
"expiresAt": "2026-11-03T10:00:12.000Z",
"…": "…"
}
],
"limits": {
"totalSnapshots": 2,
"usedSnapshots": 1
},
"…": "…"
}

expiresAt is one day after createdAt. From then on Hostup deletes the snapshot automatically. Keep the name. The rollback and the delete use it.

export SNAPSHOT_ID="before-upgrade" # data[].name above

A job can complete without a snapshot. That happens when the snapshot is refused after the call was accepted. Take it again with another name. Then wait for that job as above.

Run the upgrade, the migration or whatever you took the snapshot for. Then check the result. Step 5 depends on it.

Delete the snapshot or let it expire. A snapshot holds a slot until it is gone. A deleted snapshot cannot be brought back.

DELETE /api/v2/vps/{id}/snapshots/{snapshotId}
curl -X DELETE "https://cloud.hostup.se/api/v2/vps/$VPS_ID/snapshots/$SNAPSHOT_ID" \
-H "Authorization: Bearer $HOSTUP_API_KEY"

The answer is 204 No Content without a body. The delete itself runs in the background afterwards. The snapshot can stay in the list for a short while. List running jobs on a VPS shows the delete while it runs. A name that does not exist also gets 204.

Roll the VPS back to the snapshot: Roll back to the snapshot.

A rollback puts the disk back to the state in the snapshot. Do it while the snapshot is still in the list.

List the snapshots. actions.canRestore.allowed must be true. Take the snapshot’s name from data.

GET /api/v2/vps/{id}/snapshots
curl "https://cloud.hostup.se/api/v2/vps/$VPS_ID/snapshots" \
-H "Authorization: Bearer $HOSTUP_API_KEY"
Response 200
{
"data": [
{
"name": "before-upgrade",
"expiresAt": "2026-11-03T10:00:12.000Z",
"…": "…"
}
],
"actions": {
"canRestore": { "allowed": true, "reason": null },
"…": "…"
},
"…": "…"
}
export SNAPSHOT_ID="before-upgrade" # data[].name above

The call does not check that the name exists. A wrong name still stops a running VPS. Use the name from the list.

POST /api/v2/vps/{id}/snapshots/{snapshotId}/actions/restore takes no body. Send the Content-Type header anyway. Without it the API answers 415.

POST /api/v2/vps/{id}/snapshots/{snapshotId}/actions/restore
curl -X POST \
"https://cloud.hostup.se/api/v2/vps/$VPS_ID/snapshots/$SNAPSHOT_ID/actions/restore" \
-H "Authorization: Bearer $HOSTUP_API_KEY" \
-H "Content-Type: application/json"
Response 202
{
"operation": {
"status": "queued",
"jobId": "job_01hxb7c8d9e0f1g2h3j4k5m6n7",
"pollUrl": "/api/jobs/job_01hxb7c8d9e0f1g2h3j4k5m6n7"
}
}
export JOB_ID="job_…" # operation.jobId above

The job stops a running VPS and puts the disk back to the snapshot. A VPS that was running is started again. An ISO image mounted on it is ejected first. A VPS that was stopped stays stopped.

Wait for the job as in step 3:

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

Then check what the server is doing with GET /api/v2/vps/{id}/status.

GET /api/v2/vps/{id}/status
curl "https://cloud.hostup.se/api/v2/vps/$VPS_ID/status" \
-H "Authorization: Bearer $HOSTUP_API_KEY"
Response 200
{
"id": "vps_01hxa3b4c5d6e7f8g9h0j1k2m3",
"powerState": "running",
"uptimeSeconds": 35,
"…": "…"
}

A small uptimeSeconds confirms the fresh start. Your own services may need a little longer to come up than the server itself.

On a Cloud VPS that is on hold for an overdue invoice, the job fails before it stops the VPS. Pay the invoice first. Then roll back again. With a name that does not exist nothing is rolled back and the job fails. A running VPS has been stopped by then.

The rollback did not start the server again. That happens after a wrong name. It also happens when the job completed but the rollback was refused or the start after it did not succeed. A VPS that was stopped before stays stopped too. Start it with Start a VPS when you are ready. Wait for that job the same way.

The plan and its options decide how many slots a VPS has. Look for the option snapshotSlots in List VPS plan change options. Change VPS resources sets the number. It needs a key with write:billing. Send the new total, not the number to add. With "dryRun": true the answer shows the price and changes nothing.

POST /api/v2/vps/{id}/actions/config
curl -X POST "https://cloud.hostup.se/api/v2/vps/$VPS_ID/actions/config" \
-H "Authorization: Bearer $HOSTUP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"resources": {"snapshotSlots": 3}, "dryRun": true}'
Response 200
{
"dryRun": true,
"status": "preview",
"changeKind": "configuration",
"paymentInvoice": {
"amount": 25,
"currencyCode": "SEK",
"…": "…"
},
"renewalInvoice": {
"amount": 199,
"currencyCode": "SEK"
},
"actions": {
"canCommit": { "allowed": true, "reason": null }
},
"…": "…"
}

paymentInvoice is what you pay now. It is null when nothing is due now. renewalInvoice is the price of the next renewal. Send the same body without dryRun to order the slots.

  • Slots that cost nothing apply at once.
  • Paid slots create an invoice. The answer then has status awaiting_payment and the invoice in paymentInvoice. The slots apply when it is paid. Until then the VPS keeps the slots it had.
  • A number lower than today’s removes slots at the next renewal.
  • On a Cloud VPS this call cannot change snapshot slots. It answers 400.

A VPS can also take snapshots by itself, every 4 to 24 hours. Send enabled and intervalHours. The body takes these two fields and no others. A value outside 4 to 24 is moved to the nearest end. Decimals are rounded down.

POST /api/v2/vps/{id}/snapshot-schedules
curl -X POST "https://cloud.hostup.se/api/v2/vps/$VPS_ID/snapshot-schedules" \
-H "Authorization: Bearer $HOSTUP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"enabled": true, "intervalHours": 12}'
Response 200
{
"schedule": {
"enabled": true,
"intervalHours": 12,
"lastRun": null,
"nextRun": "2026-11-02T22:00:00.000Z"
},
"…": "…"
}
  • In this answer available, limits and actions always read as if the VPS had no slots. Get the snapshot schedule of a VPS shows the real values. It needs only read:vm.
  • The first snapshot comes one interval after you save, within the hour after nextRun.
  • Scheduled snapshots are named auto_ and the date and time in Swedish time, for example auto_20261102_230000.
  • They share the slots with your own snapshots. When every slot is in use, a run is skipped. A run is also skipped while the VPS is not active. The next run tries again.
  • To pause them, send "enabled": false with an intervalHours. Delete the snapshot schedule of a VPS removes the schedule. It answers 404 snapshot_schedule_not_found when the VPS has none. Existing snapshots stay until they expire.
  • On a VPS without snapshot slots, turning the schedule on answers 409 product_not_orderable.

You can also turn the schedule on in the client area, on the VPS’s Snapshots tab.

SnapshotBackup
Kept forAbout a day. Hostup deletes it after that.Until you delete it. A schedule deletes its own oldest backups.
Stored onThe same storage as the VPS’s disk.Separate backup storage. It is still there if something happens to the VPS’s own storage.
Restores ontoOnly the same VPS.The same VPS or a new Cloud VPS.
SlotsThe VPS’s snapshot slots.The VPS’s backup slots, apart from the snapshot slots.

A backup takes longer to make and to restore. Take a snapshot right before a change you may want to undo. Keep backups for anything you need to get back later than tomorrow. Back up a VPS covers backups.

  • Hostup deletes every snapshot about a day after it was taken. That goes for the ones you take yourself and the scheduled ones. The cleanup runs every few hours. A snapshot can therefore stay a little past its expiresAt. Don’t count on it after that time.
  • Taking, rolling back and deleting a snapshot sends no e-mail and creates no invoice. Only more snapshot slots cost money.
  • 403 forbidden with requiredScopes: the key lacks the scope named there (When a key is refused).
  • 400 invalid_request: the name breaks the rules from step 2. errors[0].pointer is then /name. For a name in the path it is /snapshotId. The same code with the detail “The request is invalid.” means the VPS has no free slot. Check actions.canCreate as in step 1.
  • 404 vps_not_found: the ID is wrong or the VPS belongs to another account.

Next: Back up a VPS