Skip to content

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
GET /projects/{slug}/images/ List images
GET /projects/{slug}/locations/ List locations
GET /projects/{slug}/pricing/ Get pricing
POST /projects/{slug}/servers/ Order a server
GET /projects/{slug}/servers/{id}/upgrade-options/ List upgrade options
POST /projects/{slug}/servers/{id}/upgrade/ Upgrade a server
POST /projects/{slug}/servers/{id}/bandwidth/ Buy extra bandwidth
GET /projects/{slug}/orders/ List orders
GET /projects/{slug}/orders/{id}/ Get an order

How ordering works

  1. Look up what you can order: products, images, locations and prices.
  2. Place the order, with an Idempotency-Key. The answer is 201 with the order.
  3. 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.
  4. 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 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, none is applied. The order waits in awaiting_payment, and invoice.url links to the invoice: pay it 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.

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, "private_network": false,
    "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.
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. User data is never stored here; has_user_data says whether there was some.
invoice object or null The invoice: id, status, total, currency, and url, where you can pay it.
warnings array Things that went wrong without stopping the order, such as a disk expansion that couldn't be scheduled.
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 Its invoice was cancelled.
failed The order was refused, and nothing was bought.
unknown Billing didn't answer when the order was placed, so whether it went through is being checked. Poll it: it moves on once that is settled. 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 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

The prices of extra addresses and extra bandwidth.

{
  "currency": "USD",
  "ipv4_additional": {"monthly": "2.00"},
  "ipv6": {"monthly": "0.00"},
  "extra_bandwidth": [{"size_gb": 500, "price": "2.00"}, {"size_gb": 1000, "price": "4.00"}]
}

ipv4_additional and ipv6 have a price per billing cycle. extra_bandwidth lists the sizes Buy extra bandwidth takes.

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,
    "private_network": false, "promo_code": null, "snapshot_id": null, "user_data": null
  }'
Field Type Description
hostname string Required. The same rules as renaming a server.
product_id integer Required. A product's id.
location string Required. A location code where the product is in stock.
billing_cycle string One of the product's billing_cycles.
image string An image the product offers. Send image or snapshot_id, not both.
snapshot_id integer A completed snapshot of the project to create the server from.
ssh_keys array of integers Ids of the project's SSH keys to install.
ipv4_additional integer How many IPv4 addresses to add to the first one.
ipv6 boolean Give the server an IPv6 subnet.
private_network boolean Attach it to the private network.
promo_code string or null A promotion code.
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, such as a location without stock, an image it doesn't take, or a key or snapshot that isn't the project's, answers 400 validation_error on that field. The answer is 201 with the order, of type server.

List upgrade options

GET /projects/{slug}/servers/{id}/upgrade-options/

Any member · Read-only keys: yes · Returns 200 UpgradeOption[]

The plans the server can be upgraded to.

{"product_id": 41, "name": "Cloud VPS 4x8", "prices": {"monthly": "24.00"}, "currency": "USD"}

Upgrade a server

POST /projects/{slug}/servers/{id}/upgrade/

Admin or owner · Read-only keys: no · Returns 201 Order

Move the server to a bigger plan. It keeps its billing cycle.

{"product_id": 41, "upgrade_disk": true}
Field Type Description
product_id integer Required. One of the server's upgrade options. Any other answers 400 validation_error.
upgrade_disk boolean Also grow the server's disk to the new plan's size. If that can't be scheduled, the order says so in warnings.

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 through the API applies the new plan.

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'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. 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, place a new order with a new key.

Errors

Status Code When
400 validation_error A field is invalid, or the product doesn't offer that choice.
400 feature_unavailable user_data in an order.
403 insufficient_role Ordering needs the admin or owner role.
403 read_only_key Ordering with a read-only key.
403 billing_account_required The project's owner has no billing account.
404 not_found No such order or server in the project.
409 idempotency_key_reused The key was used for another request.
409 project_deleting The project is being deleted.
429 rate_limited More than 10 orders a minute with one key.
502 upstream_error Billing failed or didn't answer. With an order_id, poll that order.
503 upstream_unavailable Billing is unavailable for now.