# HostHatch Docs > Guides for HostHatch servers and the HostHatch API. The HostHatch API manages HostHatch projects, servers and their resources. Base URL: https://console.hosthatch.com/api/v1. Authenticate with `Authorization: Bearer `. Requests and responses are JSON; errors are RFC 9457 problem details with a stable `code`. OpenAPI document: https://docs.hosthatch.com/api/openapi.json # API guides # HostHatch API The HostHatch API lets your code, scripts and AI agents do what you do in the console: order servers and pay for them with account credit, power and reinstall them, take snapshots, set up firewalls, mount ISOs, and manage SSH keys and reverse DNS. You create projects and manage their members in the console. ``` curl https://console.hosthatch.com/api/v1/me/ \ -H "Authorization: Bearer $HOSTHATCH_API_KEY" ``` New here? The [quickstart](https://docs.hosthatch.com/api/quickstart/index.md) goes from an API key to a rebooted server in a few calls. Using the Peregrine API at `cloud.hosthatch.com`? Its documentation is on the [Legacy API](https://docs.hosthatch.com/legacy-api/index.md) page. ## Base URL ``` https://console.hosthatch.com/api/v1 ``` Every path in these docs is relative to it. Paths end with a slash: `/me/`, not `/me`. ## Conventions **Authentication.** Send an API key as `Authorization: Bearer `. See [Authentication](https://docs.hosthatch.com/api/authentication/index.md). **JSON only.** Send request bodies as JSON with `Content-Type: application/json`. Responses are `application/json`, and errors are `application/problem+json`. Text is UTF-8. **Projects.** Everything you manage belongs to a project, and paths address the project by its slug: `/projects/acme/servers/`. The API lists and reads your projects; you create and manage them in the console. See [Projects and roles](https://docs.hosthatch.com/api/projects-and-roles/index.md). **Identifiers.** Server ids are lowercase hexadecimal strings, such as `"774736"`. Every other id is an integer. **Names and units.** Fields are `snake_case` and paths are `kebab-case`. Sizes are integers with the unit in the name: `ram_mb`, `disk_gb`, `used_gb`. Money is a decimal string with two places, such as `"12.00"`, next to a `currency` field in ISO 4217. Timestamps are ISO 8601 in UTC, with a `Z`: `2026-10-19T10:00:00Z`. **Responses to changes.** A change to a property (`PATCH` or `PUT`) answers `200` with the updated resource. An operation on a server, such as a reboot, answers `202` with an [action](https://docs.hosthatch.com/api/actions/index.md) to poll. Anything that bills you answers `201` with an [order](https://docs.hosthatch.com/api/orders-and-payment/index.md). `DELETE` answers `204` with no body. **Retries.** Send an `Idempotency-Key` header with every `POST` that starts an action or creates an order, so that a request you repeat after a lost answer can't happen twice. See [Idempotency](https://docs.hosthatch.com/api/actions/#idempotency). **Lists.** Lists are paginated. Use `?page=` and `?page_size=`: 50 per page by default, at most 100. `next` and `previous` link to the neighbouring pages, and are `null` at either end. A page past the end answers `404`. ``` { "count": 120, "next": "https://console.hosthatch.com/api/v1/projects/acme/actions/?page=2", "previous": null, "results": [] } ``` **Errors.** Errors are problem details documents with a stable `code`. See [Errors](https://docs.hosthatch.com/api/errors/index.md). ``` { "type": "https://docs.hosthatch.com/api/errors#not_found", "title": "Not found", "status": 404, "detail": "The resource does not exist, or you do not have access to it.", "code": "not_found", "instance": "/api/v1/projects/acme/servers/abc123/" } ``` **Rate limits.** Each key has a budget per minute for reads, writes and orders. See [Rate limits](https://docs.hosthatch.com/api/rate-limits/index.md). ## Versioning The version is in the path: `/api/v1`. Within v1, changes are additive: new endpoints, new optional request fields, new response fields, and new values in enumerations, such as a new action type or error code. Write clients that ignore fields and values they don't know. A breaking change, such as removing or renaming a field, comes as a new version at `/api/v2/`. Every change is listed in the [changelog](https://docs.hosthatch.com/api/changelog/index.md). ## For AI agents These docs are written for people and for agents alike. Besides the pages: - [`/llms.txt`](https://docs.hosthatch.com/llms.txt) lists every page, and [`/llms-full.txt`](https://docs.hosthatch.com/llms-full.txt) has them all in one Markdown file. - Each page that `/llms.txt` lists is also served as Markdown: add `index.md` to its address, for example [`/api/servers/index.md`](https://docs.hosthatch.com/api/servers/index.md). - The OpenAPI document is at [`/api/openapi.json`](https://docs.hosthatch.com/api/openapi.json), and the [reference](https://docs.hosthatch.com/api/reference/index.md) renders it. An agent that changes things should send an `Idempotency-Key` with every action and order, poll actions instead of assuming they finished, act on the error `code` rather than on `detail`, and wait as long as `Retry-After` says. ### Get the OpenAPI document GET `/openapi.json` No key needed · Read-only keys: yes · Returns `200 OpenAPI document` The API serves its own OpenAPI document, without a key. ## Every endpoint ### Account | Endpoint | What it does | | ---------- | ------------------------------------------------------------------------------------------- | | GET `/me/` | [Get the current user](https://docs.hosthatch.com/api/authentication/#get-the-current-user) | ### Projects | Endpoint | What it does | | ----------------------- | --------------------------------------------------------------------------------- | | GET `/projects/` | [List projects](https://docs.hosthatch.com/api/projects-and-roles/#list-projects) | | GET `/projects/{slug}/` | [Get a project](https://docs.hosthatch.com/api/projects-and-roles/#get-a-project) | ### Catalogue | Endpoint | What it does | | --------------------------------- | ----------------------------------------------------------------------------------- | | GET `/projects/{slug}/products/` | [List products](https://docs.hosthatch.com/api/orders-and-payment/#list-products) | | GET `/projects/{slug}/images/` | [List images](https://docs.hosthatch.com/api/orders-and-payment/#list-images) | | GET `/projects/{slug}/locations/` | [List locations](https://docs.hosthatch.com/api/orders-and-payment/#list-locations) | | GET `/projects/{slug}/pricing/` | [Get pricing](https://docs.hosthatch.com/api/orders-and-payment/#get-pricing) | ### Servers | Endpoint | What it does | | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | | GET `/projects/{slug}/servers/` | [List servers](https://docs.hosthatch.com/api/servers/#list-servers) | | POST `/projects/{slug}/servers/` | [Order a server](https://docs.hosthatch.com/api/orders-and-payment/#order-a-server) | | GET `/projects/{slug}/servers/{id}/` | [Get a server](https://docs.hosthatch.com/api/servers/#get-a-server) | | PATCH `/projects/{slug}/servers/{id}/` | [Rename a server](https://docs.hosthatch.com/api/servers/#rename-a-server) | | GET `/projects/{slug}/servers/{id}/reverse-dns/` | [List reverse DNS names](https://docs.hosthatch.com/api/servers/#list-reverse-dns-names) | | PUT `/projects/{slug}/servers/{id}/reverse-dns/{address}/` | [Set a reverse DNS name](https://docs.hosthatch.com/api/servers/#set-a-reverse-dns-name) | | DELETE `/projects/{slug}/servers/{id}/reverse-dns/{address}/` | [Clear a reverse DNS name](https://docs.hosthatch.com/api/servers/#clear-a-reverse-dns-name) | | GET `/projects/{slug}/servers/{id}/upgrade-options/` | [List upgrade options](https://docs.hosthatch.com/api/orders-and-payment/#list-upgrade-options) | | POST `/projects/{slug}/servers/{id}/upgrade/` | [Upgrade a server](https://docs.hosthatch.com/api/orders-and-payment/#upgrade-a-server) | | POST `/projects/{slug}/servers/{id}/bandwidth/` | [Buy extra bandwidth](https://docs.hosthatch.com/api/orders-and-payment/#buy-extra-bandwidth) | | POST `/projects/{slug}/servers/{id}/cancel/` | [Cancel a server](https://docs.hosthatch.com/api/servers/#cancel-a-server) | ### Actions | Endpoint | What it does | | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | | GET `/projects/{slug}/actions/` | [List actions](https://docs.hosthatch.com/api/actions/#list-actions) | | GET `/projects/{slug}/actions/{id}/` | [Get an action](https://docs.hosthatch.com/api/actions/#get-an-action) | | GET `/projects/{slug}/servers/{id}/actions/` | [List a server's actions](https://docs.hosthatch.com/api/actions/#list-a-servers-actions) | | POST `/projects/{slug}/servers/{id}/actions/power-on/` | [Power on](https://docs.hosthatch.com/api/actions/#power-on) | | POST `/projects/{slug}/servers/{id}/actions/shutdown/` | [Shut down](https://docs.hosthatch.com/api/actions/#shut-down) | | POST `/projects/{slug}/servers/{id}/actions/power-off/` | [Power off](https://docs.hosthatch.com/api/actions/#power-off) | | POST `/projects/{slug}/servers/{id}/actions/reboot/` | [Reboot](https://docs.hosthatch.com/api/actions/#reboot) | | POST `/projects/{slug}/servers/{id}/actions/reinstall/` | [Reinstall](https://docs.hosthatch.com/api/actions/#reinstall) | | POST `/projects/{slug}/servers/{id}/actions/reset-password/` | [Reset the root password](https://docs.hosthatch.com/api/actions/#reset-the-root-password) | | POST `/projects/{slug}/servers/{id}/actions/enable-ipv6/` | [Enable IPv6](https://docs.hosthatch.com/api/actions/#enable-ipv6) | | POST `/projects/{slug}/servers/{id}/actions/enable-private-network/` | [Enable the private network](https://docs.hosthatch.com/api/actions/#enable-the-private-network) | | POST `/projects/{slug}/servers/{id}/actions/create-snapshot/` | [Take a snapshot](https://docs.hosthatch.com/api/snapshots/#take-a-snapshot) | | POST `/projects/{slug}/servers/{id}/actions/restore-snapshot/` | [Restore a snapshot](https://docs.hosthatch.com/api/snapshots/#restore-a-snapshot) | | POST `/projects/{slug}/servers/{id}/actions/mount-iso/` | [Mount an ISO](https://docs.hosthatch.com/api/isos/#mount-an-iso) | | POST `/projects/{slug}/servers/{id}/actions/unmount-iso/` | [Unmount an ISO](https://docs.hosthatch.com/api/isos/#unmount-an-iso) | ### Orders | Endpoint | What it does | | ----------------------------------- | ------------------------------------------------------------------------------- | | GET `/projects/{slug}/orders/` | [List orders](https://docs.hosthatch.com/api/orders-and-payment/#list-orders) | | GET `/projects/{slug}/orders/{id}/` | [Get an order](https://docs.hosthatch.com/api/orders-and-payment/#get-an-order) | ### SSH keys | Endpoint | What it does | | ---------------------------------------- | ------------------------------------------------------------------------------- | | GET `/projects/{slug}/ssh-keys/` | [List SSH keys](https://docs.hosthatch.com/api/ssh-keys/#list-ssh-keys) | | POST `/projects/{slug}/ssh-keys/` | [Add an SSH key](https://docs.hosthatch.com/api/ssh-keys/#add-an-ssh-key) | | GET `/projects/{slug}/ssh-keys/{id}/` | [Get an SSH key](https://docs.hosthatch.com/api/ssh-keys/#get-an-ssh-key) | | PATCH `/projects/{slug}/ssh-keys/{id}/` | [Rename an SSH key](https://docs.hosthatch.com/api/ssh-keys/#rename-an-ssh-key) | | DELETE `/projects/{slug}/ssh-keys/{id}/` | [Delete an SSH key](https://docs.hosthatch.com/api/ssh-keys/#delete-an-ssh-key) | ### Snapshots | Endpoint | What it does | | ----------------------------------------- | -------------------------------------------------------------------------------- | | GET `/projects/{slug}/snapshots/` | [List snapshots](https://docs.hosthatch.com/api/snapshots/#list-snapshots) | | GET `/projects/{slug}/snapshots/{id}/` | [Get a snapshot](https://docs.hosthatch.com/api/snapshots/#get-a-snapshot) | | PATCH `/projects/{slug}/snapshots/{id}/` | [Rename a snapshot](https://docs.hosthatch.com/api/snapshots/#rename-a-snapshot) | | DELETE `/projects/{slug}/snapshots/{id}/` | [Delete a snapshot](https://docs.hosthatch.com/api/snapshots/#delete-a-snapshot) | ### Firewalls | Endpoint | What it does | | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | | GET `/projects/{slug}/firewall-groups/` | [List firewall groups](https://docs.hosthatch.com/api/firewalls/#list-firewall-groups) | | POST `/projects/{slug}/firewall-groups/` | [Create a firewall group](https://docs.hosthatch.com/api/firewalls/#create-a-firewall-group) | | GET `/projects/{slug}/firewall-groups/{id}/` | [Get a firewall group](https://docs.hosthatch.com/api/firewalls/#get-a-firewall-group) | | PATCH `/projects/{slug}/firewall-groups/{id}/` | [Rename a firewall group](https://docs.hosthatch.com/api/firewalls/#rename-a-firewall-group) | | DELETE `/projects/{slug}/firewall-groups/{id}/` | [Delete a firewall group](https://docs.hosthatch.com/api/firewalls/#delete-a-firewall-group) | | GET `/projects/{slug}/firewall-groups/{id}/rules/` | [List a group's rules](https://docs.hosthatch.com/api/firewalls/#list-a-groups-rules) | | POST `/projects/{slug}/firewall-groups/{id}/rules/` | [Add a rule](https://docs.hosthatch.com/api/firewalls/#add-a-rule) | | GET `/projects/{slug}/firewall-groups/{id}/rules/{rule_id}/` | [Get a rule](https://docs.hosthatch.com/api/firewalls/#get-a-rule) | | PATCH `/projects/{slug}/firewall-groups/{id}/rules/{rule_id}/` | [Change a rule](https://docs.hosthatch.com/api/firewalls/#change-a-rule) | | DELETE `/projects/{slug}/firewall-groups/{id}/rules/{rule_id}/` | [Delete a rule](https://docs.hosthatch.com/api/firewalls/#delete-a-rule) | | PUT `/projects/{slug}/servers/{id}/firewall-groups/` | [Set a server's firewall groups](https://docs.hosthatch.com/api/firewalls/#set-a-servers-firewall-groups) | ### ISOs | Endpoint | What it does | | ------------------------------------ | ------------------------------------------------------------------- | | GET `/projects/{slug}/isos/` | [List ISOs](https://docs.hosthatch.com/api/isos/#list-isos) | | POST `/projects/{slug}/isos/` | [Upload an ISO](https://docs.hosthatch.com/api/isos/#upload-an-iso) | | GET `/projects/{slug}/isos/{id}/` | [Get an ISO](https://docs.hosthatch.com/api/isos/#get-an-iso) | | DELETE `/projects/{slug}/isos/{id}/` | [Delete an ISO](https://docs.hosthatch.com/api/isos/#delete-an-iso) | ### OpenAPI document | Endpoint | What it does | | ------------------- | ------------------------------------------------------------------------------------ | | GET `/openapi.json` | [Get the OpenAPI document](https://docs.hosthatch.com/api/#get-the-openapi-document) | # Quickstart This page takes you from an API key to a server you have ordered and rebooted, a `curl` command or two at a time. ## 1. Create an API key On your [profile page](https://console.hosthatch.com/users/profile/), under **API Keys**, give the key a name, choose when it expires, and select **Create key**. The console may ask for your password again first. Copy the key: it is shown only once. Keep it in an environment variable rather than in your code: ``` export HOSTHATCH_API_KEY="paste-your-key-here" export API="https://console.hosthatch.com/api/v1" ``` ## 2. Check the key ``` curl "$API/me/" -H "Authorization: Bearer $HOSTHATCH_API_KEY" ``` ``` { "id": 1, "email": "jane@example.com", "first_name": "Jane", "last_name": "Doe", "billing_account": {"exists": true, "credit_balance": "40.00", "currency": "USD"}, "projects": [{"slug": "acme", "name": "Acme", "role": "owner"}] } ``` A `401` means the key is wrong, revoked or expired: see [Authentication](https://docs.hosthatch.com/api/authentication/#errors). ## 3. Choose a project Everything you manage belongs to a project. Use the `slug` of one from `projects`, and list its servers: ``` export PROJECT="acme" curl "$API/projects/$PROJECT/servers/" -H "Authorization: Bearer $HOSTHATCH_API_KEY" ``` ``` { "count": 1, "next": null, "previous": null, "results": [ { "id": "774736", "hostname": "web-01", "status": "running", "ipv4_primary": "203.0.113.10", "location": {"code": "AMS", "city": "Amsterdam", "country_code": "NL"}, "plan": {"name": "Cloud VPS 2x4"} } ] } ``` ## 4. Order a server Find what you can order: [products](https://docs.hosthatch.com/api/orders-and-payment/#list-products), [images](https://docs.hosthatch.com/api/orders-and-payment/#list-images) and [locations](https://docs.hosthatch.com/api/orders-and-payment/#list-locations). ``` curl "$API/projects/$PROJECT/products/" -H "Authorization: Bearer $HOSTHATCH_API_KEY" ``` Then order one. The `Idempotency-Key` makes the order safe to send again if you lose the answer: the same key returns the same order instead of placing a second one. ``` curl -X POST "$API/projects/$PROJECT/servers/" \ -H "Authorization: Bearer $HOSTHATCH_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: order-web-02" \ -d '{"hostname": "web-02", "product_id": 123, "location": "AMS", "image": "almalinux10", "billing_cycle": "monthly", "ssh_keys": [3731]}' ``` The answer is an [order](https://docs.hosthatch.com/api/orders-and-payment/#the-order-object). The project owner's account credit pays for it automatically, whoever places the order. Without enough credit it waits in `awaiting_payment`, with an invoice to pay in the console. Poll the order until it is `completed`; its `server_id` is your new server: ``` curl "$API/projects/$PROJECT/orders/55/" -H "Authorization: Bearer $HOSTHATCH_API_KEY" ``` ## 5. Look at the server ``` export SERVER="774736" curl "$API/projects/$PROJECT/servers/$SERVER/" -H "Authorization: Bearer $HOSTHATCH_API_KEY" ``` The answer is the full [server](https://docs.hosthatch.com/api/servers/#the-server-object): its status, addresses, plan, bandwidth and billing. ## 6. Reboot it Operations on a server are [actions](https://docs.hosthatch.com/api/actions/index.md). Starting one answers `202` at once, with the action to poll: ``` curl -X POST "$API/projects/$PROJECT/servers/$SERVER/actions/reboot/" \ -H "Authorization: Bearer $HOSTHATCH_API_KEY" \ -H "Idempotency-Key: reboot-web-01-0001" ``` ``` { "id": 9101, "type": "server.reboot", "status": "running", "resource": {"type": "server", "id": "774736"}, "started_at": "2026-10-19T10:00:00Z", "finished_at": null, "error": null, "result": {} } ``` Poll it until `status` is `success` or `error`: ``` curl "$API/projects/$PROJECT/actions/9101/" -H "Authorization: Bearer $HOSTHATCH_API_KEY" ``` ## 7. Cancel it When you are done with a server, cancel it. `end_of_term` keeps it until the end of the period you paid for and needs a `reason`; `immediate` deletes it, and everything on it, now. Cancelling needs the admin or owner role. ``` curl -X POST "$API/projects/$PROJECT/servers/$SERVER/cancel/" \ -H "Authorization: Bearer $HOSTHATCH_API_KEY" \ -H "Content-Type: application/json" \ -d '{"when": "end_of_term", "reason": "No longer needed"}' ``` ``` {"status": "cancellation_requested", "when": "end_of_term"} ``` ## A script in Python This script reboots a server and waits for the outcome. It reads the error `code` from problem details, waits as long as a `429` asks, and repeats a request with the same `Idempotency-Key`, so a retried reboot can't happen twice. ``` import os import time import uuid import requests API = "https://console.hosthatch.com/api/v1" session = requests.Session() session.headers["Authorization"] = f"Bearer {os.environ['HOSTHATCH_API_KEY']}" def call(method, path, **kwargs): for _ in range(5): response = session.request(method, API + path, timeout=30, **kwargs) if response.status_code != 429: break time.sleep(int(response.headers.get("Retry-After", 60))) if response.headers.get("Content-Type", "").startswith("application/problem+json"): problem = response.json() raise RuntimeError(f"{problem['status']} {problem['code']}: {problem['detail']}") response.raise_for_status() return response.json() if response.content else None def reboot(project, server_id, timeout=1800): action = call( "POST", f"/projects/{project}/servers/{server_id}/actions/reboot/", headers={"Idempotency-Key": str(uuid.uuid4())}, ) deadline = time.monotonic() + timeout while action["status"] in ("running", "unknown") and time.monotonic() < deadline: time.sleep(5) action = call("GET", f"/projects/{project}/actions/{action['id']}/") return action print(reboot("acme", "774736")) ``` ## Next - [Servers](https://docs.hosthatch.com/api/servers/index.md): everything a server has, and what you can change. - [Actions](https://docs.hosthatch.com/api/actions/index.md): how actions run, and which can run together. - [Errors](https://docs.hosthatch.com/api/errors/index.md): every error code, and what to do about it. # Authentication Every request carries an API key in the `Authorization` header, except `GET /openapi.json`, which is public. A key acts as you: it can do what your role allows in each project you are a member of. ``` curl https://console.hosthatch.com/api/v1/me/ \ -H "Authorization: Bearer $HOSTHATCH_API_KEY" ``` ## Endpoints | Endpoint | What it does | | ---------- | ------------------------------------------------------------------------------------------- | | GET `/me/` | [Get the current user](https://docs.hosthatch.com/api/authentication/#get-the-current-user) | ## Create a key Keys are created on your [profile page](https://console.hosthatch.com/users/profile/), under **API Keys**. The console may ask for your password again first. | Setting | What it does | | --------- | ---------------------------------------------------------------------------------------- | | Name | A label of up to 50 characters, such as `deploy script`. | | Expires | Never (the default), or a day you pick: the key works until the end of that day, in UTC. | | Read-only | The key can read and poll, and nothing else. See [Read-only keys](#read-only-keys). | The key is shown once, when you create it. Copy it into your secret manager or an environment variable then. The profile page lists your keys with their prefix, access, expiry and when each was last used, and is where you revoke them. ## Send the key Send the key as a Bearer token on every request: ``` Authorization: Bearer ``` Send it in this header only, never in the URL. A request without it, or with another scheme such as `Basic`, answers `401 authentication_required` with a `WWW-Authenticate: Bearer` header. ## Read-only keys A read-only key can send `GET` requests and nothing else: it can list and read everything, and poll [actions](https://docs.hosthatch.com/api/actions/index.md) and [orders](https://docs.hosthatch.com/api/orders-and-payment/index.md). Any request that would change something answers `403 read_only_key`. A read-only key never sees a reset root password. Use read-only keys for monitoring, dashboards and agents that only need to look. ## Expiry and revocation - A key past its expiry answers `401 api_key_expired`. Create a new one. - A revoked key answers `401 invalid_api_key`, as an unknown or malformed key does. - Changing or resetting your password revokes every key you have. - Keys of a deactivated account answer `401 invalid_api_key`. ## Failed attempts After 20 failed key checks from one IP address within 10 minutes, every key sent from that address answers `429 rate_limited`, even a valid one, until the 10 minutes have passed. `Retry-After` says how many seconds are left. Fix the key before trying again rather than retrying in a loop. ## Keep keys safe - Keep keys out of code and repositories. Read them from an environment variable or a secret manager. - Give each script, server and agent its own key, so that you can revoke one without touching the others. - Use a read-only key wherever you only need to read. - Choose an expiry. To rotate a key, create the new one, switch to it, then revoke the old one. - A key carries your access to every project you belong to. Treat it like your password. ## Errors | Status | Code | When | | ------ | ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | | 401 | [`authentication_required`](https://docs.hosthatch.com/api/errors/#authentication_required) | No key, or a scheme other than `Bearer`. | | 401 | [`invalid_api_key`](https://docs.hosthatch.com/api/errors/#invalid_api_key) | The key is unknown, malformed or revoked, or the account is deactivated. | | 401 | [`api_key_expired`](https://docs.hosthatch.com/api/errors/#api_key_expired) | The key is past its expiry. | | 403 | [`read_only_key`](https://docs.hosthatch.com/api/errors/#read_only_key) | A read-only key asked for a change. | | 429 | [`rate_limited`](https://docs.hosthatch.com/api/errors/#rate_limited) | Too many failed key checks from your address, or the key's [rate limit](https://docs.hosthatch.com/api/rate-limits/index.md). | ## The current user ### Get the current user GET `/me/` Any member · Read-only keys: yes · Returns `200 Me` Who the key belongs to, and the projects it can reach. Use it to check a key and to find your projects' slugs. ``` curl https://console.hosthatch.com/api/v1/me/ \ -H "Authorization: Bearer $HOSTHATCH_API_KEY" ``` ``` { "id": 1, "email": "jane@example.com", "first_name": "Jane", "last_name": "Doe", "billing_account": {"exists": true, "credit_balance": "40.00", "currency": "USD"}, "projects": [{"slug": "acme", "name": "Acme", "role": "owner"}] } ``` | Field | Type | Description | | ------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | integer | Your user id. | | `email` | string | Your email address. | | `first_name`, `last_name` | string | Your name. Either can be empty. | | `projects` | array | The projects you are a member of, by name: each has its `slug`, which addresses it in every other path, and its `name`. | | `projects[].role` | string | Your role in the project: `owner`, `admin` or `member`. | | `billing_account` | object | Your own billing account: `{"exists": false}` when you have none. Otherwise `exists` is `true`, `credit_balance` is your account credit as a decimal string, or `null` when it can't be read just now, and `currency` is its currency. Orders in the projects you own are paid from this credit: see [Orders and payment](https://docs.hosthatch.com/api/orders-and-payment/#payment). | A user without a project gets an empty `projects` list. # Projects and roles A project holds servers, SSH keys, firewall groups, snapshots, ISOs, actions and orders, and the people who manage them. Each person has one role in each project they belong to. Paths address a project by its slug, which is the project's id in every path and never changes: `/projects/acme/servers/`. You create, rename and delete projects, and invite and remove their members, in the [console](https://console.hosthatch.com/). The API lists the projects you belong to and reads one. ## Endpoints | Endpoint | What it does | | ----------------------- | --------------------------------------------------------------------------------- | | GET `/projects/` | [List projects](https://docs.hosthatch.com/api/projects-and-roles/#list-projects) | | GET `/projects/{slug}/` | [Get a project](https://docs.hosthatch.com/api/projects-and-roles/#get-a-project) | ## Roles | | Member | Admin | Owner | | -------------------------------------------------------------------------- | ------ | ----- | ----- | | Read everything, and poll actions and orders | | | | | Power, reboot and reinstall servers, and take and restore snapshots | | | | | Manage hostnames, SSH keys, firewalls, ISOs and reverse DNS | | | | | Order servers, upgrade them, buy bandwidth and cancel servers | | | | | Rename the project, and manage its members and invitations, in the console | | | | | Delete the project, in the console | | | | A request your role doesn't allow answers `403 insufficient_role`. A project you are not a member of answers `404 not_found`, the same as one that doesn't exist, so a 404 never tells anyone a project exists. A [read-only key](https://docs.hosthatch.com/api/authentication/#read-only-keys) can only read, whatever your role. Transferring a server to another account is done in the console. The API has no endpoint for it. ## Project readiness A new project takes a moment to set up. Until it is ready, its `ready` field is `false` and every endpoint for its servers and other resources answers `403 project_not_ready`. If the project's owner has no billing account yet, those endpoints answer `403 billing_account_required` instead. ## The project object ``` { "id": 7, "name": "Acme", "slug": "acme", "ready": true, "role": "owner", "created_at": "2026-10-19T10:00:00Z" } ``` | Field | Type | Description | | ------------ | ------- | ----------------------------------------------------------------- | | `id` | integer | The project's id. Paths use the slug instead. | | `name` | string | A label you can change in the console. It never appears in paths. | | `slug` | string | The project's id in paths. It never changes. | | `ready` | boolean | `false` while the project is being set up. | | `role` | string | Your role in it: `owner`, `admin` or `member`. | | `created_at` | string | When it was created. | ## Projects ### List projects GET `/projects/` Any member · Read-only keys: yes · Returns `200 Project[]` The projects you are a member of, by name, with your role in each. A user without a project gets an empty list. ``` curl https://console.hosthatch.com/api/v1/projects/ \ -H "Authorization: Bearer $HOSTHATCH_API_KEY" ``` ``` { "count": 1, "next": null, "previous": null, "results": [ {"id": 7, "name": "Acme", "slug": "acme", "ready": true, "role": "owner", "created_at": "2026-10-19T10:00:00Z"} ] } ``` ### Get a project GET `/projects/{slug}/` Any member · Read-only keys: yes · Returns `200 Project` One project you are a member of. Any other slug answers `404 not_found`. ``` curl https://console.hosthatch.com/api/v1/projects/acme/ \ -H "Authorization: Bearer $HOSTHATCH_API_KEY" ``` ## Errors | Status | Code | When | | ------ | --------------------------------------------------------------------------------------------- | ----------------------------------------------- | | 403 | [`insufficient_role`](https://docs.hosthatch.com/api/errors/#insufficient_role) | Your role doesn't allow it. | | 403 | [`billing_account_required`](https://docs.hosthatch.com/api/errors/#billing_account_required) | The project's owner has no billing account yet. | | 403 | [`project_not_ready`](https://docs.hosthatch.com/api/errors/#project_not_ready) | The project is still being set up. | | 404 | [`not_found`](https://docs.hosthatch.com/api/errors/#not_found) | No such project, or you are not a member. | # Servers A server is a virtual machine in one of your projects. Its id is a lowercase hexadecimal string, such as `"774736"`. This page covers reading servers and changing their hostname, reverse DNS and cancellation. Operations that run on the server, such as power, reinstall and snapshots, are [actions](https://docs.hosthatch.com/api/actions/index.md). Ordering, upgrading and extra bandwidth are [orders](https://docs.hosthatch.com/api/orders-and-payment/index.md). Firewall groups are attached on the [Firewalls](https://docs.hosthatch.com/api/firewalls/#attach-groups-to-a-server) page. ## Endpoints | Endpoint | What it does | | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | | GET `/projects/{slug}/servers/` | [List servers](https://docs.hosthatch.com/api/servers/#list-servers) | | POST `/projects/{slug}/servers/` | [Order a server](https://docs.hosthatch.com/api/orders-and-payment/#order-a-server) | | GET `/projects/{slug}/servers/{id}/` | [Get a server](https://docs.hosthatch.com/api/servers/#get-a-server) | | PATCH `/projects/{slug}/servers/{id}/` | [Rename a server](https://docs.hosthatch.com/api/servers/#rename-a-server) | | GET `/projects/{slug}/servers/{id}/reverse-dns/` | [List reverse DNS names](https://docs.hosthatch.com/api/servers/#list-reverse-dns-names) | | PUT `/projects/{slug}/servers/{id}/reverse-dns/{address}/` | [Set a reverse DNS name](https://docs.hosthatch.com/api/servers/#set-a-reverse-dns-name) | | DELETE `/projects/{slug}/servers/{id}/reverse-dns/{address}/` | [Clear a reverse DNS name](https://docs.hosthatch.com/api/servers/#clear-a-reverse-dns-name) | | GET `/projects/{slug}/servers/{id}/upgrade-options/` | [List upgrade options](https://docs.hosthatch.com/api/orders-and-payment/#list-upgrade-options) | | POST `/projects/{slug}/servers/{id}/upgrade/` | [Upgrade a server](https://docs.hosthatch.com/api/orders-and-payment/#upgrade-a-server) | | POST `/projects/{slug}/servers/{id}/bandwidth/` | [Buy extra bandwidth](https://docs.hosthatch.com/api/orders-and-payment/#buy-extra-bandwidth) | | POST `/projects/{slug}/servers/{id}/cancel/` | [Cancel a server](https://docs.hosthatch.com/api/servers/#cancel-a-server) | ## The server object ``` { "id": "774736", "hostname": "web-01", "status": "running", "pending_changes": false, "location": {"code": "AMS", "city": "Amsterdam", "country_code": "NL"}, "image": {"name": "almalinux10", "display_name": "AlmaLinux 10"}, "plan": {"name": "Cloud VPS 2x4", "category": "Compute", "cpu_cores": 2, "ram_mb": 4096, "disk_gb": 80, "boot_disk_gb": 0}, "network": { "ipv4": [ {"address": "203.0.113.10", "primary": true, "netmask": "255.255.255.0", "gateway": "203.0.113.1"}, {"address": "203.0.113.24", "primary": false, "netmask": "255.255.255.0", "gateway": null} ], "ipv6": [{"subnet": "2001:db8:2:f342::/64"}], "private_network": {"eligible": true, "enabled": false} }, "bandwidth": { "pooled": true, "allocated_gb": 1000, "extra_gb": 0, "used_gb": 12, "pool": {"allocated_gb": 5000, "extra_gb": 0, "used_gb": 301} }, "firewall_groups": [12], "ssh_keys": [3731], "iso": null, "has_user_data": false, "billing": { "service_id": 188060, "status": "Active", "billing_cycle": "monthly", "next_due_date": "2026-11-13", "recurring_amount": "12.00", "currency": "USD", "upgradeable_to": [41, 42] }, "created_at": "2026-10-13T12:58:59Z" } ``` | Field | Type | Description | | ------------------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | string | The server's id, lowercase hexadecimal. | | `hostname` | string or null | The server's name. | | `status` | string | `running`, `stopped` or `suspended`. | | `pending_changes` | boolean | The server has changes, such as a new plan, that its next [reboot](https://docs.hosthatch.com/api/actions/#reboot) through the API applies. A reboot from inside the server doesn't apply them. | | `location` | object | `code`, the location code that orders use, such as `AMS`; `city`; and `country_code`, ISO 3166-1 alpha-2 in capitals. Each is `null` when unknown. | | `image` | object | The operating system: `name`, which [reinstall](https://docs.hosthatch.com/api/actions/#reinstall) takes, and `display_name`. Either can be `null` when unknown. | | `plan` | object | `name`, `category`, `cpu_cores`, `ram_mb`, `disk_gb` and `boot_disk_gb`. Each can be `null` when unknown. | | `network.ipv4` | array | The server's IPv4 addresses, the primary one first. Each has `address`, `primary`, `netmask`, which can be `null`, and `gateway`, which only the primary address has. | | `network.ipv6` | array | The IPv6 subnets routed to the server, as `{"subnet": "2001:db8::/64"}`. Empty until you [enable IPv6](https://docs.hosthatch.com/api/actions/#enable-ipv6). | | `network.private_network` | object or null | `eligible`: the server can join the private network; `enabled`: it has. Either can be `null` when unknown. | | `bandwidth` | object or null | This month's traffic: `allocated_gb`, `extra_gb` bought on top, and `used_gb`, each an integer or `null` when unknown. `pooled` servers share an allowance with your other servers in the location, and `pool` has its totals, which can be `null` too; `pool` itself is `null` when the server is not pooled. | | `firewall_groups` | array or null | The ids of the [firewall groups](https://docs.hosthatch.com/api/firewalls/index.md) attached to the server. `null` when they couldn't be read just now: never read `null` as "no firewall". | | `ssh_keys` | array | The ids of the project's [SSH keys](https://docs.hosthatch.com/api/ssh-keys/index.md) set on the server. | | `iso` | object or null | The mounted ISO, as `{"id", "name"}`, where `name` can be `null`; or `null` when none is mounted. | | `has_user_data` | boolean | The server was installed with cloud-init user data. | | `billing` | object or null | `service_id`; `status` as billing reports it, such as `Active`; `billing_cycle`, such as `monthly` or `semiannually`; `next_due_date`; `recurring_amount` and `currency`; and `upgradeable_to`, the product ids an [upgrade](https://docs.hosthatch.com/api/orders-and-payment/#upgrade-a-server) can move it to. `status`, `billing_cycle`, `next_due_date` and `recurring_amount` can be `null`. The whole object is `null` when the server has no billing service, or billing couldn't be read just now. | | `created_at` | string or null | When the server was created. | When billing or the firewall can't be read, the server is still returned, with that field `null`. ### In lists Lists return a summary of each server. Get the server for the rest. `hostname`, `ipv4_primary` and `plan.name` can be `null`, and so can each of `location`'s fields. ``` { "id": "774736", "hostname": "web-01", "status": "running", "ipv4_primary": "203.0.113.10", "location": {"code": "AMS", "city": "Amsterdam", "country_code": "NL"}, "plan": {"name": "Cloud VPS 2x4"} } ``` ## Reading and renaming ### List servers GET `/projects/{slug}/servers/` Any member · Read-only keys: yes · Returns `200 ServerSummary[]` The project's servers, as summaries. ``` curl "https://console.hosthatch.com/api/v1/projects/acme/servers/" \ -H "Authorization: Bearer $HOSTHATCH_API_KEY" ``` ### Get a server GET `/projects/{slug}/servers/{id}/` Any member · Read-only keys: yes · Returns `200 Server` One server, with everything in [the server object](#the-server-object). ### Rename a server PATCH `/projects/{slug}/servers/{id}/` Any member · Read-only keys: no · Returns `200 Server` Change the server's hostname, the only property of a server that can change this way. The answer is the updated server. ``` curl -X PATCH "https://console.hosthatch.com/api/v1/projects/acme/servers/774736/" \ -H "Authorization: Bearer $HOSTHATCH_API_KEY" \ -H "Content-Type: application/json" \ -d '{"hostname": "web-01.example.com"}' ``` A hostname is 2 to 64 characters. It is one or more labels separated by dots, and each label has 1 to 63 letters, digits and hyphens, and starts and ends with a letter or a digit. A body without `hostname` changes nothing. ## Reverse DNS Reverse DNS maps an address back to a name, which mail servers and some other services check. Set a matching forward record (`A` or `AAAA`) for the name at your DNS provider too. ### List reverse DNS names GET `/projects/{slug}/servers/{id}/reverse-dns/` Any member · Read-only keys: yes · Returns `200 ReverseDns[]` The reverse DNS name of each of the server's IPv4 addresses. `ptr` is `null` where none is set. ``` { "count": 2, "next": null, "previous": null, "results": [ {"address": "203.0.113.10", "ptr": "web-01.example.com"}, {"address": "203.0.113.24", "ptr": null} ] } ``` ### Set a reverse DNS name PUT `/projects/{slug}/servers/{id}/reverse-dns/{address}/` Any member · Read-only keys: no · Returns `200 ReverseDns` Set the name of one of the server's addresses: one of its IPv4 addresses, or any address inside one of its IPv6 subnets. Another address answers `404 not_found`. ``` curl -X PUT "https://console.hosthatch.com/api/v1/projects/acme/servers/774736/reverse-dns/203.0.113.10/" \ -H "Authorization: Bearer $HOSTHATCH_API_KEY" \ -H "Content-Type: application/json" \ -d '{"ptr": "web-01.example.com"}' ``` ``` {"address": "203.0.113.10", "ptr": "web-01.example.com"} ``` `ptr` is a domain name of up to 255 characters, in ASCII, without a dot at the end. Each label has letters, digits and hyphens, and doesn't start or end with a hyphen. ### Clear a reverse DNS name DELETE `/projects/{slug}/servers/{id}/reverse-dns/{address}/` Any member · Read-only keys: no · Returns `204` Remove the name of one of the server's addresses. When the reverse DNS service is unavailable, these endpoints answer `503 reverse_dns_unavailable` with `Retry-After: 300`. ## Cancellation ### Cancel a server POST `/projects/{slug}/servers/{id}/cancel/` Admin or owner · Read-only keys: no · Returns `200 Cancellation` Ask for the server to be cancelled. | Field | Type | Description | | -------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | | `when` | string | `end_of_term`: the server runs until the end of the period you paid for, then it is deleted. `immediate`: it is deleted now, with everything on it. | | `reason` | string | Why, in up to 1000 characters. Required for `end_of_term`. | ``` curl -X POST "https://console.hosthatch.com/api/v1/projects/acme/servers/774736/cancel/" \ -H "Authorization: Bearer $HOSTHATCH_API_KEY" \ -H "Content-Type: application/json" \ -d '{"when": "end_of_term", "reason": "Moving to a bigger plan"}' ``` ``` {"status": "cancellation_requested", "when": "end_of_term"} ``` A server keeps one cancellation request, so the request is safe to repeat as it is. Sending the same one again answers `200` and changes nothing, and it needs no `Idempotency-Key`. A request of the other kind answers `409 conflict`. Once an immediate cancellation has been carried out, the server is gone and a repeat answers `404`. A `502 upstream_error` means the request may not have been recorded: send the same request again. ## Errors | Status | Code | When | | ------ | --------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | 400 | [`validation_error`](https://docs.hosthatch.com/api/errors/#validation_error) | A field is invalid. | | 403 | [`insufficient_role`](https://docs.hosthatch.com/api/errors/#insufficient_role) | Cancelling needs the admin or owner role. | | 403 | [`read_only_key`](https://docs.hosthatch.com/api/errors/#read_only_key) | A change with a read-only key. | | 403 | [`billing_account_required`](https://docs.hosthatch.com/api/errors/#billing_account_required) | The project's owner has no billing account. | | 403 | [`project_not_ready`](https://docs.hosthatch.com/api/errors/#project_not_ready) | The project is still being set up. | | 404 | [`not_found`](https://docs.hosthatch.com/api/errors/#not_found) | No such server or address in the project. | | 409 | [`conflict`](https://docs.hosthatch.com/api/errors/#conflict) | A cancellation of the other kind is requested, or the server's billing service can't be matched: contact support. | | 502 | [`upstream_error`](https://docs.hosthatch.com/api/errors/#upstream_error) | A system behind the API failed. | | 503 | [`upstream_unavailable`](https://docs.hosthatch.com/api/errors/#upstream_unavailable) | A system behind the API is unavailable for now. | | 503 | [`reverse_dns_unavailable`](https://docs.hosthatch.com/api/errors/#reverse_dns_unavailable) | The reverse DNS service is unavailable for now. | # 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 `/projects/{slug}/actions/` | [List actions](https://docs.hosthatch.com/api/actions/#list-actions) | | GET `/projects/{slug}/actions/{id}/` | [Get an action](https://docs.hosthatch.com/api/actions/#get-an-action) | | GET `/projects/{slug}/servers/{id}/actions/` | [List a server's actions](https://docs.hosthatch.com/api/actions/#list-a-servers-actions) | | POST `/projects/{slug}/servers/{id}/actions/power-on/` | [Power on](https://docs.hosthatch.com/api/actions/#power-on) | | POST `/projects/{slug}/servers/{id}/actions/shutdown/` | [Shut down](https://docs.hosthatch.com/api/actions/#shut-down) | | POST `/projects/{slug}/servers/{id}/actions/power-off/` | [Power off](https://docs.hosthatch.com/api/actions/#power-off) | | POST `/projects/{slug}/servers/{id}/actions/reboot/` | [Reboot](https://docs.hosthatch.com/api/actions/#reboot) | | POST `/projects/{slug}/servers/{id}/actions/reinstall/` | [Reinstall](https://docs.hosthatch.com/api/actions/#reinstall) | | POST `/projects/{slug}/servers/{id}/actions/reset-password/` | [Reset the root password](https://docs.hosthatch.com/api/actions/#reset-the-root-password) | | POST `/projects/{slug}/servers/{id}/actions/enable-ipv6/` | [Enable IPv6](https://docs.hosthatch.com/api/actions/#enable-ipv6) | | POST `/projects/{slug}/servers/{id}/actions/enable-private-network/` | [Enable the private network](https://docs.hosthatch.com/api/actions/#enable-the-private-network) | | POST `/projects/{slug}/servers/{id}/actions/create-snapshot/` | [Take a snapshot](https://docs.hosthatch.com/api/snapshots/#take-a-snapshot) | | POST `/projects/{slug}/servers/{id}/actions/restore-snapshot/` | [Restore a snapshot](https://docs.hosthatch.com/api/snapshots/#restore-a-snapshot) | | POST `/projects/{slug}/servers/{id}/actions/mount-iso/` | [Mount an ISO](https://docs.hosthatch.com/api/isos/#mount-an-iso) | | POST `/projects/{slug}/servers/{id}/actions/unmount-iso/` | [Unmount an ISO](https://docs.hosthatch.com/api/isos/#unmount-an-iso) | ## How an action runs 1. You start it with a `POST` to one of the endpoints above. The answer is `202` with the action, whose `status` is `running`. 1. You poll it with [Get an action](#get-an-action) until its `status` is `success` or `error`. Poll the action itself: lists show each action as it was last recorded, which can be a few minutes behind. 1. On `success`, `result` holds what the action produced, if anything. On `error`, `error` says 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](#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](https://docs.hosthatch.com/api/rate-limits/index.md). 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](#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](#error-codes). Otherwise `null`. | | `result` | object | What the action produced, see [Results](#results). Empty for most types. | ### Types | Type | Started by | Exclusive | | ------------------------------- | ---------------------------------------------------------------------------------- | --------- | | `server.power_on` | [Power on](#power-on) | | | `server.shutdown` | [Shut down](#shut-down) | | | `server.power_off` | [Power off](#power-off) | | | `server.reboot` | [Reboot](#reboot) | | | `server.reinstall` | [Reinstall](#reinstall) | | | `server.reset_password` | [Reset the root password](#reset-the-root-password) | | | `server.enable_ipv6` | [Enable IPv6](#enable-ipv6) | | | `server.enable_private_network` | [Enable the private network](#enable-the-private-network) | | | `server.create_snapshot` | [Take a snapshot](https://docs.hosthatch.com/api/snapshots/#take-a-snapshot) | | | `server.restore_snapshot` | [Restore a snapshot](https://docs.hosthatch.com/api/snapshots/#restore-a-snapshot) | | | `server.mount_iso` | [Mount an ISO](https://docs.hosthatch.com/api/isos/#mount-an-iso) | | | `server.unmount_iso` | [Unmount an ISO](https://docs.hosthatch.com/api/isos/#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](#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 `200` with the original action, whatever state it has reached, and an `Idempotent-Replayed: true` header. 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 `/projects/{slug}/actions/` Any member · Read-only keys: yes · Returns `200 Action[]` 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 `/projects/{slug}/actions/{id}/` Any member · Read-only keys: yes · Returns `200 Action` One action, checked for its latest state before the answer. This is the endpoint to poll. ### List a server's actions GET `/projects/{slug}/servers/{id}/actions/` Any member · Read-only keys: yes · Returns `200 Action[]` The actions run on one server, newest first. ## Power The power endpoints take no request body. ### Power on POST `/projects/{slug}/servers/{id}/actions/power-on/` Any member · Read-only keys: no · Returns `202 Action` Start the server. ### Shut down POST `/projects/{slug}/servers/{id}/actions/shutdown/` Any member · Read-only keys: no · Returns `202 Action` Ask the operating system to shut down, as pressing the power button would. The operating system may ignore it: [power off](#power-off) if the server doesn't stop. ### Power off POST `/projects/{slug}/servers/{id}/actions/power-off/` Any member · Read-only keys: no · Returns `202 Action` Cut the power at once, as pulling the plug would. Anything the server hasn't saved is lost. ### Reboot POST `/projects/{slug}/servers/{id}/actions/reboot/` Any member · Read-only keys: no · Returns `202 Action` Restart the server. A server with `pending_changes` gets them applied by this reboot. ## Operating system ### Reinstall POST `/projects/{slug}/servers/{id}/actions/reinstall/` Any member · Read-only keys: no · Returns `202 Action` 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](https://docs.hosthatch.com/api/orders-and-payment/#list-images) lists them all. | | `ssh_keys` | array of integers | Ids of the project's [SSH keys](https://docs.hosthatch.com/api/ssh-keys/index.md) 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 `/projects/{slug}/servers/{id}/actions/reset-password/` Any member · Read-only keys: no · Returns `202 Action` 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](#get-an-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](https://docs.hosthatch.com/api/authentication/#read-only-keys); - only while it is still the server's root password, and the server is still in the project. ## Network ### Enable IPv6 POST `/projects/{slug}/servers/{id}/actions/enable-ipv6/` Any member · Read-only keys: no · Returns `202 Action` 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](https://docs.hosthatch.com/networking/#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 `/projects/{slug}/servers/{id}/actions/enable-private-network/` Any member · Read-only keys: no · Returns `202 Action` 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](https://docs.hosthatch.com/networking/#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`](https://docs.hosthatch.com/api/errors/#validation_error) | A field, or the `Idempotency-Key`, is invalid. | | 400 | [`feature_unavailable`](https://docs.hosthatch.com/api/errors/#feature_unavailable) | `user_data` on a reinstall. | | 403 | [`read_only_key`](https://docs.hosthatch.com/api/errors/#read_only_key) | Starting an action with a read-only key. | | 403 | [`billing_account_required`](https://docs.hosthatch.com/api/errors/#billing_account_required) | The project's owner has no billing account. | | 403 | [`project_not_ready`](https://docs.hosthatch.com/api/errors/#project_not_ready) | The project is still being set up. | | 404 | [`not_found`](https://docs.hosthatch.com/api/errors/#not_found) | No such server or action in the project. | | 409 | [`action_in_progress`](https://docs.hosthatch.com/api/errors/#action_in_progress) | Another exclusive action is running on the server. | | 409 | [`conflict`](https://docs.hosthatch.com/api/errors/#conflict) | Mounting a custom ISO that isn't `ready`. See [Mount an ISO](https://docs.hosthatch.com/api/isos/#mount-an-iso). | | 409 | [`idempotency_key_reused`](https://docs.hosthatch.com/api/errors/#idempotency_key_reused) | The key was used for another request. | | 409 | [`already_enabled`](https://docs.hosthatch.com/api/errors/#already_enabled), [`not_eligible`](https://docs.hosthatch.com/api/errors/#not_eligible), [`insufficient_capacity`](https://docs.hosthatch.com/api/errors/#insufficient_capacity) | See [Network](#network). | | 502 | [`upstream_error`](https://docs.hosthatch.com/api/errors/#upstream_error) | The host failed, or its answer was lost: with an `action_id`, poll that action. | | 503 | [`upstream_unavailable`](https://docs.hosthatch.com/api/errors/#upstream_unavailable) | A system behind the API is unavailable for now. Try again after `Retry-After`. | | 503 | [`node_maintenance`](https://docs.hosthatch.com/api/errors/#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`. # Orders and payment Ordering a server, an upgrade or extra bandwidth creates an order. The order pays for itself from the project owner's account credit when the credit covers it. Otherwise it waits while its invoice is paid in the console. You follow an order from its invoice to the server it delivers. ## Endpoints | Endpoint | What it does | | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------- | | GET `/projects/{slug}/products/` | [List products](https://docs.hosthatch.com/api/orders-and-payment/#list-products) | | GET `/projects/{slug}/images/` | [List images](https://docs.hosthatch.com/api/orders-and-payment/#list-images) | | GET `/projects/{slug}/locations/` | [List locations](https://docs.hosthatch.com/api/orders-and-payment/#list-locations) | | GET `/projects/{slug}/pricing/` | [Get pricing](https://docs.hosthatch.com/api/orders-and-payment/#get-pricing) | | POST `/projects/{slug}/servers/` | [Order a server](https://docs.hosthatch.com/api/orders-and-payment/#order-a-server) | | GET `/projects/{slug}/servers/{id}/upgrade-options/` | [List upgrade options](https://docs.hosthatch.com/api/orders-and-payment/#list-upgrade-options) | | POST `/projects/{slug}/servers/{id}/upgrade/` | [Upgrade a server](https://docs.hosthatch.com/api/orders-and-payment/#upgrade-a-server) | | POST `/projects/{slug}/servers/{id}/bandwidth/` | [Buy extra bandwidth](https://docs.hosthatch.com/api/orders-and-payment/#buy-extra-bandwidth) | | GET `/projects/{slug}/orders/` | [List orders](https://docs.hosthatch.com/api/orders-and-payment/#list-orders) | | GET `/projects/{slug}/orders/{id}/` | [Get an order](https://docs.hosthatch.com/api/orders-and-payment/#get-an-order) | ## How ordering works 1. Look up what you can order: [products](#list-products), [images](#list-images), [locations](#list-locations) and [prices](#get-pricing). 1. Place the order, with an [`Idempotency-Key`](#if-you-lose-the-answer). The answer is `201` with the order. 1. The order is paid from the project owner's credit if it covers the invoice. Otherwise it waits in `awaiting_payment`, with a link to the invoice. 1. Poll the order until it is `completed`. A server order then has the new server's id in `server_id`. Ordering, upgrading and buying bandwidth need the admin or owner role in the project, and are billed to the project owner's account. Orders have a [rate limit](https://docs.hosthatch.com/api/rate-limits/index.md) of their own: 10 a minute per key. ## Payment When the order's invoice is created, the project owner's account credit is applied to it if the credit covers what the invoice still owes. The order then moves on by itself. If the credit doesn't cover it, the order waits in `awaiting_payment`, and `invoice.url` links to the invoice, which the project's owner pays in the console. Once it is paid, the order continues, and polling it shows the progress. The credit used is the project owner's. The owner sees its balance in `billing_account.credit_balance` of [the current user](https://docs.hosthatch.com/api/authentication/#get-the-current-user). ## The order object ``` { "id": 55, "type": "server", "status": "awaiting_payment", "server_id": null, "request": { "hostname": "web-02", "product_id": 123, "location": "AMS", "image": "almalinux10", "billing_cycle": "monthly", "ipv4_additional": 0, "ipv6": true, "ssh_keys": [3731], "snapshot_id": null, "promo_code": null, "has_user_data": false }, "invoice": {"id": 777, "status": "unpaid", "total": "12.00", "currency": "USD", "url": "https://console.hosthatch.com/billing/invoice/777/"}, "warnings": [], "created_at": "2026-10-19T10:00:00Z", "updated_at": "2026-10-19T10:00:00Z" } ``` | Field | Type | Description | | -------------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | integer | The order's id. | | `type` | string | `server`, `upgrade` or `bandwidth`. | | `status` | string | See [Order statuses](#order-statuses). | | `server_id` | string or null | The server. For a `server` order it is set once the server exists; for an upgrade or bandwidth order, from the start. | | `request` | object | What you ordered. For a server, the order's fields with the billing cycle it got, and `has_user_data` in place of `user_data`, which is never stored. For an upgrade, `product_id` and `upgrade_disk`. For bandwidth, `size_gb`. | | `invoice` | object or null | The invoice: `id`, `status` as billing last showed it, such as `unpaid`, `total`, `currency`, and `url`, where the project's owner can pay it. `null` while the order has none. | | `warnings` | array | Things that went wrong after billing took the order, each with a `code` and a `message`. `credit_unconfirmed` means paying the invoice from account credit couldn't be confirmed, and support checks it. | | `created_at`, `updated_at` | string | When the order was placed, and last changed. | ### Order statuses | Status | Meaning | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `submitting` | Being placed. It lasts a few seconds. | | `awaiting_payment` | Its invoice waits to be paid. Pay it at `invoice.url`. | | `provisioning` | Paid, and being delivered: the server is being set up, or the bandwidth added. | | `completed` | Delivered. A server order has its `server_id`. | | `cancelled` | Cancelled in billing: its invoice was cancelled or refunded, or the new server's service was cancelled. | | `failed` | Billing refused the order. Nothing was charged, unless its invoice shows `paid`: then contact support instead of ordering again. | | `unknown` | Billing didn't confirm the order when it was placed, so whether it went through is being checked. It is never placed again. Poll it: it moves on once that is settled, by support if billing can't tell. Don't order again meanwhile. | ## The catalogue What you can order, as the project's owner can order it. The catalogue can be up to 5 minutes old. The examples' prices are illustrations: the endpoints answer with yours. ### List products GET `/projects/{slug}/products/` Any member · Read-only keys: yes · Returns `200 Product[]` The products you can order. ``` { "id": 123, "name": "Cloud VPS 2x4", "category": "Cloud Servers", "description": ["2 vCPU", "4 GB RAM", "80 GB NVMe"], "prices": {"monthly": "12.00", "quarterly": "34.00", "annually": "120.00"}, "setup_fees": {"monthly": "0.00"}, "currency": "USD", "billing_cycles": ["monthly", "quarterly", "annually"], "default_billing_cycle": "monthly", "locations": [{"code": "AMS", "in_stock": true, "upgrade_in_stock": true}], "images": ["almalinux10", "ubuntu24"] } ``` `locations` says where the product is in stock for new servers (`in_stock`) and for upgrades (`upgrade_in_stock`). `images` lists the images it can be installed with. ### List images GET `/projects/{slug}/images/` Any member · Read-only keys: yes · Returns `200 Image[]` The images a server can be installed with. `name` is what an order and a [reinstall](https://docs.hosthatch.com/api/actions/#reinstall) take. ``` {"name": "almalinux10", "distro": "AlmaLinux", "version": "10", "display_name": "AlmaLinux 10"} ``` ### List locations GET `/projects/{slug}/locations/` Any member · Read-only keys: yes · Returns `200 Location[]` The locations that at least one product is offered in. `code` is what an order takes. ``` {"code": "AMS", "city": "Amsterdam", "country": "The Netherlands", "country_code": "NL"} ``` ### Get pricing GET `/projects/{slug}/pricing/` Any member · Read-only keys: yes · Returns `200 Pricing` What a new server's extras cost on top of its product's price: additional IPv4 addresses, IPv6 and extra bandwidth. ``` { "currency": "USD", "ipv4_additional": [ {"count": 0, "prices": {"monthly": "0.00"}, "setup_fees": {"monthly": "0.00"}}, {"count": 1, "prices": {"monthly": "2.00"}, "setup_fees": {"monthly": "0.00"}} ], "ipv6": {"prices": {"monthly": "0.00"}, "setup_fees": {"monthly": "0.00"}}, "extra_bandwidth": [{"size_gb": 500, "price": "2.00"}, {"size_gb": 1000, "price": "4.00"}] } ``` `ipv4_additional` has an entry for each count of additional addresses an order can ask for, starting at `0`. Each entry, and `ipv6`, has a recurring price and a setup fee per billing cycle. `ipv6` is `null` when no product offers IPv6. These are the prices of the first product that offers each extra: billing charges a product that prices them differently its own price. `extra_bandwidth` lists the sizes [Buy extra bandwidth](#buy-extra-bandwidth) takes, each with its one-time price. ## Ordering ### Order a server POST `/projects/{slug}/servers/` Admin or owner · Read-only keys: no · Returns `201 Order` Order a new server in the project. ``` curl -X POST "https://console.hosthatch.com/api/v1/projects/acme/servers/" \ -H "Authorization: Bearer $HOSTHATCH_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 3b9f7c2e-8d41-4a6f-b0c5-9e2a7d1f4c88" \ -d '{ "hostname": "web-02", "product_id": 123, "location": "AMS", "image": "almalinux10", "billing_cycle": "monthly", "ssh_keys": [3731], "ipv4_additional": 0, "ipv6": true, "promo_code": null, "snapshot_id": null, "user_data": null }' ``` | Field | Type | Description | | ----------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | `hostname` | string | Required. The [same rules](https://docs.hosthatch.com/api/servers/#rename-a-server) as renaming a server. | | `product_id` | integer | Required. A [product](#list-products)'s `id`. | | `location` | string | Required. A location `code` where the product is in stock. | | `billing_cycle` | string | One of the product's `billing_cycles`. Leave it out for its `default_billing_cycle`. | | `image` | string or null | One of the product's `images`. Send `image` or `snapshot_id`: one of them, not both. | | `snapshot_id` | integer or null | A `completed` [snapshot](https://docs.hosthatch.com/api/snapshots/index.md) of the project to create the server from, instead of an image. | | `ssh_keys` | array of integers | Ids of the project's [SSH keys](https://docs.hosthatch.com/api/ssh-keys/index.md) to install, up to 100. | | `ipv4_additional` | integer | How many IPv4 addresses to add to the first one, `0` by default. [Pricing](#get-pricing) lists the counts. | | `ipv6` | boolean | Give the server an IPv6 subnet. | | `promo_code` | string or null | A promotion code: letters, digits, hyphens and underscores, up to 64 characters. | | `user_data` | string or null | Not available yet: sending it answers `400 feature_unavailable`. Cloud-init user data, up to 65536 characters, starting with `#cloud-config` or `#!`. | A choice the product doesn't offer answers `400 validation_error` on that field: a location without stock, a billing cycle it isn't sold with, an image it doesn't take, more additional addresses than it allows, IPv6 it can't have, a snapshot whose image it can't be ordered with, or a key or snapshot that isn't the project's. The answer is `201` with the order, of type `server`. To put the server on its location's private network, start its [enable-private-network](https://docs.hosthatch.com/api/actions/#enable-the-private-network) action once the order is `completed`. ### List upgrade options GET `/projects/{slug}/servers/{id}/upgrade-options/` Any member · Read-only keys: yes · Returns `200 UpgradeOption[]` The products the server can be upgraded to, with their prices. One that isn't `in_stock` can't be ordered in the server's location now. ``` {"product_id": 41, "name": "Cloud VPS 4x8", "prices": {"monthly": "24.00"}, "currency": "USD", "in_stock": true} ``` ### Upgrade a server POST `/projects/{slug}/servers/{id}/upgrade/` Admin or owner · Read-only keys: no · Returns `201 Order` Move the server to one of its upgrade options. It keeps its billing cycle, and billing invoices what is left of the current term. ``` {"product_id": 41, "upgrade_disk": true} ``` | Field | Type | Description | | -------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------- | | `product_id` | integer | Required. One of the server's [upgrade options](#list-upgrade-options). Any other answers `400 validation_error`. | | `upgrade_disk` | boolean | Also grow the server's disk to the new plan's size when the upgrade is applied. Left out or `false`, the disk keeps its size. | The answer is `201` with an order of type `upgrade`, which completes once its invoice is paid. If the server then shows `pending_changes: true`, a [reboot](https://docs.hosthatch.com/api/actions/#reboot) through the API applies the new plan. A server takes one upgrade at a time. While one is unpaid or its outcome is unknown, another answers `409 conflict`, with the open order's id in `order_id` when it is the project's. An option that isn't `in_stock` answers `409 insufficient_capacity`. ### Buy extra bandwidth POST `/projects/{slug}/servers/{id}/bandwidth/` Admin or owner · Read-only keys: no · Returns `201 Order` Add bandwidth to the server's allowance for this month. ``` {"size_gb": 1000} ``` `size_gb` is one of the sizes in [pricing](#get-pricing)'s `extra_bandwidth`, and the price is the one listed there. The answer is `201` with an order of type `bandwidth`. It is `provisioning` while the bandwidth is added, and `completed` once the server's `bandwidth.extra_gb` has it. ## Orders ### List orders GET `/projects/{slug}/orders/` Any member · Read-only keys: yes · Returns `200 Order[]` The project's orders, newest first. Lists show each order as last recorded: poll the order itself to follow it. ### Get an order GET `/projects/{slug}/orders/{id}/` Any member · Read-only keys: yes · Returns `200 Order` One order, checked against billing for its latest state before the answer. ## If you lose the answer Send an `Idempotency-Key` with every order, as for [actions](https://docs.hosthatch.com/api/actions/#idempotency). If you send the same order again with the same key, you get the first order back, with `200` and `Idempotent-Replayed: true`, instead of buying twice. The same key with a different body answers `409 idempotency_key_reused`. When billing doesn't answer, the order is still recorded: the answer is `502 upstream_error` with the order's id in `order_id`, and the order is `unknown` while it is checked. Poll that order rather than ordering again. If it ends `failed` and its invoice isn't `paid`, place a new order with a new key. ## Errors | Status | Code | When | | ------ | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | | 400 | [`validation_error`](https://docs.hosthatch.com/api/errors/#validation_error) | A field is invalid, or the product doesn't offer that choice. | | 400 | [`feature_unavailable`](https://docs.hosthatch.com/api/errors/#feature_unavailable) | `user_data` in an order. | | 403 | [`insufficient_role`](https://docs.hosthatch.com/api/errors/#insufficient_role) | Ordering needs the admin or owner role. | | 403 | [`read_only_key`](https://docs.hosthatch.com/api/errors/#read_only_key) | Ordering with a read-only key. | | 403 | [`billing_account_required`](https://docs.hosthatch.com/api/errors/#billing_account_required) | The project's owner has no billing account. | | 403 | [`project_not_ready`](https://docs.hosthatch.com/api/errors/#project_not_ready) | The project is still being set up. | | 404 | [`not_found`](https://docs.hosthatch.com/api/errors/#not_found) | No such order or server in the project. | | 409 | [`idempotency_key_reused`](https://docs.hosthatch.com/api/errors/#idempotency_key_reused) | The key was used for another request. | | 409 | [`conflict`](https://docs.hosthatch.com/api/errors/#conflict) | The server has an upgrade that isn't paid or settled yet, or its billing service can't be found. | | 409 | [`insufficient_capacity`](https://docs.hosthatch.com/api/errors/#insufficient_capacity) | The upgrade is out of stock in the server's location. | | 429 | [`rate_limited`](https://docs.hosthatch.com/api/errors/#rate_limited) | More than 10 orders a minute with one key. | | 502 | [`upstream_error`](https://docs.hosthatch.com/api/errors/#upstream_error) | Billing failed or didn't answer. With an `order_id`, poll that order. | | 503 | [`upstream_unavailable`](https://docs.hosthatch.com/api/errors/#upstream_unavailable) | Billing is unavailable for now. | # Snapshots A snapshot is a copy of a server's disk at one moment. Take one before a risky change, restore it if the change goes wrong, or create a new server from it. ## Endpoints | Endpoint | What it does | | -------------------------------------------------------------- | ---------------------------------------------------------------------------------- | | POST `/projects/{slug}/servers/{id}/actions/create-snapshot/` | [Take a snapshot](https://docs.hosthatch.com/api/snapshots/#take-a-snapshot) | | POST `/projects/{slug}/servers/{id}/actions/restore-snapshot/` | [Restore a snapshot](https://docs.hosthatch.com/api/snapshots/#restore-a-snapshot) | | GET `/projects/{slug}/snapshots/` | [List snapshots](https://docs.hosthatch.com/api/snapshots/#list-snapshots) | | GET `/projects/{slug}/snapshots/{id}/` | [Get a snapshot](https://docs.hosthatch.com/api/snapshots/#get-a-snapshot) | | PATCH `/projects/{slug}/snapshots/{id}/` | [Rename a snapshot](https://docs.hosthatch.com/api/snapshots/#rename-a-snapshot) | | DELETE `/projects/{slug}/snapshots/{id}/` | [Delete a snapshot](https://docs.hosthatch.com/api/snapshots/#delete-a-snapshot) | ## The snapshot object ``` { "id": 2958, "name": "before upgrade", "status": "completed", "state": "completed", "size_gb": 12.5, "server_id": "4c63f6", "image": "almalinux9", "created_at": "2026-10-19T10:00:00Z", "error": null } ``` | Field | Type | Description | | ------------ | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | integer | The snapshot's id. | | `name` | string | Its name, which you can change. | | `status` | string | `pending` while it is being taken, then `completed` or `failed`. | | `state` | string or null | A finer-grained step of the same progress, for display, such as `uploading`. More may be added: act on `status`. | | `size_gb` | number or null | Its size, or `null` while it isn't known. | | `server_id` | string or null | The server it was taken from. | | `image` | string or null | The image that server was installed with. | | `created_at` | string | When it was taken. | | `error` | object or null | On `failed`: a `code`, which is `failed`, and a `message` that says to take a new snapshot, and to contact support with this one's id if that fails too. More codes may be added. Otherwise `null`. | ## Reading, renaming and deleting ### List snapshots GET `/projects/{slug}/snapshots/` Any member · Read-only keys: yes · Returns `200 Snapshot[]` The project's snapshots, of all its servers, oldest first. A snapshot belongs to the project: you can restore it onto any of the project's servers, and it stays when the server it was taken from is gone. ### Get a snapshot GET `/projects/{slug}/snapshots/{id}/` Any member · Read-only keys: yes · Returns `200 Snapshot` One snapshot. Poll it while it is `pending`. ### Rename a snapshot PATCH `/projects/{slug}/snapshots/{id}/` Any member · Read-only keys: no · Returns `200 Snapshot` Change the snapshot's name, 1 to 250 characters. Nothing else about it can change. ``` {"name": "before upgrade"} ``` ### Delete a snapshot DELETE `/projects/{slug}/snapshots/{id}/` Any member · Read-only keys: no · Returns `204` Delete the snapshot. It can't be recovered, and it no longer counts against the project owner's snapshot limit. ## Taking and restoring Both are [actions](https://docs.hosthatch.com/api/actions/index.md), and exclusive: a server takes or restores one snapshot at a time, and not during another exclusive action. Send an [`Idempotency-Key`](https://docs.hosthatch.com/api/actions/#idempotency) with each. ### Take a snapshot POST `/projects/{slug}/servers/{id}/actions/create-snapshot/` Any member · Read-only keys: no · Returns `202 Action` Take a snapshot of the server's disk. The request takes no body. The snapshot is `pending` in the list while it is taken, and the action succeeds once it is `completed`. Once the action has succeeded, its `result` has the new snapshot's id when it can be told, as `{"snapshot_id": 2960}`; the snapshot list shows it either way. The project owner's account can hold a limited number of snapshots, across all the owner's projects: one more answers `409 snapshot_limit_reached`. Delete one of this project's snapshots, or ask the owner to delete one elsewhere. Failed snapshots don't count. A legacy server can't take snapshots, and answers `409 not_eligible`. ### Restore a snapshot POST `/projects/{slug}/servers/{id}/actions/restore-snapshot/` Any member · Read-only keys: no · Returns `202 Action` Overwrite the server's disk with a snapshot. Warning Everything on the server's disk now is lost: the snapshot replaces all of it, even when it was taken of another server. ``` {"snapshot_id": 2958} ``` The snapshot must be one of the project's, and `completed`. It can be of this server or of another one. Any other answers `400 validation_error`. A restore that ends in `error` may have changed the disk already. Check the server before you use it. ## New servers from a snapshot To create a new server from a snapshot, [order a server](https://docs.hosthatch.com/api/orders-and-payment/#order-a-server) with its `snapshot_id` instead of an `image`. ## Errors | Status | Code | When | | ------ | --------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | | 400 | [`validation_error`](https://docs.hosthatch.com/api/errors/#validation_error) | A field is invalid, or the snapshot to restore isn't the project's or isn't `completed`. | | 403 | [`read_only_key`](https://docs.hosthatch.com/api/errors/#read_only_key) | A change with a read-only key. | | 403 | [`billing_account_required`](https://docs.hosthatch.com/api/errors/#billing_account_required) | The project's owner has no billing account. | | 403 | [`project_not_ready`](https://docs.hosthatch.com/api/errors/#project_not_ready) | The project is still being set up. | | 404 | [`not_found`](https://docs.hosthatch.com/api/errors/#not_found) | No such snapshot or server in the project. | | 409 | [`action_in_progress`](https://docs.hosthatch.com/api/errors/#action_in_progress) | Another exclusive action is running on the server. | | 409 | [`idempotency_key_reused`](https://docs.hosthatch.com/api/errors/#idempotency_key_reused) | The key was used for another request. | | 409 | [`snapshot_limit_reached`](https://docs.hosthatch.com/api/errors/#snapshot_limit_reached) | The project owner's account has as many snapshots as it can hold. | | 409 | [`not_eligible`](https://docs.hosthatch.com/api/errors/#not_eligible) | A legacy server can't take snapshots. | | 502 | [`upstream_error`](https://docs.hosthatch.com/api/errors/#upstream_error) | A system behind the API failed. | | 503 | [`upstream_unavailable`](https://docs.hosthatch.com/api/errors/#upstream_unavailable) | A system behind the API is unavailable for now. Try again after `Retry-After`. | | 503 | [`node_maintenance`](https://docs.hosthatch.com/api/errors/#node_maintenance) | The server's host is in maintenance. | # Firewalls A firewall group is a set of rules that allow traffic, and you attach groups to servers. Groups belong to the project, so one group can protect many servers, and a server can have several groups. ## Endpoints | Endpoint | What it does | | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | | GET `/projects/{slug}/firewall-groups/` | [List firewall groups](https://docs.hosthatch.com/api/firewalls/#list-firewall-groups) | | POST `/projects/{slug}/firewall-groups/` | [Create a firewall group](https://docs.hosthatch.com/api/firewalls/#create-a-firewall-group) | | GET `/projects/{slug}/firewall-groups/{id}/` | [Get a firewall group](https://docs.hosthatch.com/api/firewalls/#get-a-firewall-group) | | PATCH `/projects/{slug}/firewall-groups/{id}/` | [Rename a firewall group](https://docs.hosthatch.com/api/firewalls/#rename-a-firewall-group) | | DELETE `/projects/{slug}/firewall-groups/{id}/` | [Delete a firewall group](https://docs.hosthatch.com/api/firewalls/#delete-a-firewall-group) | | GET `/projects/{slug}/firewall-groups/{id}/rules/` | [List a group's rules](https://docs.hosthatch.com/api/firewalls/#list-a-groups-rules) | | POST `/projects/{slug}/firewall-groups/{id}/rules/` | [Add a rule](https://docs.hosthatch.com/api/firewalls/#add-a-rule) | | GET `/projects/{slug}/firewall-groups/{id}/rules/{rule_id}/` | [Get a rule](https://docs.hosthatch.com/api/firewalls/#get-a-rule) | | PATCH `/projects/{slug}/firewall-groups/{id}/rules/{rule_id}/` | [Change a rule](https://docs.hosthatch.com/api/firewalls/#change-a-rule) | | DELETE `/projects/{slug}/firewall-groups/{id}/rules/{rule_id}/` | [Delete a rule](https://docs.hosthatch.com/api/firewalls/#delete-a-rule) | | PUT `/projects/{slug}/servers/{id}/firewall-groups/` | [Set a server's firewall groups](https://docs.hosthatch.com/api/firewalls/#set-a-servers-firewall-groups) | ## How firewalls work - A group has a direction, chosen when you create it: `ingress` rules allow traffic to the servers, and `egress` rules allow traffic from them. - Every rule allows traffic. Traffic that no rule allows is refused, so there are no deny rules. - A server without groups allows all traffic. Once it has any group attached, it refuses new inbound connections that no ingress rule allows. Once it has an egress group, it also refuses outbound traffic that no egress rule allows. - Rules are stateful: replies to an allowed connection pass. - The rules of all of a server's groups add up. - Changes reach the servers in the background, shortly after the request has been answered. Tip To allow only SSH and HTTPS in, create an `ingress` group with a `tcp` rule for port 22 and one for port 443, and attach it to the server. Leave the CIDR empty to allow every address, or give one, such as `198.51.100.0/24`, to allow only that network. ## The firewall group object ``` { "id": 12, "name": "web", "direction": "ingress", "servers": ["774736"], "rules": [ {"id": 1, "protocol": "tcp", "port_min": 443, "port_max": null, "source_cidr": null, "destination_cidr": null, "priority": 1000, "comment": "HTTPS"} ], "created_at": "2026-10-19T10:00:00Z" } ``` | Field | Type | Description | | ------------ | ------- | --------------------------------------------------------------------------------------------------------------------------------------- | | `id` | integer | The group's id. | | `name` | string | Up to 255 characters: letters, digits, spaces, dots, underscores and hyphens, starting with a letter or a digit. Unique in the project. | | `direction` | string | `ingress` or `egress`. It can't change. | | `servers` | array | The ids of the servers the group is attached to. | | `rules` | array | The group's rules, lowest `priority` first. | | `created_at` | string | When the group was created. | ## The rule object Every field is present, and `null` where it isn't set. | Field | Type | Description | | ------------------ | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | integer | The rule's id. | | `protocol` | string | `tcp`, `udp`, `icmp` or `any`. | | `port_min` | integer or null | For `tcp` and `udp` only: the first port, from 1 to 65535. Leave both ports empty to allow every port. | | `port_max` | integer or null | The last port of a range. Leave it empty to allow `port_min` alone. It needs `port_min`. | | `source_cidr` | string or null | For an `ingress` group: where the traffic comes from. | | `destination_cidr` | string or null | For an `egress` group: where the traffic goes. | | `priority` | integer | From 1 to 64996, 1000 if you leave it out. It orders the rules, lowest first. Every rule allows, so the order never changes what is allowed. | | `comment` | string or null | A note of up to 255 characters. It changes nothing. | A rule of an `ingress` group matches on `source_cidr` only, and one of an `egress` group on `destination_cidr` only. The other must be empty, so that a rule never looks narrower than what it allows. ### Addresses `source_cidr` and `destination_cidr` take a network in CIDR notation, such as `192.0.2.0/24` or `2001:db8::/32`, or a single address, which is stored as a `/32` or `/128` network. Empty or `null` means any address. - Send the network address: `192.0.2.1/24` is refused rather than read as `192.0.2.0/24`, so a typo can't change what the rule matches. - A scope id (`%`), quotes and whitespace are refused. - Addresses come back in canonical form: `2001:DB8::/32` becomes `2001:db8::/32`, and `192.0.2.7` becomes `192.0.2.7/32`. - A network matches its own address family only. `0.0.0.0/0` allows every IPv4 address and no IPv6 one: leave the field empty to allow both. ### Identical rules A group can't hold two rules that match the same traffic. Rules are the same when they have the same protocol, the same ports, and the same CIDR for the group's direction, however each is written: no ports and 1 to 65535 both mean every port, and a `port_min` alone is that one port. Adding or changing a rule to match another answers `409 rule_exists`, with that rule's id in `rule_id` when it can be read back. ## Groups ### List firewall groups GET `/projects/{slug}/firewall-groups/` Any member · Read-only keys: yes · Returns `200 FirewallGroup[]` The project's groups, each with its rules and the servers it is attached to. ### Create a firewall group POST `/projects/{slug}/firewall-groups/` Any member · Read-only keys: no · Returns `201 FirewallGroup` Create an empty group, then [add its rules](#add-a-rule). ``` curl -X POST "https://console.hosthatch.com/api/v1/projects/acme/firewall-groups/" \ -H "Authorization: Bearer $HOSTHATCH_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "web", "direction": "ingress"}' ``` A name another group of the project has answers `409 name_taken`, with that group's id in `group_id`. The id is left out only when the group couldn't be read back just then; list the project's groups to find it. So a create whose answer you lost is safe to send again: if it worked the first time, the group with that name is yours. ### Get a firewall group GET `/projects/{slug}/firewall-groups/{id}/` Any member · Read-only keys: yes · Returns `200 FirewallGroup` One group, with its rules and servers. ### Rename a firewall group PATCH `/projects/{slug}/firewall-groups/{id}/` Any member · Read-only keys: no · Returns `200 FirewallGroup` Change the group's name, the only thing about a group that can change. ``` {"name": "web-public"} ``` Other fields are ignored, so a group you read and send back whole with a new name is a rename. A different `direction` is refused, though: create a group with the other direction instead. A name another group has answers `409 name_taken`, with its id in `group_id` when it can be read back. ### Delete a firewall group DELETE `/projects/{slug}/firewall-groups/{id}/` Any member · Read-only keys: no · Returns `204` Delete the group and its rules. A group still attached to servers answers `409 group_in_use`, with their ids in `servers`: detach it from them first, since removing it changes what they allow. ## Rules ### List a group's rules GET `/projects/{slug}/firewall-groups/{id}/rules/` Any member · Read-only keys: yes · Returns `200 FirewallRule[]` The group's rules, lowest `priority` first. ### Add a rule POST `/projects/{slug}/firewall-groups/{id}/rules/` Any member · Read-only keys: no · Returns `201 FirewallRule` Add a rule that allows the traffic it matches. ``` curl -X POST "https://console.hosthatch.com/api/v1/projects/acme/firewall-groups/12/rules/" \ -H "Authorization: Bearer $HOSTHATCH_API_KEY" \ -H "Content-Type: application/json" \ -d '{"protocol": "tcp", "port_min": 22, "source_cidr": "198.51.100.0/24", "comment": "SSH from the office"}' ``` ``` {"id": 2, "protocol": "tcp", "port_min": 22, "port_max": null, "source_cidr": "198.51.100.0/24", "destination_cidr": null, "priority": 1000, "comment": "SSH from the office"} ``` Only `protocol` is required. A rule the group already has answers `409 rule_exists`, with its id in `rule_id`. The id is left out only when the group couldn't be read back just then; list the group's rules to find it. So a create whose answer you lost is safe to send again. ### Get a rule GET `/projects/{slug}/firewall-groups/{id}/rules/{rule_id}/` Any member · Read-only keys: yes · Returns `200 FirewallRule` One rule of the group. ### Change a rule PATCH `/projects/{slug}/firewall-groups/{id}/rules/{rule_id}/` Any member · Read-only keys: no · Returns `200 FirewallRule` Change some of a rule's fields; the others keep their values. The rule that results is checked as a whole, so changing `protocol` to `icmp` or `any` also needs `port_min` and `port_max` set to `null`. ``` {"port_min": 8443, "comment": "HTTPS on another port"} ``` ### Delete a rule DELETE `/projects/{slug}/firewall-groups/{id}/rules/{rule_id}/` Any member · Read-only keys: no · Returns `204` Remove the rule. The traffic it allowed is refused, unless another rule allows it. ## Attach groups to a server ### Set a server's firewall groups PUT `/projects/{slug}/servers/{id}/firewall-groups/` Any member · Read-only keys: no · Returns `200 ServerFirewallGroups` Set the groups attached to a server. The list replaces the current one, and `[]` detaches every group. Each id must be one of the project's groups. ``` curl -X PUT "https://console.hosthatch.com/api/v1/projects/acme/servers/774736/firewall-groups/" \ -H "Authorization: Bearer $HOSTHATCH_API_KEY" \ -H "Content-Type: application/json" \ -d '{"firewall_groups": [12, 13]}' ``` ``` {"firewall_groups": [12, 13]} ``` The server's [`firewall_groups`](https://docs.hosthatch.com/api/servers/#the-server-object) field shows the groups attached to it. ## Errors | Status | Code | When | | ------ | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | | 400 | [`validation_error`](https://docs.hosthatch.com/api/errors/#validation_error) | A field is invalid: a name, ports, a CIDR, a changed direction, or a group id that isn't the project's. | | 403 | [`read_only_key`](https://docs.hosthatch.com/api/errors/#read_only_key) | A change with a read-only key. | | 403 | [`billing_account_required`](https://docs.hosthatch.com/api/errors/#billing_account_required) | The project's owner has no billing account. | | 403 | [`project_not_ready`](https://docs.hosthatch.com/api/errors/#project_not_ready) | The project is still being set up. | | 404 | [`not_found`](https://docs.hosthatch.com/api/errors/#not_found) | No such group, rule or server in the project, or a rule of another group. | | 409 | [`name_taken`](https://docs.hosthatch.com/api/errors/#name_taken) | Another group of the project has the name. | | 409 | [`rule_exists`](https://docs.hosthatch.com/api/errors/#rule_exists) | The group already has an identical rule. | | 409 | [`group_in_use`](https://docs.hosthatch.com/api/errors/#group_in_use) | Deleting a group that is attached to servers. | | 502 | [`upstream_error`](https://docs.hosthatch.com/api/errors/#upstream_error) | A system behind the API failed. Send the same request again. | | 503 | [`upstream_unavailable`](https://docs.hosthatch.com/api/errors/#upstream_unavailable) | A system behind the API is unavailable for now. | # ISOs Mount an ISO image on a server to boot an installer or a rescue system from it. HostHatch keeps a library of ISOs, and each project can add one ISO of its own, which we download from a URL you give. ## Endpoints | Endpoint | What it does | | --------------------------------------------------------- | --------------------------------------------------------------------- | | POST `/projects/{slug}/servers/{id}/actions/mount-iso/` | [Mount an ISO](https://docs.hosthatch.com/api/isos/#mount-an-iso) | | POST `/projects/{slug}/servers/{id}/actions/unmount-iso/` | [Unmount an ISO](https://docs.hosthatch.com/api/isos/#unmount-an-iso) | | GET `/projects/{slug}/isos/` | [List ISOs](https://docs.hosthatch.com/api/isos/#list-isos) | | POST `/projects/{slug}/isos/` | [Upload an ISO](https://docs.hosthatch.com/api/isos/#upload-an-iso) | | GET `/projects/{slug}/isos/{id}/` | [Get an ISO](https://docs.hosthatch.com/api/isos/#get-an-iso) | | DELETE `/projects/{slug}/isos/{id}/` | [Delete an ISO](https://docs.hosthatch.com/api/isos/#delete-an-iso) | ## The ISO object ``` { "id": 8980, "name": "AlmaLinux-10.2-x86_64-dvd.iso", "type": "custom", "status": "ready", "progress": 100, "created_at": "2026-10-07T14:09:24Z" } ``` | Field | Type | Description | | ------------ | -------------- | -------------------------------------------------------------------- | | `id` | integer | The ISO's id, which [mounting](#mount-an-iso) takes. | | `name` | string | Its file name. | | `type` | string | `library` for HostHatch's ISOs, `custom` for the project's own. | | `status` | string | `downloading`, `ready` or `failed`. Library ISOs are always `ready`. | | `progress` | integer | How much is downloaded, from 0 to 100. | | `created_at` | string or null | When it was added. | ## Library and custom ISOs ### List ISOs GET `/projects/{slug}/isos/` Any member · Read-only keys: yes · Returns `200 Iso[]` The ISOs the project's servers can boot from: the project's own custom ISO first, then HostHatch's library. ### Upload an ISO POST `/projects/{slug}/isos/` Any member · Read-only keys: no · Returns `201 Iso` Add the project's custom ISO from a URL. The answer is `201` with the ISO, `downloading`: poll it until it is `ready`. Uploads through the API aren't available yet: until they are, this answers `400 feature_unavailable`. ``` {"url": "https://example.com/images/rescue.iso"} ``` The URL must: - be at most 255 characters of ASCII, use `http` or `https`, and have no user name, password, query string or fragment; - end in the file's name, made of letters, digits, dots, hyphens, underscores and plus signs, and ending in `.iso`; - point at a public host, not a private or internal address; - answer a `HEAD` request with `200` directly, without redirecting; - be a file that reports its size, at most 10 GiB, with a name the project's owner hasn't uploaded in any of their projects. A URL that breaks a rule answers `400 validation_error`, and the `url` error's `code` says which: `invalid` for the URL itself or the file, `unreachable` when its host can't be reached or isn't public, `redirect`, or `not_the_file` when it answers `HEAD` with anything but `200`. A project holds one custom ISO: while it has one, an upload answers `409 iso_limit_reached` with that ISO's id in `iso_id`. Delete it to upload another. If you lose the answer to an upload, send it again: if the upload had started, the answer is `409 iso_limit_reached` with its id. ### Get an ISO GET `/projects/{slug}/isos/{id}/` Any member · Read-only keys: yes · Returns `200 Iso` A library ISO, or the project's own. Poll a custom ISO while it is `downloading`. ### Delete an ISO DELETE `/projects/{slug}/isos/{id}/` Any member · Read-only keys: no · Returns `204` Delete the project's custom ISO. An ISO mounted on a server answers `409 iso_mounted`: unmount it first. Library ISOs are shared by every project, so deleting one answers `403 permission_denied`. ## Mounting Mounting and unmounting are [actions](https://docs.hosthatch.com/api/actions/index.md), and exclusive: one at a time per server, and not during another exclusive action. Each changes the server's boot order and reboots it. Send an [`Idempotency-Key`](https://docs.hosthatch.com/api/actions/#idempotency) with each. ### Mount an ISO POST `/projects/{slug}/servers/{id}/actions/mount-iso/` Any member · Read-only keys: no · Returns `202 Action` Mount an ISO on the server: a library ISO, or the project's own once it is `ready`. One still downloading, or whose download failed, answers `409 conflict`, and any other ISO `404 not_found`. ``` {"iso_id": 8980} ``` The server's `iso` field shows the mounted ISO. ### Unmount an ISO POST `/projects/{slug}/servers/{id}/actions/unmount-iso/` Any member · Read-only keys: no · Returns `202 Action` Unmount the server's ISO, so it boots from its disk again. The request takes no body. ## Errors | Status | Code | When | | ------ | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | | 400 | [`validation_error`](https://docs.hosthatch.com/api/errors/#validation_error) | A field is invalid, or the URL doesn't meet the rules above. | | 400 | [`feature_unavailable`](https://docs.hosthatch.com/api/errors/#feature_unavailable) | Uploading an ISO, while uploads through the API aren't available. | | 403 | [`read_only_key`](https://docs.hosthatch.com/api/errors/#read_only_key) | A change with a read-only key. | | 403 | [`permission_denied`](https://docs.hosthatch.com/api/errors/#permission_denied) | Deleting a library ISO. | | 403 | [`billing_account_required`](https://docs.hosthatch.com/api/errors/#billing_account_required) | The project's owner has no billing account. | | 403 | [`project_not_ready`](https://docs.hosthatch.com/api/errors/#project_not_ready) | The project is still being set up. | | 404 | [`not_found`](https://docs.hosthatch.com/api/errors/#not_found) | No such ISO or server in the project. | | 409 | [`iso_limit_reached`](https://docs.hosthatch.com/api/errors/#iso_limit_reached) | The project already has a custom ISO. | | 409 | [`iso_mounted`](https://docs.hosthatch.com/api/errors/#iso_mounted) | Deleting an ISO that is mounted. | | 409 | [`action_in_progress`](https://docs.hosthatch.com/api/errors/#action_in_progress) | Another exclusive action is running on the server. | | 409 | [`conflict`](https://docs.hosthatch.com/api/errors/#conflict) | Mounting a custom ISO that isn't `ready`. | | 409 | [`idempotency_key_reused`](https://docs.hosthatch.com/api/errors/#idempotency_key_reused) | The key was used for another request. | | 502 | [`upstream_error`](https://docs.hosthatch.com/api/errors/#upstream_error) | A system behind the API failed. | | 503 | [`upstream_unavailable`](https://docs.hosthatch.com/api/errors/#upstream_unavailable) | A system behind the API is unavailable for now. Try again after `Retry-After`. | | 503 | [`node_maintenance`](https://docs.hosthatch.com/api/errors/#node_maintenance) | The server's host is in maintenance. | # SSH keys A project's SSH keys are the public keys its servers can be installed with. Choose them when you [order a server](https://docs.hosthatch.com/api/orders-and-payment/#order-a-server) or [reinstall](https://docs.hosthatch.com/api/actions/#reinstall) one, and you can log in as root with the matching private key. ## Endpoints | Endpoint | What it does | | ---------------------------------------- | ------------------------------------------------------------------------------- | | GET `/projects/{slug}/ssh-keys/` | [List SSH keys](https://docs.hosthatch.com/api/ssh-keys/#list-ssh-keys) | | POST `/projects/{slug}/ssh-keys/` | [Add an SSH key](https://docs.hosthatch.com/api/ssh-keys/#add-an-ssh-key) | | GET `/projects/{slug}/ssh-keys/{id}/` | [Get an SSH key](https://docs.hosthatch.com/api/ssh-keys/#get-an-ssh-key) | | PATCH `/projects/{slug}/ssh-keys/{id}/` | [Rename an SSH key](https://docs.hosthatch.com/api/ssh-keys/#rename-an-ssh-key) | | DELETE `/projects/{slug}/ssh-keys/{id}/` | [Delete an SSH key](https://docs.hosthatch.com/api/ssh-keys/#delete-an-ssh-key) | ## The SSH key object ``` { "id": 3731, "name": "laptop", "public_key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEVixTZuGvU8D1V9aa9dGeyoy232lYd96rmgF27xLXF0", "fingerprint": "SHA256:OCKYOxqtDS41EAE0r/CqnopP+oYiNVHhN7k9Qm/R6lk", "created_at": "2026-03-18T00:00:00Z" } ``` | Field | Type | Description | | ------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | integer | The key's id, which orders and reinstalls take. | | `name` | string | The key's label. | | `public_key` | string | The public key, as ` `, without the comment it was added with. | | `fingerprint` | string or null | The SHA256 fingerprint, as `ssh-keygen -lf` prints it. `null` for a key added before keys were checked that the API would refuse today, such as a DSA key. | | `created_at` | string | The day the key was added. Only the day is recorded, so the time is midnight UTC. | ## Accepted keys A public key is one line of a `.pub` file, in OpenSSH format, of up to 2048 characters. The API accepts these types: | Type | Notes | | ------------------------------------------------------------------- | ------------------ | | `ssh-ed25519` | Recommended. | | `ecdsa-sha2-nistp256`, `ecdsa-sha2-nistp384`, `ecdsa-sha2-nistp521` | | | `ssh-rsa` | 2048 bits or more. | Security keys (`sk-` types), certificates and DSA keys are refused. The comment after the key isn't kept: the `name` is the key's label. To make a key, run `ssh-keygen -t ed25519` and add the contents of the `.pub` file it writes. ## Keys ### List SSH keys GET `/projects/{slug}/ssh-keys/` Any member · Read-only keys: yes · Returns `200 SshKey[]` The project's SSH keys. ### Add an SSH key POST `/projects/{slug}/ssh-keys/` Any member · Read-only keys: no · Returns `201 SshKey` Add a public key to the project. ``` jq -n --arg key "$(cat ~/.ssh/id_ed25519.pub)" '{name: "laptop", public_key: $key}' | curl -X POST "https://console.hosthatch.com/api/v1/projects/acme/ssh-keys/" \ -H "Authorization: Bearer $HOSTHATCH_API_KEY" \ -H "Content-Type: application/json" \ -d @- ``` | Field | Type | Description | | ------------ | ------ | -------------------------------------------------------------------------------------------- | | `name` | string | Required. Up to 255 characters: letters, digits, spaces, dots, `@`, underscores and hyphens. | | `public_key` | string | Required. One public key, as [above](#accepted-keys). | A key that is already in the project, whatever its comment, answers `409 key_exists`, with the existing key's id in `key_id`. So a create whose answer you lost is safe to send again. While one request is adding a key, another adding the same key answers `409 conflict`. ### Get an SSH key GET `/projects/{slug}/ssh-keys/{id}/` Any member · Read-only keys: yes · Returns `200 SshKey` One of the project's keys. ### Rename an SSH key PATCH `/projects/{slug}/ssh-keys/{id}/` Any member · Read-only keys: no · Returns `200 SshKey` Change the key's name. The public key itself can't change: add the new key and delete the old one. A body without `name` changes nothing. ``` {"name": "work laptop"} ``` ### Delete an SSH key DELETE `/projects/{slug}/ssh-keys/{id}/` Any member · Read-only keys: no · Returns `204` Delete the key from the project. Servers already installed with it still accept it: remove it from `~/.ssh/authorized_keys` on them too. ## Errors | Status | Code | When | | ------ | --------------------------------------------------------------------------------------------- | ------------------------------------------------- | | 400 | [`validation_error`](https://docs.hosthatch.com/api/errors/#validation_error) | The name or the public key is invalid. | | 403 | [`read_only_key`](https://docs.hosthatch.com/api/errors/#read_only_key) | A change with a read-only key. | | 403 | [`billing_account_required`](https://docs.hosthatch.com/api/errors/#billing_account_required) | The project's owner has no billing account. | | 403 | [`project_not_ready`](https://docs.hosthatch.com/api/errors/#project_not_ready) | The project is still being set up. | | 404 | [`not_found`](https://docs.hosthatch.com/api/errors/#not_found) | No such key in the project. | | 409 | [`key_exists`](https://docs.hosthatch.com/api/errors/#key_exists) | The key is already in the project. | | 409 | [`conflict`](https://docs.hosthatch.com/api/errors/#conflict) | Another request is adding the same key right now. | | 502 | [`upstream_error`](https://docs.hosthatch.com/api/errors/#upstream_error) | A system behind the API failed. | # API reference # Errors Every error is a problem details document ([RFC 9457](https://www.rfc-editor.org/rfc/rfc9457)), sent as `application/problem+json`. Its `code` is stable and made for code to act on. Its `type` links to the code's section on this page. One answer is an HTML page instead: the limit in front of the API answers `429` without problem details. See [Rate limits](https://docs.hosthatch.com/api/rate-limits/#the-limit-in-front-of-the-api). ``` { "type": "https://docs.hosthatch.com/api/errors#validation_error", "title": "Validation failed", "status": 400, "detail": "One or more fields are invalid.", "code": "validation_error", "instance": "/api/v1/projects/acme/servers/774736/", "errors": [ {"field": "hostname", "code": "invalid", "message": "Enter a valid hostname (letters, numbers, hyphens, and dots). Labels cannot start or end with a hyphen."} ] } ``` | Field | Description | | ---------- | -------------------------------------------------------------------------------------------------------------------- | | `type` | A link to this page's section for the code. | | `title` | A short summary of the code. | | `status` | The HTTP status. | | `detail` | A sentence for people. It can change, so don't act on it. | | `code` | The stable code, one of those below. | | `instance` | The path of the request. | | `errors` | On `validation_error` only: each problem, with its `field` (`null` for the whole request), a `code` and a `message`. | Some errors add a member that names a resource: `action_id`, `order_id`, `key_id`, `group_id`, `rule_id` or `servers`. Each code's section says when. ## Handle errors - Act on `code`. Don't parse `detail` or `title`. - A `4xx` usually means the request has to change, and sending it again unchanged gets the same answer. Some describe a state that passes with time, such as `409 action_in_progress`, `429 rate_limited`, `403 project_not_ready` and `409 insufficient_capacity`. Each code's section below says whether waiting helps. - A `5xx` is worth retrying after a short wait. Repeat an action or an order with the same [`Idempotency-Key`](https://docs.hosthatch.com/api/actions/#idempotency), so it can't happen twice. Before you repeat any other write, check whether the first one went through, unless its page says it is safe to send again. - When there is a `Retry-After` header, wait that many seconds. - New codes can appear in v1. Treat a code you don't know by its HTTP status. - Check the `Content-Type` before you parse the body as problem details, because of that one HTML answer. ## Codes | Code | Status | Title | | ------------------------------------------------------- | ------ | ------------------------ | | [`validation_error`](#validation_error) | 400 | Validation failed | | [`feature_unavailable`](#feature_unavailable) | 400 | Feature unavailable | | [`authentication_required`](#authentication_required) | 401 | Authentication required | | [`invalid_api_key`](#invalid_api_key) | 401 | Invalid API key | | [`api_key_expired`](#api_key_expired) | 401 | API key expired | | [`permission_denied`](#permission_denied) | 403 | Permission denied | | [`insufficient_role`](#insufficient_role) | 403 | Insufficient role | | [`read_only_key`](#read_only_key) | 403 | Read-only API key | | [`billing_account_required`](#billing_account_required) | 403 | Billing account required | | [`project_not_ready`](#project_not_ready) | 403 | Project not ready | | [`not_found`](#not_found) | 404 | Not found | | [`method_not_allowed`](#method_not_allowed) | 405 | Method not allowed | | [`not_acceptable`](#not_acceptable) | 406 | Not acceptable | | [`conflict`](#conflict) | 409 | Conflict | | [`action_in_progress`](#action_in_progress) | 409 | Action in progress | | [`already_enabled`](#already_enabled) | 409 | Already enabled | | [`not_eligible`](#not_eligible) | 409 | Not eligible | | [`insufficient_capacity`](#insufficient_capacity) | 409 | Insufficient capacity | | [`snapshot_limit_reached`](#snapshot_limit_reached) | 409 | Snapshot limit reached | | [`iso_limit_reached`](#iso_limit_reached) | 409 | ISO limit reached | | [`iso_mounted`](#iso_mounted) | 409 | ISO mounted | | [`name_taken`](#name_taken) | 409 | Name taken | | [`group_in_use`](#group_in_use) | 409 | Group in use | | [`key_exists`](#key_exists) | 409 | Key exists | | [`rule_exists`](#rule_exists) | 409 | Rule exists | | [`idempotency_key_reused`](#idempotency_key_reused) | 409 | Idempotency key reused | | [`unsupported_media_type`](#unsupported_media_type) | 415 | Unsupported media type | | [`rate_limited`](#rate_limited) | 429 | Rate limited | | [`internal_error`](#internal_error) | 500 | Internal error | | [`upstream_error`](#upstream_error) | 502 | Upstream error | | [`node_maintenance`](#node_maintenance) | 503 | Node maintenance | | [`reverse_dns_unavailable`](#reverse_dns_unavailable) | 503 | Reverse DNS unavailable | | [`upstream_unavailable`](#upstream_unavailable) | 503 | Upstream unavailable | ## 400 Bad Request ### `validation_error` 400 Validation failed **When:** The request body, a query parameter or the `Idempotency-Key` header is invalid, or the body is not valid JSON. `errors` lists each problem: the `field` (null when it is about the whole request), a `code` and a `message`. **What to do:** Correct the fields that `errors` names and send the request again. Sending it unchanged gets the same answer. Default `detail`: “One or more fields are invalid.” ### `feature_unavailable` 400 Feature unavailable **When:** The request uses a feature that is not available yet: `user_data` (cloud-init) on a reinstall or a new server, or uploading an ISO. **What to do:** Leave `user_data` out. For an ISO, mount one from the library instead. Default `detail`: “This feature is not available yet.” ## 401 Unauthorized ### `authentication_required` 401 Authentication required **When:** The request carries no API key, or its `Authorization` header uses a scheme other than `Bearer`. The response has a `WWW-Authenticate: Bearer` header. **What to do:** Send `Authorization: Bearer `. See [Authentication](https://docs.hosthatch.com/api/authentication/index.md). Default `detail`: “Authentication credentials were not provided.” ### `invalid_api_key` 401 Invalid API key **When:** The key is unknown, malformed or revoked, or the user it belongs to is deactivated. Changing or resetting your password revokes every key you have. **What to do:** Create a new key on your [profile page](https://console.hosthatch.com/users/profile/). After 20 failed attempts in 10 minutes, every key attempt from your address is refused with `rate_limited` until the 10 minutes are over. Default `detail`: “The API key is unknown, revoked or malformed.” ### `api_key_expired` 401 API key expired **When:** The key is past the expiry date it was created with. **What to do:** Create a new key on your [profile page](https://console.hosthatch.com/users/profile/). Default `detail`: “The API key has expired. Create a new one on your profile page.” ## 403 Forbidden ### `permission_denied` 403 Permission denied **When:** You are signed in, but not allowed to do this, and no more specific code applies. For example, deleting a library ISO, which every project shares. **What to do:** Check that the key is yours and that you have the role the endpoint needs. Contact support if you think it is a mistake. Default `detail`: “You do not have permission to perform this action.” ### `insufficient_role` 403 Insufficient role **When:** The endpoint needs the admin or owner role in the project and you are a member: ordering, upgrading, buying bandwidth and cancelling a server. **What to do:** Ask an admin or the owner of the project. See [Projects and roles](https://docs.hosthatch.com/api/projects-and-roles/#roles). Default `detail`: “This action needs the owner or admin role in the project.” ### `read_only_key` 403 Read-only API key **When:** A read-only key sent a request that would change something. Read-only keys can only read and poll. **What to do:** Use a key that is not read-only. Default `detail`: “This API key is read-only, so it cannot make changes.” ### `billing_account_required` 403 Billing account required **When:** The project's owner has no billing account yet, which the project's servers and other resources need. **What to do:** The project's owner sets one up on their profile page in the console. Default `detail`: “The project owner needs to create a billing account first.” ### `project_not_ready` 403 Project not ready **When:** The project is still being set up, so its servers and other resources can't be reached yet. **What to do:** Wait a minute and try again. Contact support if it lasts. Default `detail`: “The project is still being set up. Please try again shortly.” ## 404 Not Found ### `not_found` 404 Not found **When:** The resource does not exist, belongs to another project, or is in a project you are not a member of. A path the API does not have, a malformed id, and a page past the end of a list answer this too. **What to do:** Check the project slug, the id and the path. A 404 never says whether the resource exists somewhere you can't see. Default `detail`: “The resource does not exist, or you do not have access to it.” ## 405 Method Not Allowed ### `method_not_allowed` 405 Method not allowed **When:** The path exists, but not with this method. **What to do:** Check the method on the endpoint's page, for example `PATCH` rather than `PUT`. Default `detail`: “This method is not allowed on this resource.” ## 406 Not Acceptable ### `not_acceptable` 406 Not acceptable **When:** The request's `Accept` header rules out JSON, which is the only format the API answers with. **What to do:** Send `Accept: application/json`, or no `Accept` header. Default `detail`: “This API only answers with JSON.” ## 409 Conflict ### `conflict` 409 Conflict **When:** The resource is not in a state that allows the request, and no more specific code applies. For example, the server has an upgrade that isn't paid or settled yet, the ISO to mount hasn't finished downloading, a cancellation of the other kind is already requested, or the same SSH key is being added by another request at that moment. A refused upgrade carries the open upgrade's id in `order_id`. **What to do:** Read `detail`, check the resource's current state, and try again once it has changed. Default `detail`: “The resource is not in a state that allows this request.” ### `action_in_progress` 409 Action in progress **When:** Another exclusive action is still running on the server, or its outcome is still being confirmed. `action_id` names it when it is in your project. **What to do:** Poll that action until it finishes, then send your request again. See [Actions](https://docs.hosthatch.com/api/actions/#one-exclusive-action-at-a-time). Default `detail`: “Another action is still running on this server.” ### `already_enabled` 409 Already enabled **When:** IPv6 or the private network is already enabled on the server. **What to do:** Nothing to do. The server's `network` shows the subnet or the private network. Default `detail`: “This is already enabled.” ### `not_eligible` 409 Not eligible **When:** The server can't have this. The private network is not available for it, or it is a legacy server, which can't take snapshots. **What to do:** Contact support if you need it. Default `detail`: “This server is not eligible for this action.” ### `insufficient_capacity` 409 Insufficient capacity **When:** There is no capacity for this right now, for example no free IPv6 block for the server's location, or no stock in the server's location of the product an upgrade asks for. **What to do:** Try again later, or contact support. Default `detail`: “There is not enough capacity for this right now.” ### `snapshot_limit_reached` 409 Snapshot limit reached **When:** The project owner's account has as many snapshots as it is allowed, across all the owner's projects. Failed snapshots don't count. **What to do:** Delete a snapshot of the project that you no longer need, or ask the project's owner to delete one, then try again. Default `detail`: “The account has reached its snapshot limit.” ### `iso_limit_reached` 409 ISO limit reached **When:** The project already has a custom ISO. A project can hold one. `iso_id` names it when it can be read: after an upload whose answer was lost, it is the ISO that upload started. **What to do:** Use the ISO in `iso_id`, or delete it and upload again. Default `detail`: “The project already has a custom ISO.” ### `iso_mounted` 409 ISO mounted **When:** The ISO you are deleting is mounted on a server. **What to do:** Unmount it from every server first. Default `detail`: “The ISO is mounted on a server.” ### `name_taken` 409 Name taken **When:** Another firewall group in the project has this name. `group_id` names it when it can be read back. **What to do:** Choose another name. If you were retrying a create whose answer you lost, the group with this name is the one you created: it is in `group_id`, or in the project's list of groups. Default `detail`: “The name is already in use.” ### `group_in_use` 409 Group in use **When:** The firewall group you are deleting is still attached to servers. `servers` lists their ids. **What to do:** Detach the group from those servers first. Deleting it would change what they allow. Default `detail`: “The firewall group is still attached to servers.” ### `key_exists` 409 Key exists **When:** The public key is already in the project, whatever its comment. `key_id` names it. **What to do:** Use the existing key. If you were retrying a create whose answer you lost, it is the one you added. Default `detail`: “This public key is already in the project.” ### `rule_exists` 409 Rule exists **When:** The firewall group already has a rule that matches the same traffic, however it is spelled. `rule_id` names it when it can be read back. **What to do:** Use the existing rule: it is in `rule_id`, or in the group's list of rules. If you were retrying a create whose answer you lost, it is the one you added. Default `detail`: “An identical rule is already in the group.” ### `idempotency_key_reused` 409 Idempotency key reused **When:** The `Idempotency-Key` was already used in this project for a request with another method, path or body. **What to do:** Use a new key for every new request, and the same key only to repeat the same request. Default `detail`: “This Idempotency-Key was already used for a different request.” ## 415 Unsupported Media Type ### `unsupported_media_type` 415 Unsupported media type **When:** The request body is not JSON. **What to do:** Send the body as JSON with `Content-Type: application/json`. Default `detail`: “Send the request body as application/json.” ## 429 Too Many Requests ### `rate_limited` 429 Rate limited **When:** The key, or your address, has used its budget for now. `Retry-After` says how many seconds to wait. **What to do:** Wait for `Retry-After`, then continue at a slower pace. See [Rate limits](https://docs.hosthatch.com/api/rate-limits/index.md), which also covers the plain HTML 429 that can come before the API's own. Default `detail`: “Too many requests. Wait for the time given in Retry-After.” ## 500 Internal Server Error ### `internal_error` 500 Internal error **When:** Something went wrong on our side. **What to do:** Try again after a short wait. Repeat an action or an order with the same `Idempotency-Key`, so it can't happen twice. Before you repeat any other write, check whether it went through, unless its page says it is safe to send again. Contact support with the time and the path if it keeps happening. Default `detail`: “Something went wrong on our side. Please try again.” ## 502 Bad Gateway ### `upstream_error` 502 Upstream error **When:** A system behind the API refused the request or did not answer. When an action or order was already recorded, the response carries its id in `action_id` or `order_id`, because the operation may have happened. **What to do:** With `action_id` or `order_id`, poll that action or order instead of sending the request again. After an ISO upload, list the project's ISOs, or send the upload again: if it had started, the answer is `409 iso_limit_reached` with its id. Otherwise try again after a short wait. Default `detail`: “The request could not be completed. Please try again.” ## 503 Service Unavailable ### `node_maintenance` 503 Node maintenance **When:** The host the server runs on is in maintenance. `Retry-After` is 300 seconds. **What to do:** Try again after `Retry-After`. Default `detail`: “The server's host is in maintenance. Please try again later.” ### `reverse_dns_unavailable` 503 Reverse DNS unavailable **When:** The reverse DNS service is unavailable. `Retry-After` is 300 seconds. **What to do:** Try again after `Retry-After`. Default `detail`: “Reverse DNS is unavailable. Please try again later.” ### `upstream_unavailable` 503 Upstream unavailable **When:** A service behind the API, such as billing, is unavailable for now. `Retry-After` says when to try again. **What to do:** Try again after `Retry-After`. Default `detail`: “A service behind the API is unavailable right now.” ## Action errors An action that fails reports its own codes in `error.code`: `failed`, `outcome_unknown`, `task_result_unavailable`, `timeout` and `resource_moved`. See [Actions](https://docs.hosthatch.com/api/actions/#error-codes). # Rate limits Each API key has its own budget of requests per minute. Going over it answers `429 rate_limited`, with a `Retry-After` header that says how many seconds to wait. ## The limits | Requests | Limit | Counted per | | ------------------------------------------------- | ---------------- | ----------- | | Reads: `GET` | 300 a minute | API key | | Writes: every other method | 60 a minute | API key | | Orders: new servers, upgrades and extra bandwidth | 10 a minute | API key | | Requests without a key | 30 a minute | IP address | | Failed key checks | 20 in 10 minutes | IP address | - Every request counts, including those that are refused. - Two keys have two budgets, even when they belong to the same person. - After 20 failed key checks from an address, every key sent from it is refused for the rest of the 10 minutes, a valid one too. See [Failed attempts](https://docs.hosthatch.com/api/authentication/#failed-attempts). ``` { "type": "https://docs.hosthatch.com/api/errors#rate_limited", "title": "Rate limited", "status": 429, "detail": "Request was throttled. Expected available in 36 seconds.", "code": "rate_limited", "instance": "/api/v1/me/" } ``` ## The limit in front of the API 60 requests a minute per IP address A rule at HostHatch's network edge answers before the API's own limits. More than 60 requests a minute from one IP address to the API get an HTML `429` page, without a `Retry-After` header and without problem details. The address is then refused for about a minute. It counts all requests from the address together, whatever key they carry, so two scripts behind one address share it. Plan for **60 requests a minute per IP address**, and treat a `429` that isn't `application/problem+json` as "wait 60 seconds". ## Stay under the limits - Poll [actions](https://docs.hosthatch.com/api/actions/index.md) and [orders](https://docs.hosthatch.com/api/orders-and-payment/index.md) every few seconds, not in a tight loop, and less often as time passes. - Read one resource rather than its whole list when you only need one. - Give each script or agent its own key, so one busy client doesn't spend another's budget. - On a `429`, wait for `Retry-After`, or 60 seconds when there is none, then go on at a slower pace. Add a little random delay, so that clients don't all retry at the same moment. # API reference The document itself is at [`https://docs.hosthatch.com/api/openapi.json`](https://docs.hosthatch.com/api/openapi.json), for your own tools and agents. Each endpoint below has examples for `curl`, Python, JavaScript, Go and more: choose a language at the top of the example, and run it from your own machine with your key. Loading the reference… # Changelog ## Versioning The version is in the path: `/api/v1`. Changes within v1 are additive, so code written against v1 keeps working: - new endpoints; - new optional fields in requests, and new fields in responses; - new values in enumerations, such as a new action type, order status or error code. Write clients that ignore fields they don't know, and treat an unknown value by what they can tell from the rest: an unknown error `code` by its HTTP status, for example. A breaking change, such as removing or renaming a field or changing what one means, comes as a new version at `/api/v2/`, announced here. ## 1.0.0 The first version of the HostHatch API, at `https://console.hosthatch.com/api/v1`. - [API keys](https://docs.hosthatch.com/api/authentication/index.md) sent as Bearer tokens, with names, expiry, read-only keys and revocation on a password change, and [`GET /me/`](https://docs.hosthatch.com/api/authentication/#get-the-current-user). - [Projects](https://docs.hosthatch.com/api/projects-and-roles/index.md): list and read the projects you belong to, with your role in each, member, admin or owner. - [Servers](https://docs.hosthatch.com/api/servers/index.md): list, detail, rename, reverse DNS and cancellation. - [Actions](https://docs.hosthatch.com/api/actions/index.md) for power, reboot, reinstall, root password reset, IPv6 and private networking, with one exclusive action per server and `Idempotency-Key` on every start. - [Orders](https://docs.hosthatch.com/api/orders-and-payment/index.md) for new servers, upgrades and extra bandwidth, from the catalogue of images, locations and plans, paid from account credit or in the console. - [Snapshots](https://docs.hosthatch.com/api/snapshots/index.md) and [ISOs](https://docs.hosthatch.com/api/isos/index.md): take, restore and delete snapshots, upload a custom ISO, and mount ISOs on servers. - [Firewalls](https://docs.hosthatch.com/api/firewalls/index.md): groups, rules, and attaching groups to servers. - [SSH keys](https://docs.hosthatch.com/api/ssh-keys/index.md), with fingerprints and duplicate detection. - [Problem details errors](https://docs.hosthatch.com/api/errors/index.md) with stable codes, [rate limits](https://docs.hosthatch.com/api/rate-limits/index.md) per key, and paginated lists. # Legacy API # Legacy API This is the legacy API This page documents the Peregrine API at `https://cloud.hosthatch.com`. It stays supported for existing customers, and nothing here changes for them. New integrations should use the [HostHatch API](https://docs.hosthatch.com/api/index.md) instead. Info The API is currently limited to a select few users. ## Authorization If you've been granted access to the API, you may generate a token from your [control panel](https://cloud.hosthatch.com/settings/tokens). You must then attach your token as the `Authorization: Bearer {token}` header with every request. ______________________________________________________________________ ## Requests Every request requires the `Accept: application/json` & `Authorization: Bearer {token}` headers. ``` curl --request GET \ --url 'https://cloud.hosthatch.com/api/v1/products' \ --header 'Accept: application/json' \ --header 'Authorization: Bearer {token}' ``` ______________________________________________________________________ ## Responses | Attribute | Type | Description | | --------- | ------ | ----------------------------------------------------------------------------------- | | `result` | string | A successful response will always have the value of `success` | | `data` | mixed | The data attribute depends on the endpoint, refer to their individual documentation | ``` { "result": "success", "data": null } ``` The value of the message attribute may be adjusted and should not be relied upon, it would be recommended to rely on the status code instead. Validation errors will always be sent with the `422` status code. | Attribute | Type | Description | | --------- | ------------------- | ------------------------------------------------------------------------------------------------- | | `result` | string | A failed response will always have the value of `error` | | `message` | string | A brief explaination of what went wrong | | `errors` | array\ | Any validation errors will appear here, and may be omitted entirely if no validation errors exist | ``` { "result": "error", "message": "Access forbidden" } ``` ``` { "result": "error", "message": "Validation failed", "errors": [ "The product field is required.", "The image field is required." ] } ``` ______________________________________________________________________ ## Endpoints ### GET /api/v1/products Retrieve a list of all the products we offer through our API. This may or may not vary from the products listed on our website and/or control panel. ``` curl --request GET \ --url 'https://cloud.hosthatch.com/api/v1/products' \ --header 'Accept: application/json' \ --header 'Authorization: Bearer {token}' ``` Optionally, you may append the `?prices` query parameter to include pricing information for the product and it's various features. ``` curl --request GET \ --url 'https://cloud.hosthatch.com/api/v1/products?prices' \ --header 'Accept: application/json' \ --header 'Authorization: Bearer {token}' ``` | Attribute | Type | Description | | --------------------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `_id` | string | The product identifier, used for creating or upgrading servers | | `name` | string | The human readable product name | | `description` | string | The product description | | `options.locations` | object\ | An object of locations where the product is available. The key is the location identifier (e.g. `AMS` or `LON`) and the value is a representation of the current stock status | | `options.images` | array\ | An array of available images. | | `options.features` | array\ | An array of available optional features objects | | `options.features.*._id` | string | The identifier of the optional feature | | `options.features.*.name` | string | The human readable name of the optional feature | | `options.features.*.values` | array\ | An array containing all of the available optional feature values | | `options.billing_cycles` | array\ | An array of all the available billing cycles | ``` { "result": "success", "data": { "products": [ { "_id": "nvme-2gb", "name": "NVMe 2 GB", "description": "1 AMD EPYC Milan core (fair share)\n2 GB DDR4 RAM\n10 GB NVMe Storage\n1 TB bandwidth (500GB in HKG, TOK, SYD, SGP)", "options": { "locations": { "AMS": true, "STO": false, "LON": true }, "images": [ "almalinux8", "ubuntu22", "centos7", "centosstream8", "debian11", "debian10", "ubuntu20", "ubuntu18" ], "features": [ { "_id": "ipv6", "name": "IPv6", "values": [ true, false ] }, { "_id": "additional-ipv4", "name": "Additional IPv4", "values": [ "0", "1", "2", "3", "4" ] } ], "billing_cycles": [ "monthly", "quarterly" ] } } ] } } ``` | Attribute | Type | Description | | -------------------------------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `_id` | string | The product identifier, used for creating or upgrading servers | | `name` | string | The human readable product name | | `description` | string | The product description | | `options.locations` | object\ | An object of locations where the product is available. The key is the location identifier (e.g. `AMS` or `LON`) and the value is a representation of the current stock status | | `options.images` | array\ | An array of available images. | | `options.features` | array\ | An array of available optional features objects | | `options.features.*._id` | string | The identifier of the optional feature | | `options.features.*.name` | string | The human readable name of the optional feature | | `options.features.*.values` | object\ | An object containing all the available values of the optional feature. Keyed by their identifiers. | | `options.features.*.values.*._id` | mixed | The identifier of the optional feature value | | `options.features.*.values.*.billing_cycles` | object | The billing cycles the optional feature value is available for. Please refer to the `options.billing_cycles` attribute below for further information about the structure | | `options.billing_cycles` | object\ | An object of all available billing cycles. Keyed by their identifiers. | | `options.billing_cycles.*._id` | string | The identifier of the billing cycle | | `options.billing_cycles.*.price` | float | The recurring price of the billing cycle | | `options.billing_cycles.*.setupfee` | float | The initial setup fee of the billing cycle | | `options.billing_cycles.*.currency` | string | The currency in which the billing cycle's prices are displayed in | ``` { "result": "success", "data": { "products": [ { "_id": "nvme-2gb", "name": "NVMe 2 GB", "description": "1 AMD EPYC Milan core (fair share)\n2 GB DDR4 RAM\n10 GB NVMe Storage\n1 TB bandwidth (500GB in HKG, TOK, SYD, SGP)", "options": { "locations": { "AMS": true, "STO": false, "LON": true }, "images": [ "almalinux8", "ubuntu22", "centos7", "centosstream8", "debian11", "debian10", "ubuntu20", "ubuntu18" ], "features": [ { "_id": "ipv6", "name": "IPv6", "values": { "true": { "_id": true, "billing_cycles": { "monthly": { "price": 0, "setupfee": 0, "currency": "USD" }, "quarterly": { "price": 0, "setupfee": 0, "currency": "USD" } } }, "false": { "_id": false } } }, { "_id": "additional-ipv4", "name": "Additional IPv4", "values": [ { "_id": "0", "billing_cycles": { "monthly": { "price": 0, "setupfee": 0, "currency": "USD" }, "quarterly": { "price": 0, "setupfee": 0, "currency": "USD" } } }, { "_id": "1", "billing_cycles": { "monthly": { "price": 2, "setupfee": 0, "currency": "USD" }, "quarterly": { "price": 6, "setupfee": 0, "currency": "USD" } } }, { "_id": "2", "billing_cycles": { "monthly": { "price": 4, "setupfee": 0, "currency": "USD" }, "quarterly": { "price": 12, "setupfee": 0, "currency": "USD" } } }, { "_id": "3", "billing_cycles": { "monthly": { "price": 6, "setupfee": 0, "currency": "USD" }, "quarterly": { "price": 18, "setupfee": 0, "currency": "USD" } } }, { "_id": "4", "billing_cycles": { "monthly": { "price": 8, "setupfee": 0, "currency": "USD" }, "quarterly": { "price": 24, "setupfee": 0, "currency": "USD" } } } ] } ], "billing_cycles": { "monthly": { "_id": "monthly", "price": 16, "setupfee": 4, "currency": "USD" }, "quarterly": { "_id": "quarterly", "price": 48, "setupfee": 12, "currency": "USD" } } } } ] } } ``` ______________________________________________________________________ ### POST /api/v1/deploy Deploy a new server. | Attribute | Type | Required | Value | | ------------------- | ---------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `product` | string | true | The product identifier of your choice. See `GET /api/v1/products` for more information | | `location` | string | true | The location identifier of your choice. See `GET /api/v1/products` for more information | | `billing` | string | true | The billing cycle identifier of your choice. See `GET /api/v1/products` for more information | | `image` | string | true | The image identifier of your choice. See `GET /api/v1/products` for more information | | `hostname` | string | true | The hostname of your new server | | `features` | object\ | false | An object containing the optional feature identifiers as the object key and their respective value as the object value . See `GET /api/v1/products` for more information | | `features.ssh_keys` | array | false | An array containing the optional and identifiers of your choice . See `GET /api/v1/keys` for more information | ``` curl --request POST \ --url https://cloud.hosthatch.com/api/v1/deploy \ --header 'Accept: application/json' \ --header 'Authorization: Bearer {token}' \ --header 'Content-Type: application/json' \ --data '{ "product": "nvme-2gb", "location": "AMS", "billing": "monthly", "image": "almalinux8", "hostname": "my-new-server", "features": { "ipv6": true, "additional-ipv4": 2, "ssh_keys": [118, 119] } }' ``` | Attribute | Type | Description | | ------------------------------ | ------ | ------------------------------------------------------------------------------------------------------------------------------ | | `invoice_id` | int | The identifier of the generated invoice | | `invoice_url` | int | The control panel link to the generated invoice | | `server_id` | int | The identifier of the newly created server | | `server_url` | int | The control panel link to the newly created server | | `requires_payment` | bool | Indicates whether the user manually has to complete the payment because their credit balance didn't cover the entire cost | | `outstanding_balance.price` | float | The outstanding invoice cost. Only exists if the `requires_payment` attribute is `true` | | `outstanding_balance.currency` | string | The currency in which the outstanding balance price is displayed in. Only exists if the `requires_payment` attribute is `true` | **Covered entirely by your account's credit balance** (status code 201)\ If the cost was entirely covered by your account's credit balance, the response will contain the `requires_payment` attribute with a `false` value. You don't need to do anything else from this point and the server creation process should have begun. ``` { "result": "success", "data": { "invoice_id": 445566, "invoice_url": "https://cloud.hosthatch.com/invoices/445566", "server_id": 112233, "server_url": "https://cloud.hosthatch.com/servers/112233", "requires_payment": false } } ``` **Not covered by the account's credit balance** (status code 200)\ If the cost wasn't entirely covered by your account's credit balance, the response will contain the `requires_payment` attribute with a `true` value. This means that you will manually have to pay the outstanding balance of your server invoice by logging into the control panel before the creation process can proceed. ``` { "result": "success", "data": { "invoice_id": 445566, "invoice_url": "https://cloud.hosthatch.com/invoices/445566", "server_id": 112233, "server_url": "https://cloud.hosthatch.com/servers/112233", "requires_payment": true, "outstanding_balance": { "price": 10, "currency": "USD" } } } ``` ______________________________________________________________________ ### GET /api/v1/servers Retrieve all of your active servers. ``` curl --request GET \ --url 'https://cloud.hosthatch.com/api/v1/servers' \ --header 'Accept: application/json' \ --header 'Authorization: Bearer {token}' ``` | Attribute | Type | Description | | --------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `servers` | array\ | An array of server objects. Please refer to the `GET /api/v1/server/{server}` endpoint documentation for more information regarding the structure of each server object. | ``` { "result": "success", "data": { "servers": [ { "id": 112233, "hostname": "example-server", "state": "Active", "created": "2023-05-09", "product": { "_id": "nvme-2gb", "name": "NVMe 2 GB", "location": "AMS", "image": "Alma Linux 8 64-bit", "available_upgrades": [ "nvme-4gb", "nvme-8gb", "nvme-16gb" ] }, "network": { "primary_ipaddress": "192.0.2.10" }, "billing": { "billing_cycle": "monthly", "next_due": "2023-06-09", "initial_cost": 20, "recurring_cost": 16, "currency": "USD" } } ] } } ``` ______________________________________________________________________ ### GET /api/v1/servers/{server} Retrieve one of your active servers. ``` curl --request GET \ --url 'https://cloud.hosthatch.com/api/v1/servers/{server}' \ --header 'Accept: application/json' \ --header 'Authorization: Bearer {token}' ``` | Attribute | Type | Description | | ----------------------------------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | `server.id` | int | The identifier of the server | | `server.hostname` | string | The hostname of the server | | `server.state` | string | The current state of the server (e.g. `Active` or `Suspended`). Not to be confused with the status (e.g. `online` or `offline`) of the server | | `server.created` | string | The creation date of the server in the `YYYY-MM-DD` format | | `server.product._id` | string | The identifier of the product | | `server.product.name` | string | The human readable name of the product | | `server.product.location` | string | The location identifier | | `server.product.image` | string | The server's installed image | | `server.product.available_upgrades` | array\ | An array of available upgrade product identifiers | | `server.network.primary_ipaddress` | string | The primary IP address of the server | | `server.billing.billing_cycle` | string | The billing cycle identifier | | `server.billing.next_due` | string | The date of the next due invoice date in the `YYYY-MM-DD` format | | `server.billing.initial_cost` | float | The initial setup fee that was paid for the server | | `server.billing.recurring_cost` | float | The recurring cost according to the server's billing cycle | | `server.billing.currency` | string | The currency in which the `server.billing.initial_cost` and `server.billing.recurring_cost` are displayed in | ``` { "result": "success", "data": { "server": { "id": 112233, "hostname": "example-server", "state": "Active", "created": "2023-05-09", "product": { "_id": "nvme-2gb", "name": "NVMe 2 GB", "location": "AMS", "image": "Alma Linux 8 64-bit", "available_upgrades": [ "nvme-4gb", "nvme-8gb", "nvme-16gb" ] }, "network": { "primary_ipaddress": "192.0.2.10" }, "billing": { "billing_cycle": "monthly", "next_due": "2023-06-09", "initial_cost": 20, "recurring_cost": 16, "currency": "USD" } } } } ``` ______________________________________________________________________ ### GET /api/v1/servers/{server}/status Retrieve the status of one of your active servers. ``` curl --request GET \ --url 'https://cloud.hosthatch.com/api/v1/servers/{server}/status' \ --header 'Accept: application/json' \ --header 'Authorization: Bearer {token}' ``` | Attribute | Type | Description | | --------- | ------ | ----------------------------------------------------- | | `status` | string | The status (e.g. `online` or `offline`) of the server | ``` { "result": "success", "data": { "status": "online" } } ``` ______________________________________________________________________ ### POST /api/v1/servers/{server}/boot Attempt to boot up one of your active servers. Note Please note that the response from this endpoint is indicative of the request to boot your server being successful or not, and not the actual boot up process. ``` curl --request POST \ --url 'https://cloud.hosthatch.com/api/v1/servers/{server}/boot' \ --header 'Accept: application/json' \ --header 'Authorization: Bearer {token}' ``` ``` { "result": "success", "data": null } ``` ______________________________________________________________________ ### POST /api/v1/servers/{server}/shutdown Attempt to shutdown one of your active servers. Note Please note that the response from this endpoint is indicative of the request to shutdown your server being successful or not, and not the actual shutting down process. ``` curl --request POST \ --url 'https://cloud.hosthatch.com/api/v1/servers/{server}/shutdown' \ --header 'Accept: application/json' \ --header 'Authorization: Bearer {token}' ``` ``` { "result": "success", "data": null } ``` ______________________________________________________________________ ### POST /api/v1/servers/{server}/reboot Attempt to reboot one of your active servers. Note Please note that the response from this endpoint is indicative of the request to reboot your server being successful or not, and not the actual rebooting process. ``` curl --request POST \ --url 'https://cloud.hosthatch.com/api/v1/servers/{server}/reboot' \ --header 'Accept: application/json' \ --header 'Authorization: Bearer {token}' ``` ``` { "result": "success", "data": null } ``` ______________________________________________________________________ ### PUT /api/v1/servers/{server}/upgrade Generate an invoice for a server upgrade. Note Please note that when this request is sent, we will automatically attempt to charge the upgrade cost from your account's credit balance. If there isn't enough credit to cover the invoice, you will have to manually pay the remaining invoice balance from our control panel. | Attribute | Type | Required | Value | | -------------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `product` | string | true | The identifier of a product. These can be found as the `_id` value from each available product in the `GET /api/v1/products` endpoint response | | `upgrade_disk` | bool | false | Whether to upgrade the disk or not. Defaults to `false` | ``` curl --request PUT \ --url https://cloud.hosthatch.com/api/v1/servers/{server}/upgrade \ --header 'Accept: application/json' \ --header 'Authorization: Bearer {token}' \ --header 'Content-Type: application/json' \ --data '{ "product": "nvme-4gb", "upgrade_disk": false }' ``` | Attribute | Type | Description | | ------------------------------ | ------ | ------------------------------------------------------------------------------------------------------------------------------ | | `invoice_id` | int | The identifier of the generated invoice | | `invoice_url` | int | The control panel link to the generated invoice | | `requires_payment` | bool | Indicates whether the user manually has to complete the payment because their credit balance didn't cover the entire cost | | `outstanding_balance.price` | float | The outstanding invoice cost. Only exists if the `requires_payment` attribute is `true` | | `outstanding_balance.currency` | string | The currency in which the outstanding balance price is displayed in. Only exists if the `requires_payment` attribute is `true` | **Covered entirely by your account's credit balance** (status code 201)\ If the upgrade cost was entirely covered by your account's credit balance, the response will contain the `requires_payment` attribute with a `false` value. You don't need to do anything else from this point and the upgrade process should have begun. ``` { "result": "success", "data": { "invoice_id": 112233, "invoice_url": "https://cloud.hosthatch.com/invoices/112233", "requires_payment": false } } ``` **Not covered by the account's credit balance** (status code 200)\ If the upgrade cost wasn't entirely covered by your account's credit balance, the response will contain the `requires_payment` attribute with a `true` value. This means that you will manually have to pay the outstanding balance of your upgrade invoice by logging into the control panel before the upgrade process can proceed. ``` { "result": "success", "data": { "invoice_id": 112233, "invoice_url": "https://cloud.hosthatch.com/invoices/112233", "requires_payment": true, "outstanding_balance": { "price": 10, "currency": "USD" } } } ``` ______________________________________________________________________ ### PUT /api/v1/servers/{server}/cancel Generate a cancel request for a server. | Attribute | Type | Required | Value | | ----------- | ------ | -------- | --------------------------------------------------------------------------------------------------------------------- | | `reason` | string | true | A short message explaining why you want to cancel the server | | `immediate` | bool | false | Set to `true` to cancel immediately or `false` to cancel at the end of the current billing cycle. Defaults to `false` | ``` curl --request POST \ --url https://cloud.hosthatch.com/api/v1/servers/{server}/cancel \ --header 'Accept: application/json' \ --header 'Authorization: Bearer {token}' \ --header 'Content-Type: application/json' \ --data '{ "reason": "I no longer need this service", "immediate": true }' ``` ``` { "result": "success", "data": null } ``` ### GET /api/v1/keys Retrieve all of your keys. ``` curl --request GET \ --url 'https://cloud.hosthatch.com/api/v1/keys' \ --header 'Accept: application/json' \ --header 'Authorization: Bearer {token}' ``` | Attribute | Type | Description | | --------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `keys` | array\ | An array of key objects. Please refer to the `GET /api/v1/keys/{key}` endpoint documentation for more information regarding the structure of each key object. | ``` { "result": "success", "data": { "keys": [ { "id": 112233, "type": "sshkey", "description": "Key description", "value": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILisU08Fudla/eq9wMjIfu4lLUQ7OCa6TWzlxuoPtzMK user@example.com" } ] } } ``` ______________________________________________________________________ ### GET /api/v1/keys/{key} Retrieve one of your active key. ``` curl --request GET \ --url 'https://cloud.hosthatch.com/api/v1/keys/{key}' \ --header 'Accept: application/json' \ --header 'Authorization: Bearer {token}' ``` | Attribute | Type | Description | | ----------------- | ------ | ------------------------------------ | | `key.id` | int | The identifier of the key | | `key.type` | string | The type of the key (e.g. `sshkey`). | | `key.description` | string | The decription of the key | | `key.value` | string | The value of the key | ``` { "result": "success", "data": { "key": { "id": 112233, "type": "sshkey", "description": "Your key description", "value": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILisU08Fudla/eq9wMjIfu4lLUQ7OCa6TWzlxuoPtzMK user@example.com" } } } ``` ______________________________________________________________________ ### POST /api/v1/keys Create a new key. | Attribute | Type | Required | Value | | ------------- | ------ | -------- | ------------------------------------ | | `description` | string | true | The description of the key | | `type` | string | true | The type of the key (e.g. `sshkey`). | | `value` | string | true | The value of the key | ``` curl --request POST \ --url https://cloud.hosthatch.com/api/v1/keys \ --header 'Accept: application/json' \ --header 'Authorization: Bearer {token}' \ --header 'Content-Type: application/json' \ --data '{ "description": "new key", "type": "sshkey", "value": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILisU08Fudla/eq9wMjIfu4lLUQ7OCa6TWzlxuoPtzMK" }' ``` | Attribute | Type | Description | | ------------- | ------ | ---------------------------------- | | `id` | int | The identifier of created key | | `type` | string | The type of the created key | | `description` | string | The description of the created key | | `value` | string | The value of the created key | ``` { "result": "success", "data": { "key": { "id": 1234, "description": "new key description", "type": "sshkey", "value": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILisU08Fudla/eq9wMjIfu4lLUQ7OCa6TWzlxuoPtzMK" } } } ``` ______________________________________________________________________ ### DELETE /api/v1/keys/{key} Delete created key. Note Please note that the key you are not using some active server. ``` curl --request DELETE \ --url 'https://cloud.hosthatch.com/api/v1/keys/{key}' \ --header 'Accept: application/json' \ --header 'Authorization: Bearer {token}' ``` ``` { "result": "success", "data": null } ``` ______________________________________________________________________ # Servers and networking # Datacenters & Features ## Matrix | Region | Compute VM | Storage VM | Public Port | Private Port | IPv6 | Datacenter | | ----------- | ---------- | ---------- | ----------- | ------------ | ---- | -------------------- | | Amsterdam | | | 100 GbE | 100 GbE | | Digital Realty AMS18 | | Chicago | | | 40 GbE | 40 GbE | | CoreSite CH2 | | Hong Kong | | | 40 GbE | 40 GbE | | Equinix HK1 | | London | | | 40 GbE | 40 GbE | | Iron Mountain LON1 | | Los Angeles | | | 100 GbE | 100 GbE | | Digital Realty LAX10 | | New York | | | 100 GbE | 100 GbE | | Evocative EWR1 | | Oslo | | | 40 GbE | 40 GbE | | Blix BDC | | Seoul | | | 10 GbE | 100 GbE | | KINX Dogok | | Singapore | | | 40 GbE | 40 GbE | | Epsilon Global Hubs | | Stockholm | | | 40 GbE | 40 GbE | | Digital Realty STO4 | | Sydney | | | 40 GbE | 40 GbE | | Equinix SY4 | | Tokyo | | | 100 GbE | 100 GbE | | Equinix TY8 | | Vienna | | | 40 GbE | 40 GbE | | Digital Realty VIE2 | | Zurich | | | 40 GbE | 40 GbE | | Digital Realty ZUR1 | # Networking ## IPv4 Configuration We support both dynamic (DHCP) and static IPv4 configuration. It will automatically be set to DHCP if you install your server with one of our templates. However, you may change this to static configuration if you prefer to not rely on the DHCP server. The necessary details (gateway, netmask) are available in the control panel under the network management section. ## IPv6 Configuration We do not support automatic (SLAAC) or dynamic (DHCPv6) IPv6 assignment, it needs to be configured statically. If you choose to enable IPv6 when you first create your server, it will automatically be configured via cloud-init. However, manual configuration is needed if you choose to enable it after your server has been created, or if you install your OS via ISO. You will be assigned a public /64 IP block. You may configure any IP address in this block on your primary network interface `eth0`. Please see the configuration examples below. In the examples we use the `::1` IP address in the example block `2a0e:dc0:d0d0:c0de::/64`. Make sure to change this accordingly. Tip The entire /64 subnet is **routed** towards your VM, so it's not necessary to configure additional addresses on the interface in order to receive traffic to them. **Applies to:** 20.04, 22.04, 24.04 and 26.04 — all configured through netplan. 1. Identify the interface name: `ip -o link | grep 00:20` 1. Create a new netplan file for the IPv6 config. Replace `2a0e:dc0:d0d0:c0de::1/64` with your unique IPv6 and change `eth0` to your interface's name, if different. /etc/netplan/90-ipv6.yaml ``` network: version: 2 ethernets: eth0: addresses: - 2a0e:dc0:d0d0:c0de::1/64 routes: - to: default via: fe80::1 ``` 1. Tighten the file permissions — from 24.04 on, netplan warns about world-readable configuration: `sudo chmod 600 /etc/netplan/90-ipv6.yaml` 1. Enable the new config: `sudo netplan apply` 1. Confirm it's configured: `ip -6 a` Version notes On 20.04, if `netplan apply` rejects the `routes:` block, replace it with `gateway6: fe80::1` — netplan releases older than 0.103 do not understand `to: default`. Releases before 18.04 predate netplan altogether; use the Debian instructions instead. **Applies to:** 11, 12 and 13 — all configured through `ifupdown`. 1. Identify the interface name: `ip -o link | grep 00:20` 1. Create a new interfaces.d file for the IPv6 config. Replace `2a0e:dc0:d0d0:c0de::1/64` with your unique IPv6 and change `eth0` to your interface's name, if different. /etc/network/interfaces.d/90-ipv6 ``` iface eth0 inet6 static address 2a0e:dc0:d0d0:c0de::1/64 gateway fe80::1 ``` 1. Enable the new config: `sudo systemctl restart networking` 1. Confirm it's configured: `ip -6 a` Version notes Our Debian images are the official generic cloud images, which use `ifupdown` on every release — the existing IPv4 setup lives in `/etc/network/interfaces.d/50-cloud-init`. Leave that file alone and add yours alongside it. If your image has an `/etc/netplan/` directory instead, follow the Ubuntu tab. **Applies to:** EL8, EL9 and EL10. NetworkManager is the default on all three, and `nmcli` writes the right format for each — `ifcfg` files on EL8, keyfiles on EL9 and EL10. If you manage your interfaces with `network-scripts`, see *Using ifcfg files instead* below. 1. Identify the interface and the NetworkManager connection attached to it — on cloud images the connection is often named something other than the device: ``` ip -o link | grep 00:20 nmcli -g NAME,DEVICE connection show ``` 1. Add the IPv6 configuration to that connection. Replace `System eth0` with your connection name and `2a0e:dc0:d0d0:c0de::1/64` with your unique IPv6 block. ``` sudo nmcli connection modify "System eth0" \ ipv6.method manual \ ipv6.addresses 2a0e:dc0:d0d0:c0de::1/64 \ ipv6.gateway fe80::1 ``` 1. Enable the new configuration: `sudo nmcli connection up "System eth0"` 1. Confirm it's configured: `ip -6 a` Using ifcfg files instead Append the IPv6 settings to your existing interface file, keeping the current contents. The file name depends on your interface name. /etc/sysconfig/network-scripts/ifcfg-eth0 ``` IPV6INIT=yes IPV6ADDR=2a0e:dc0:d0d0:c0de::1/64 IPV6_DEFAULTGW=fe80::1%eth0 ``` How you apply it depends on the release: - **EL8** — `sudo systemctl restart network`. This needs the `network-scripts` package, which is not installed by default. - **EL9** — the `network` service no longer exists, but NetworkManager still reads `ifcfg` files. Apply with `sudo nmcli connection reload`, then `sudo nmcli connection up "System eth0"`. - **EL10** — `ifcfg` support has been dropped. Use the `nmcli` steps above. ## Private Networking Info If you enable private networking on your server after it has been setup, you must reboot it by clicking the reboot button in the control panel in order to attach the interface. You must have at least two active VMs in the same location in order to enable private networking If you enable Private Networking on your VM you will get an additional network interface that you can use for traffic between your own VMs in the same region. You cannot access any external networks through this interface. It is isolated with its own unique VLAN/VXLAN and you can assign any IPv4/IPv6 address to it. We do not offer any automated address assignment or DHCP on these networks. If you configure all your servers to the same subnet/broadcast domain you don't have to set any manual routes or gateway, e.g `192.168.10.1/24`, `192.168.10.2/24` etc. See below for configuration examples. If you add it upon creation of your VM, or if you reinstall your VM once it is enabled, the interface will automatically be named `eth1`. You can also identify it by its MAC address, it always starts with `00:22`. You can use jumbo frames (MTU 9000) on this interface for maximum throughput. Bandwidth is unlimited and not counted towards your server's monthly bandwidth usage. Following is a general configuration guide. Note that your configuration may be different, depending on your OS and its version. Warning Do not set a gateway on the private interface. It cannot reach anything outside the private network, so a default route via `eth1` will black-hole your server's internet traffic. **Applies to:** 20.04, 22.04, 24.04 and 26.04 — all configured through netplan. 1. Identify the interface name and MAC address: `ip -o link | grep 00:22` 1. Create a new netplan file with your `00:22` MAC address. Change `eth1` to your interface's name, if different. /etc/netplan/90-private.yaml ``` network: version: 2 ethernets: eth1: match: macaddress: "00:22:xx:xx:xx:xx" addresses: - 192.168.10.1/24 mtu: 9000 ``` 1. Tighten the file permissions: `sudo chmod 600 /etc/netplan/90-private.yaml` 1. Enable the new config: `sudo netplan apply` Version notes Releases before 18.04 predate netplan; use the Debian instructions instead. **Applies to:** 11, 12 and 13 — all configured through `ifupdown`. 1. Identify the interface name and MAC address: `ip -o link | grep 00:22` 1. Create a new interfaces.d file. Change `eth1` to your interface's name, if different. /etc/network/interfaces.d/90-private ``` auto eth1 iface eth1 inet static address 192.168.10.1/24 mtu 9000 ``` 1. Bring the interface up: `sudo ifup eth1` **Applies to:** EL8, EL9 and EL10. 1. Identify the interface name and MAC address: `ip -o link | grep 00:22` 1. Add a new NetworkManager connection for it. Change `eth1` to your interface's name, if different. ``` sudo nmcli connection add type ethernet con-name private ifname eth1 \ ipv4.method manual \ ipv4.addresses 192.168.10.1/24 \ ipv4.never-default yes \ ipv6.method disabled \ 802-3-ethernet.mtu 9000 ``` 1. Bring the connection up: `sudo nmcli connection up private` `ipv4.never-default yes` is what keeps the private interface off your default route, so leave it in. Using ifcfg files instead Create a new interface file. Note that it has no `GATEWAY` line — that is deliberate. /etc/sysconfig/network-scripts/ifcfg-eth1 ``` TYPE=Ethernet DEVICE=eth1 HWADDR=00:22:xx:xx:xx:xx BOOTPROTO=static ONBOOT=yes IPADDR=192.168.10.1 NETMASK=255.255.255.0 MTU=9000 ``` Apply it as above: `sudo systemctl restart network` on EL8, or `sudo nmcli connection reload` followed by `sudo nmcli connection up eth1` on EL9. EL10 has dropped `ifcfg` support — use `nmcli`. ## BGP Info Please note that this is under active development and may not be available in all locations yet. You should only try to configure this if you have been in contact with our support team to have it enabled. Setup a BGP session using a router software such as Bird using the details below. #### Our side - ASN: 65473 - IPv4: 169.254.169.254 - IPv6: 2605:4840:ffff::179 #### Your side - IPv4: The main IP address of your server - IPv6: The ::179 address of your server's /64 subnet - eBGP multihop 2 - Password provided by our support team We are advertising default route for both v4 and v6. Tip You can announce more specific subnets within your allowed prefixes up to /32 (IPv4) or /128 (IPv6). This means you can use IPs from one prefix on multiple servers in same location. Please make sure to also announce the full subnet (as per your LOA) from at least one server so that it gets announced out on the internet. ## Bandwidth Usage Both incoming and outgoing traffic is counted towards the bandwidth usage of your VM (traffic on the private network interface is not counted). You can see the limit in the control panel (cloud.hosthatch.com). The counter resets on the 1st day of each calendar month, no matter what your billing period is.