HelipodDocs

Migrate

Move a server from another provider onto a VPS with one command, or copy a restore point onto another of your VPS. Free.

Migrate brings files onto one of your VPS from somewhere else:

  • Import ("Migrate to Helipod"): a Linux server outside Helipod runs a one-time command that uploads it, encrypted. Helipod checks the upload (operating system, size, file count), then you restore folders or the whole server onto the VPS.
  • Copy: a restore point of one of your VPS (see Backups) is restored onto another of your VPS.

Migrations are free. An import is kept for 7 days, so it can be restored more than once, then it is deleted. The same features are in the dashboard's Migrate tab.

The target VPS must be running, a Linux server, and in your key's scope; for a copy, the source VPS must be too. One import per VPS at a time, at most 3 imports per account, and an upload may not be larger than the target's disk.

How an import works

  1. Start an import on the target VPS. The answer has a one-time command: curl -fsSL https://api.helipod.io/migrate/… | sudo bash.
  2. Run it as root on the old server. It works once and expires in 1 hour. With sourceIp it only works from that IP. It uploads in the background, so the terminal can be closed.
  3. Follow the status: waiting → uploading → inspecting → uploaded. While it uploads you can pause, resume or stop it.
  4. Check fetchedIp (who ran the command), then restore it with confirmSource: true the first time.

The command is shown once, in the answer that creates it. Lost or expired: get a new command.

List migrations

