A reinstall is a single authenticated call, which makes it a natural fit for a pipeline: rebuild a server on a schedule, on a tag, or on a button in your CI. This walks through a GitHub Actions job that reinstalls one bare-metal server, applies a saved disk layout and post-install script, and waits for it to come up. Read Getting started with the Amóni API first if you have not made a key yet.
Before the pipeline: the allow-list
Every API key is locked to the source addresses you give it, and this is the one part of CI that needs thought. A GitHub-hosted runner has no fixed egress address, so it cannot sit on an allow-list. You have two honest options: run the job on a self-hosted runner whose egress IP you know, or send the calls through a proxy or bastion with a static address. Put that one address on the key. Then the key is both narrowly scoped and pinned to a machine you control.
The key itself needs read and servers.write, nothing more. Store it as a repository or environment secret named AMONI_API_KEY; never commit it.
Find the ids once
A reinstall is addressed by numbers: the server id, an OS profile id, and optionally a saved disk layout and post-install script. Look them up once and keep them in your workflow. The server id is in its portal URL and in GET /v1/servers. The rest come from the server's profiles:
curl -s https://api.amoni.app/v1/servers/955/profiles \
-H "Authorization: Bearer $AMONI_API_KEY"
{
"success": true,
"data": {
"profiles": [ { "id": 12, "name": "Ubuntu 24.04", "os_slug": "ubuntu-24.04" } ],
"disk_layouts": [ { "id": 1, "name": "web-raid1-lvm", "scope": "client" } ],
"scripts": [ { "id": 1, "name": "harden-ssh", "scope": "client" } ]
}
}
Saved disk layouts and scripts are your own, created once on the Install Profiles page. A custom layout or script has to be active before a reinstall can use it, so create and let it activate before you wire it into CI.
The workflow
This job reinstalls the server, then polls until the install reports complete. It runs on a self-hosted runner so its egress is on the key's allow-list.
name: Reinstall server
on:
workflow_dispatch: # a button in the Actions tab
env:
AMONI: https://api.amoni.app/v1
SERVER_ID: "955"
jobs:
reinstall:
runs-on: self-hosted # fixed egress, on the key's allow-list
steps:
- name: Start the reinstall
env:
KEY: ${{ secrets.AMONI_API_KEY }}
run: |
curl -sf -X POST "$AMONI/servers/$SERVER_ID/reinstall" \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{
"profile_id": 12,
"hostname": "web1.example.com",
"disk_layout_id": 1,
"script_ids": [1]
}' | tee reinstall.json
jq -e '.success' reinstall.json
- name: Wait for it to come up
env:
KEY: ${{ secrets.AMONI_API_KEY }}
run: |
for i in $(seq 1 60); do
sleep 30
body=$(curl -sf "$AMONI/servers/$SERVER_ID/provision-status" \
-H "Authorization: Bearer $KEY")
echo "$body" | jq -r '.data | "\(.status) \(.step // "")"'
if [ "$(echo "$body" | jq -r '.data.complete')" = "true" ]; then
echo "Install complete."; exit 0
fi
done
echo "Timed out waiting for the install."; exit 1
The reinstall call answers at once:
{ "success": true, "data": {
"message": "Reinstall started. This can take several minutes.",
"notice": null,
"applied": { "os": "Ubuntu 24.04" }
} }
It is not finished when this returns; it has started. Progress is on GET /v1/servers/{id}/provision-status, which reports complete, a status and a step. There is no percentage while it runs, so poll on complete, not on a number.
Getting the root password
If you did not send a password, the installer sets an auto-generated one. Read it once the install is done with GET /v1/servers/{id}/root-password. It answers 404 with code root_password_unavailable when there is nothing to hand back, so treat that as “use your SSH key” rather than an error. Better still, pass ssh_key_ids in the reinstall so the machine comes up with your key already on it and no password to fetch. Manage those keys with the API or in the portal.
Let a webhook tell you instead
Polling is fine for one server in one job. Across a fleet, subscribe a webhook and let the operation.succeeded event for server.reinstall tell you when each one lands, rather than every job holding a runner open on a sleep loop.
Questions we get
- Why will my GitHub-hosted runner not work? Its egress address is not fixed, so it cannot be on the key's allow-list. Use a self-hosted runner or a static-IP proxy.
- Do I have to give a disk layout and script? No. With only
profile_idthe reinstall uses the image's default partitioning and no post-install step. - How many scripts can I run? Up to five per reinstall, in the order you list them. Each runs once, as root, after the OS is installed.
- Can I reinstall a cloud instance this way? That is a rebuild, not a reinstall. Cloud instances use
POST /v1/vms/{account}/{id}/rebuildand cloud-init. - Something was refused mid-install. The response
noticesays so in plain terms, for example that your key could not be applied. Read it, do not assume the request went on exactly as sent.
Still stuck?
Open a support ticket with the server id and the reinstall.json your job saved. We can see the operation and what the installer did with it.