Actions¶
Operations that run on a server, such as a reboot or a reinstall, take a while. Starting one answers 202 Accepted at once with an action, and you poll the action until it has finished.
curl -X POST "https://console.hosthatch.com/api/v1/projects/acme/servers/774736/actions/reboot/" \
-H "Authorization: Bearer $HOSTHATCH_API_KEY" \
-H "Idempotency-Key: 5f0c2e8a-6b1d-4c9e-9a57-3e2d1f0b7c44"
Endpoints¶
| Endpoint | What it does |
|---|---|
GET / |
List actions |
GET / |
Get an action |
GET / |
List a server's actions |
POST / |
Power on |
POST / |
Shut down |
POST / |
Power off |
POST / |
Reboot |
POST / |
Reinstall |
POST / |
Reset the root password |
POST / |
Enable IPv6 |
POST / |
Enable the private network |
POST / |
Take a snapshot |
POST / |
Restore a snapshot |
POST / |
Mount an ISO |
POST / |
Unmount an ISO |
How an action runs¶
- You start it with a
POSTto one of the endpoints above. The answer is202with the action, whosestatusisrunning. - You poll it with Get an action until its
statusissuccessorerror. Poll the action itself: lists show each action as it was last recorded, which can be a few minutes behind. - On
success,resultholds what the action produced, if anything. Onerror,errorsays why.
| Status | Meaning |
|---|---|
running |
Started, and not finished yet. |
unknown |
The operation was sent to the server's host, but its answer was lost, so the outcome isn't confirmed yet. Keep polling: see One exclusive action at a time for how it settles. |
success |
Done. |
error |
It failed, or never happened. error.code and error.message say which. |
Poll every few seconds at first, then less often: a reinstall can take several minutes. Every poll counts against the key's read budget. An action still running after 24 hours ends as error with the code timeout.
The action object¶
{
"id": 9101,
"type": "server.reboot",
"status": "success",
"resource": {"type": "server", "id": "774736"},
"started_at": "2026-10-19T10:00:00Z",
"finished_at": "2026-10-19T10:00:41Z",
"error": null,
"result": {}
}
| Field | Type | Description |
|---|---|---|
id |
integer | The action's id. |
type |
string | What it does, such as server.reboot. See Types. |
status |
string | running, unknown, success or error. |
resource |
object | What it runs on: {"type": "server", "id": "774736"}. |
started_at |
string | When it was started. |
finished_at |
string or null | When it finished, or null while it runs. |
error |
object or null | On error: code and message, see Error codes. Otherwise null. |
result |
object | What the action produced, see Results. Empty for most types. |
Types¶
| Type | Started by | Exclusive |
|---|---|---|
server.power_on |
Power on | |
server.shutdown |
Shut down | |
server.power_off |
Power off | |
server.reboot |
Reboot | |
server.reinstall |
Reinstall | |
server.reset_password |
Reset the root password | |
server.enable_ipv6 |
Enable IPv6 | |
server.enable_private_network |
Enable the private network | |
server.create_snapshot |
Take a snapshot | |
server.restore_snapshot |
Restore a snapshot | |
server.mount_iso |
Mount an ISO | |
server.unmount_iso |
Unmount an ISO |
Results¶
| Type | result |
|---|---|
server.enable_ipv6 |
{"subnet": "2001:db8:2:f342::/64"}, the subnet routed to the server, as soon as it is assigned. |
server.reset_password |
{"password": "..."}, the new root password, for 15 minutes. See Reset the root password. |
server.create_snapshot |
{"snapshot_id": 2960}, the new snapshot, when it can be told. |
| Every other type | {} |
Error codes¶
error.code |
Meaning |
|---|---|
failed |
The operation failed. Try again, and contact support if it keeps failing. |
outcome_unknown |
Its outcome could never be confirmed. Check the server, and contact support if it is not as you expect. |
task_result_unavailable |
Its result is no longer available. Check the server. |
timeout |
It didn't finish within 24 hours. |
resource_moved |
The server left the project before the result could be collected. |
One exclusive action at a time¶
Power, reboot, reinstall, snapshot and ISO actions are exclusive: a server runs one of them at a time. Starting a second while one runs answers 409 action_in_progress, with the running action's id in action_id. Poll that action, and start yours once it has finished.
{
"type": "https://docs.hosthatch.com/api/errors#action_in_progress",
"title": "Action in progress",
"status": 409,
"detail": "Another action is still running on this server.",
"code": "action_in_progress",
"instance": "/api/v1/projects/acme/servers/774736/actions/reinstall/",
"action_id": 9101
}
Resetting the password and enabling IPv6 or the private network can run next to anything.
Actions started in the console count too: an action from the console and one from the API never overlap on a server.
An exclusive action that is unknown keeps blocking its server until its outcome is settled, so that a second operation can never overlap one that may still be running. Power on, shut down and power off settle from the server's state. The others settle from the host's own record of the operation, or, if that can't tell, by support: support is told after 30 minutes, or 4 hours for a snapshot, and settles the action after checking the server. Contact support with the action's id if it holds up your work. A non-exclusive action that stays unknown for 30 minutes ends as error, with the code outcome_unknown.
Idempotency¶
Send an Idempotency-Key header with every request that starts an action. If you lose the answer and send the same request again with the same key, you get the original action back instead of starting a second one.
- The key is 1 to 64 characters. Use a new random value, such as a UUID, for each operation.
- Repeating the request with the same key and body answers
200with the original action, whatever state it has reached, and anIdempotent-Replayed: trueheader. That works even while the systems behind the API are down. - The same key with another method, path or body answers
409 idempotency_key_reused, so a key spent on a power-on can never start a reinstall. - Keys are per project, and stay bound to their action for as long as it exists.
Without a key, a repeated request starts a new action, or answers 409 action_in_progress while the first is still running.
A 502 upstream_error with an action_id means the action was recorded, but its outcome isn't confirmed yet. Poll that action rather than starting another.
Listing actions¶
List actions¶
GET /
The project's actions, newest first. Filter them with query parameters:
| Parameter | Description |
|---|---|
resource_type |
server. |
resource_id |
A server id. |
status |
running, unknown, success or error. |
type |
An action type, such as server.reinstall. |
curl "https://console.hosthatch.com/api/v1/projects/acme/actions/?status=running" \
-H "Authorization: Bearer $HOSTHATCH_API_KEY"
An unknown filter value answers 400 validation_error.
Get an action¶
GET /
One action, checked for its latest state before the answer. This is the endpoint to poll.
List a server's actions¶
GET /
The actions run on one server, newest first.
Power¶
The power endpoints take no request body.
Power on¶
POST /
Start the server.
Shut down¶
POST /
Ask the operating system to shut down, as pressing the power button would. The operating system may ignore it: power off if the server doesn't stop.
Power off¶
POST /
Cut the power at once, as pulling the plug would. Anything the server hasn't saved is lost.
Reboot¶
POST /
Restart the server. A server with pending_changes gets them applied by this reboot.
Operating system¶
Reinstall¶
POST /
Erase the server's disk and install an image on it.
Warning
Everything on the server's disk is lost, and can't be recovered.
curl -X POST "https://console.hosthatch.com/api/v1/projects/acme/servers/774736/actions/reinstall/" \
-H "Authorization: Bearer $HOSTHATCH_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 0d7a1f7e-1f52-4cf7-b7a4-0f8e5d6c3b21" \
-d '{"image": "ubuntu24", "ssh_keys": [3731]}'
| Field | Type | Description |
|---|---|---|
image |
string | Required. The image to install, by name, such as almalinux10. A server's image.name is one, and List images lists them all. |
ssh_keys |
array of integers | Ids of the project's SSH keys to install, up to 100. Leave it out to keep the server's keys; [] removes them. |
user_data |
string | Not available yet: sending it answers 400 feature_unavailable. Cloud-init user data, up to 65536 characters, starting with #cloud-config or #!. |
An image that doesn't exist or is disabled, and a key that isn't the project's, answer 400 validation_error on that field.
Reset the root password¶
POST /
Set a new random root password on the running server, through its guest agent. The request takes no body.
Once the action has succeeded, reading the action shows the new password in result.password:
{
"id": 9102,
"type": "server.reset_password",
"status": "success",
"resource": {"type": "server", "id": "774736"},
"started_at": "2026-10-19T10:05:00Z",
"finished_at": "2026-10-19T10:05:09Z",
"error": null,
"result": {"password": "kN4v-..."}
}
The password is shown:
- for 15 minutes after the action succeeded, and then erased;
- only when you read this one action, never in lists or in the answer to a repeated request;
- never to a read-only key;
- only while it is still the server's root password, and the server is still in the project.
Network¶
Enable IPv6¶
POST /
Route an IPv6 /64 subnet to the server. The request takes no body. result.subnet holds the subnet as soon as it is assigned, as the server's network.ipv6 lists it, and the action succeeds once the network is set up for it. Then configure an address from the subnet inside the server: see IPv6 configuration.
A server that already has IPv6 answers 409 already_enabled, and 409 insufficient_capacity means no subnet is free for its location right now.
Enable the private network¶
POST /
Attach the server to the private network shared by your servers in its location. The request takes no body. The action succeeds at once, but the server gets its private interface at its next start or reboot through this API. Then configure the interface's address inside the server: see Private networking.
A server that can't join answers 409 not_eligible, and one that already has it 409 already_enabled.
Errors¶
Every endpoint that starts an action can answer these:
| Status | Code | When |
|---|---|---|
| 400 | validation_error |
A field, or the Idempotency-Key, is invalid. |
| 400 | feature_unavailable |
user_data on a reinstall. |
| 403 | read_only_key |
Starting an action with a read-only key. |
| 403 | billing_account_required |
The project's owner has no billing account. |
| 403 | project_not_ready |
The project is still being set up. |
| 404 | not_found |
No such server or action in the project. |
| 409 | action_in_progress |
Another exclusive action is running on the server. |
| 409 | conflict |
The server's host refused the operation in the server's current state. |
| 409 | idempotency_key_reused |
The key was used for another request. |
| 409 | already_enabled, not_eligible, insufficient_capacity |
See Network. |
| 502 | upstream_error |
The host failed, or its answer was lost: with an action_id, poll that action. |
| 503 | node_maintenance |
The server's host is in maintenance. Retry-After is 300 seconds. |
A start that the server's host refuses is still recorded: the action list shows it as error, with the code failed.