GET/vps/:id/migrations
Migrations onto this VPS (imports and copies), newest first.
curl https://api.helipod.io/api/vps/1f757fd5-98d1-490a-81a0-58b5798a5815/migrations \
  -H "Authorization: Bearer hel_your_key"
{
  "success": true,
  "data": [
    {
      "id": "2746f3c3-ae6f-4944-9d46-a8fb5e34221a",
      "kind": "import",
      "status": "uploaded",
      "targetVpsId": "1f757fd5-98d1-490a-81a0-58b5798a5815",
      "sourcePointId": null,
      "sourceOs": "ubuntu-24.04",
      "sourceBytes": 3308296246,
      "mode": null,
      "paths": null,
      "restoreTarget": null,
      "commandExpiresAt": null,
      "sourceIp": "203.0.113.10",
      "fetchedIp": "203.0.113.10",
      "fetchedAt": "2026-10-21T00:03:31.000Z",
      "fetchedAgent": "curl/8.5.0",
      "sourceConfirmedAt": null,
      "result": { "hostname": "old-server", "files": 25238 },
      "error": null,
      "expiresAt": "2026-10-28T00:02:11.000Z",
      "createdAt": "2026-10-21T00:02:11.000Z",
      "updatedAt": "2026-10-21T00:09:02.000Z"
    }
  ]
}
FieldDescription
kindimport (from a server outside Helipod) or copy (from another VPS's restore point).
statuswaiting (command not run yet), uploading, inspecting (checking the upload), uploaded (ready to restore), awaiting_command, restoring, restored, failed, expired, cancelled.
sourceOsThe uploaded server's OS, e.g. ubuntu-24.04, debian-12 (import), or the source VPS's (copy).
sourceBytesSize of the upload (import).
mode, paths, restoreTargetThe last restore asked for: files or full, the paths, folder or inplace.
commandExpiresAtSet while a one-time command can still be fetched.
sourceIpThe IP the upload command is locked to, if you gave one.
fetchedIp, fetchedAt, fetchedAgentWho ran the command, as Helipod saw it. Check it before restoring.
sourceConfirmedAtWhen the upload was confirmed as yours (needed once before restoring an import).
resulthostname and files of the uploaded server; after a restore target (where the files went), restored, bytes, restoredAt; while a job runs progress, stored and control (below); refusedFetch when someone tried the command from another IP.
errorWhy it failed, in plain words.
expiresAtWhen the import is deleted.

While an upload or a command-run restore runs, result also has:

FieldDescription
progress.percentPercentage of the data read (an upload then finishes sending the last of it).
progress.bytesDone, progress.bytesTotalBytes read (upload) or restored, out of the total.
progress.sentBytes, progress.sendRateBytes the server sent since the upload began, and its current speed (bytes per second).
progress.secondsLeftEstimate, when there is a sensible one.
stored.bytesWhat has arrived in Helipod storage so far (checked every 2 minutes).
control.want, control.state, control.atPause state: what is wanted (run, pause, stop), what the job last reported (running, paused, stopping) and when it last checked in (every ~5 s).

Get a migration

GET/vps/:id/migrations/:migrationId
One migration onto this VPS, the same fields as in the list.

Poll it every 15 to 30 seconds while it runs.

Start an import

POST/vps/:id/migrations/import
Start moving a server from outside Helipod onto this VPS. Returns the one-time command.
curl -X POST https://api.helipod.io/api/vps/1f757fd5-98d1-490a-81a0-58b5798a5815/migrations/import \
  -H "Authorization: Bearer hel_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "sourceIp": "203.0.113.10" }'
Body fieldDescription
sourceIpOptional, recommended. The old server's public IP (on that server: curl -4 ifconfig.me). The command then only works from there; a fetch from anywhere else is refused, does not use it up, and shows as result.refusedFetch.
{
  "success": true,
  "data": {
    "migration": { "id": "2746f3c3-ae6f-4944-9d46-a8fb5e34221a", "kind": "import", "status": "waiting", "commandExpiresAt": "2026-10-21T01:02:11.000Z", "sourceIp": "203.0.113.10" },
    "command": "curl -fsSL https://api.helipod.io/migrate/Xr3…kQ | sudo bash"
  }
}

Run command as root on the old server. It needs bash and curl (it installs curl and bzip2 itself when missing), uploads the main disk (/) except system, temporary and cache folders, and dumps running MySQL, MariaDB and PostgreSQL databases into /var/backups/helipod-db first. Nothing stays installed on the server afterwards.

New command

POST/vps/:id/migrations/:migrationId/command
A fresh one-time command, for an import that has not finished uploading or a restore that waits for its command.

The previous command stops working, and a job still running with it is told to stop. The answer has the same shape as Start an import.

Pause, resume or stop

POST/vps/:id/migrations/:migrationId/control
Pause, resume or stop an upload or a restore that runs from a one-time command.
curl -X POST https://api.helipod.io/api/vps/1f757fd5-98d1-490a-81a0-58b5798a5815/migrations/2746f3c3-ae6f-4944-9d46-a8fb5e34221a/control \
  -H "Authorization: Bearer hel_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "action": "pause" }'

action is pause, resume or stop. The job follows within a few seconds; result.control.state shows when it has. Stop is final: the migration becomes failed, and a new command starts the upload again, reusing what was already uploaded where possible. On the server itself the same works with sudo helipod-migrate pause | resume | stop (and helipod-restore for a restore), and both stay in sync.

Restore an import

POST/vps/:id/migrations/:migrationId/restore
Restore an uploaded import onto its VPS.
curl -X POST https://api.helipod.io/api/vps/1f757fd5-98d1-490a-81a0-58b5798a5815/migrations/2746f3c3-ae6f-4944-9d46-a8fb5e34221a/restore \
  -H "Authorization: Bearer hel_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "mode": "files", "paths": ["/var/www", "/home", "/var/backups/helipod-db"], "target": "folder", "confirmSource": true }'
Body fieldDescription
modefiles: the given paths. full: the whole VPS becomes a copy of the old server (everything on it is replaced).
pathsfiles only: 1 to 50 absolute paths.
targetfiles only: folder (default) restores into /root/restore-<date>; inplace overwrites files at the same paths.
confirmSourcetrue confirms the upload came from your server. Needed for the first restore of an import: check fetchedIp first, and delete the import instead if you do not recognise it.

full needs a VPS of the full-VM kind (providerCode 2) running the same OS and version as the upload (sourceOs); otherwise restore folders. The restore runs in the background; follow it with Get a migration. An import can be restored again until it expires.

Copy to another VPS

POST/vps/:id/backup/points/:pointId/copy
Copy a restore point of this VPS onto another VPS of the account.
curl -X POST https://api.helipod.io/api/vps/1f757fd5-98d1-490a-81a0-58b5798a5815/backup/points/5d0e…/copy \
  -H "Authorization: Bearer hel_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "targetVpsId": "9a8b7c6d-…", "mode": "files", "paths": ["/var/www"], "target": "folder" }'
Body fieldDescription
targetVpsIdThe VPS to copy onto. Not the same VPS: to bring a VPS back, use restore.
mode, paths, targetAs in Restore an import. full only between two VPS of the same kind running the same OS and version.

The copy is listed on the target VPS (List migrations with the target's id). If the answer has a command, the target's management agent could not be reached: run that one-time command as root on the target instead.

Delete a migration

DELETE/vps/:id/migrations/:migrationId
Delete a migration. An import's uploaded files are deleted and cannot be restored any more.

A running upload is told to stop. Not possible while a restore or the upload check runs (BUSY).

Errors

Migrate answers like the rest of the VPS API (Errors): a VPS or migration that does not exist or is outside your key's scope is 404; a business rule is 400 with one of these codes:

CodeMeaning
IMPORT_EXISTSThis VPS already has an import: open it or delete it first.
TOO_MANY_IMPORTSThe account already has 3 imports.
BAD_IPsourceIp is not an IP address.
VPS_NOT_RUNNING, VPS_EXPIRED, NOT_ELIGIBLEThe target VPS cannot take a migration right now.
NOT_READYThe upload has not finished yet.
CONFIRM_SOURCESend confirmSource: true after checking fetchedIp.
FULL_NOT_ALLOWED, OS_MISMATCHA whole-server restore needs the same kind of VPS and the same OS; restore folders instead.
SAME_VPSA copy needs another VPS.
NO_COMMANDThere is no command to run for this migration right now.
NOT_CONTROLLABLE, STOPPINGOnly a job started by a command, once it checked in, can be paused or stopped; or it is already stopping.
VPS_BUSY, BUSYAnother restore onto the VPS is waiting or running, or a step is in progress.

On this page