Skip to content

Order a VPS at a fixed monthly or yearly price

7 steps · Checked against the API code 3 November 2026

A guide does one task from start to finish. Every endpoint, with all its fields and cases, is in the API reference.

There are two ways to get a VPS through the API:

  • Order a VPS (this guide): a fixed price per month or year. You pay an invoice first. You choose the operating system and an SSH key in the order.
  • Deploy a Cloud VPS: billed by the hour, with no invoice up front. The account must be verified and have paid Hostup at least 2,000 SEK. You cannot choose the operating system or an SSH key at deploy.

You pick a plan, preview the price, place the order and pay its invoice. Hostup builds the server once the invoice is paid. Then you log in.

  • Your API key in $HOSTUP_API_KEY, as set in the Quickstart. It needs these scopes (Keys and scopes):
    • write:orders to place the order. It also lets the key read the order.
    • write:billing to pay the invoice. It also lets the key read the invoice and list your saved cards.
    • read:vm to find the new VPS, read its login and list your saved SSH keys.
  • A way to pay: a saved card on the account (List saved cards), account credit or a person who pays through a payment link.
  • uuidgen for the order key in step 3 and jq for the loop in step 6.

The amounts in the samples are examples. The real prices are in the catalog (step 1) and the preview (step 2).

List VPS plans returns every plan with its resources, prices and options. It also answers without a key.

GET /api/v2/products/vps
curl "https://cloud.hostup.se/api/v2/products/vps" \
-H "Authorization: Bearer $HOSTUP_API_KEY"
Response 200
{
"data": [
{
"slug": "vps-xs",
"name": "VPS XS",
"resources": {
"cpuCores": 2,
"memoryGb": 4,
"storageGb": 80
},
"available": true,
"reason": null,
"billingCycles": [
{ "billingCycle": "monthly", "amount": 200, "currencyCode": "SEK", "isPrimary": true, "…": "…" },
{ "billingCycle": "annually", "amount": 2000, "currencyCode": "SEK", "isPrimary": false, "…": "…" }
],
"configurableOptions": [
{
"key": "operatingSystem",
"label": "Operating system",
"type": "select",
"default": "ubuntu-24-04",
"choices": [
{
"value": "ubuntu-24-04",
"label": "Ubuntu 24.04",
"osTemplateId": "os_01hxa3b4c5d6e7f8g9h0j1k2m3"
}
]
}
],
"…": "…"
}
],
"hasMore": false,
"nextCursor": null
}

Pick a plan by its resources and its price in billingCycles[].amount. It must have available: true. When it is false, reason says why. Option prices are in configurableOptions[].pricing.

The order item uses other field names for the values you pick here:

In the catalogIn the order item
data[].slugproductSlug
billingCycles[].billingCyclebillingCycle
a choices[].value of the operatingSystem optionos
the key of any other optiona key in configurableOptions (what each option type takes)

customHostname names the server. Step 6 finds the server by this name. Use letters, digits, hyphens and dots. Keep it to 63 characters or fewer. An order without a name or with a longer one still goes through. The server then gets a generated name instead. Set the name once. Steps 2, 3 and 6 use it:

export VPS_NAME="app-01.example.com" # the host name you want

quantity orders up to 10 servers of the same plan. Give each one its own name in customHostnames. With customHostname and a quantity above 1, every server gets a numbered variant of that name.

List saved SSH keys returns the keys saved on the account.

GET /api/v2/ssh-keys
curl "https://cloud.hostup.se/api/v2/ssh-keys" \
-H "Authorization: Bearer $HOSTUP_API_KEY"
Response 200
{
"data": [
{
"id": "ssh_01hxa3b4c5d6e7f8g9h0j1k2m3",
"name": "deploy-key",
"fingerprint": "SHA256:8pQ3ExampleFingerprintForDocsOnly",
"…": "…"
}
],
"hasMore": false,
"nextCursor": null
}
export SSH_KEY_ID="ssh_…" # data[].id of your key above

Add "configurableOptions": { "sshKeyIds": ["'$SSH_KEY_ID'"] } to the item in steps 2 and 3, with the ID of one saved key. A key that belongs to another account is left out without an error. The server then uses password login.

Preview an order prices the items without creating an order or an invoice. Send the items you are going to order. Leave out clientData. With your key the preview then uses the account’s country, VAT number and currency, as the order will.

POST /api/v2/orders/preview
curl -X POST "https://cloud.hostup.se/api/v2/orders/preview" \
-H "Authorization: Bearer $HOSTUP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"type": "vps",
"productSlug": "vps-xs",
"billingCycle": "monthly",
"os": "ubuntu-24-04",
"customHostname": "'$VPS_NAME'"
}
]
}'
Response 200
{
"items": [
{
"productSlug": "vps-xs",
"type": "vps",
"billingCycle": "monthly",
"quantity": 1,
"unitAmount": 200,
"setupAmount": 0,
"subtotal": 200,
"currencyCode": "SEK",
"…": "…"
}
],
"subtotal": 200,
"taxRateDecimal": 0.25,
"taxAmount": 50,
"total": 250,
"currencyCode": "SEK",
"reverseCharge": false,
"…": "…"
}

total is what the invoice will ask for, tax included. An organisation account in an EU country other than Sweden with a valid VAT number pays no VAT. reverseCharge is then true and taxAmount is 0.

Create an order takes the same items and an attemptKey. The key makes a repeat of this request safe: the same key never places a second order. Make a new random key for this order:

export ATTEMPT_KEY="$(uuidgen)"

Use a random value such as a UUID, never a counter. Keys are checked across all accounts, not only yours. A key that anyone sent in the last hour returns that earlier order. Nothing is ordered for you then.

paymentMethod is optional. Send card when you pay right away in step 5, whether by saved card, credit or payment link. The invoice e-mail then waits a short while (see What happens next).

POST /api/v2/orders
curl -X POST "https://cloud.hostup.se/api/v2/orders" \
-H "Authorization: Bearer $HOSTUP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"attemptKey": "'$ATTEMPT_KEY'",
"paymentMethod": "card",
"items": [
{
"type": "vps",
"productSlug": "vps-xs",
"billingCycle": "monthly",
"os": "ubuntu-24-04",
"customHostname": "'$VPS_NAME'"
}
]
}'
Response 201
{
"id": "ord_01hxa3b4c5d6e7f8g9h0j1k2m3",
"status": "pending",
"invoiceId": "inv_01hxa3b4c5d6e7f8g9h0j1k2m3",
"invoice": {
"id": "inv_01hxa3b4c5d6e7f8g9h0j1k2m3",
"amount": 250,
"currencyCode": "SEK",
"dueAt": "2026-11-03T00:00:00.000Z",
"status": "unpaid",
"…": "…"
},
"paymentStatus": {
"status": "unpaid",
"reason": "Invoice has not been paid yet."
},
"actions": {
"canCancel": { "allowed": true, "reason": null },
"…": "…"
},
"…": "…"
}
export ORDER_ID="ord_…" # id above
export INVOICE_ID="inv_…" # invoiceId above

The order creates one unpaid invoice. id is the order. invoiceId is the invoice you pay in step 5. The order is priced again when you place it. Compare invoice.amount with the preview total before you pay.

Cancel the order while actions.canCancel.allowed is true. That also cancels its unpaid invoice.

POST /api/v2/orders/{id}/actions/cancel
curl -X POST "https://cloud.hostup.se/api/v2/orders/$ORDER_ID/actions/cancel" \
-H "Authorization: Bearer $HOSTUP_API_KEY" \
-H "Content-Type: application/json"
Response 200
{
"id": "ord_01hxa3b4c5d6e7f8g9h0j1k2m3",
"status": "cancelled",
"invoice": {
"id": "inv_01hxa3b4c5d6e7f8g9h0j1k2m3",
"status": "cancelled",
"…": "…"
},
"…": "…"
}

invoiceLookupPending is then true: the invoice could not be read right away. Read the order again a moment later with Get an order.

Send the same request again with the same $ATTEMPT_KEY within one hour. You get the first order back with 200. No second order is placed. While the first request still runs, the repeat answers 504 TIMEOUT or 200 with "status": "processing". Send it again a little later. Sending a change twice has more.

Get an invoice shows whether it can be paid and how much credit it can take.

GET /api/v2/billing/invoices/{id}
curl "https://cloud.hostup.se/api/v2/billing/invoices/$INVOICE_ID" \
-H "Authorization: Bearer $HOSTUP_API_KEY"
Response 200
{
"id": "inv_01hxa3b4c5d6e7f8g9h0j1k2m3",
"status": "unpaid",
"totals": { "total": 250, "outstanding": 250, "…": "…" },
"actions": {
"canPay": { "allowed": true, "reason": null },
"…": "…"
},
"paymentMethods": {
"accountCredit": {
"available": true,
"requirements": {
"maximumAmount": {
"amount": 250,
"currencyCode": "SEK",
"comparison": "less_than_or_equal"
},
"…": "…"
},
"…": "…"
},
"…": "…"
},
"…": "…"
}

When canPay.allowed is false, reason says why, for example “Invoice is already paid.”

Pick one of the three ways. Each needs the invoice to be unpaid. Pay an invoice covers each way in more detail.

Pay an invoice with a saved card charges the card at once for the whole outstanding amount. Send {} for the account’s primary card. Send savedCardId with a card_… ID to use another saved card.

POST /api/v2/billing/invoices/{id}/actions/pay
curl -X POST "https://cloud.hostup.se/api/v2/billing/invoices/$INVOICE_ID/actions/pay" \
-H "Authorization: Bearer $HOSTUP_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'
Response 200
{
"id": "inv_01hxa3b4c5d6e7f8g9h0j1k2m3",
"status": "paid",
"totals": { "total": 250, "amountPaid": 250, "outstanding": 0, "…": "…" },
"…": "…"
}

If the call times out, read the invoice again (step 4) before you send it a second time. A second call never charges the card again.

Pay an invoice with account credit uses the credit at once. maximumAmount.amount from step 4 is the most credit this invoice can take: the smaller of your credit and what is left to pay. Send an amount above 0 and no higher than that. When the credit covers only part of the invoice, the rest stays unpaid. Pay the rest with the saved card or a payment link.

POST /api/v2/billing/invoices/{id}/actions/apply-credit
curl -X POST "https://cloud.hostup.se/api/v2/billing/invoices/$INVOICE_ID/actions/apply-credit" \
-H "Authorization: Bearer $HOSTUP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"amount": 250}'
Response 200
{
"id": "inv_01hxa3b4c5d6e7f8g9h0j1k2m3",
"status": "paid",
"totals": { "total": 250, "creditApplied": 250, "outstanding": 0, "…": "…" },
"…": "…"
}

Create an invoice payment link gives you a payment page to pass on to whoever pays. The page needs no login. It offers card, Swish or Alipay where the invoice allows them. Creating the link moves no money and sends no e-mail.

POST /api/v2/billing/invoices/{id}/actions/generate-payment-link
curl -X POST "https://cloud.hostup.se/api/v2/billing/invoices/$INVOICE_ID/actions/generate-payment-link" \
-H "Authorization: Bearer $HOSTUP_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'
Response 200
{
"paymentUrl": "https://cloud.hostup.se/billing/pay/550e8400-e29b-41d4-a716-446655440000",
"expiresAt": "2026-12-03T10:00:00.000Z",
"…": "…"
}

A new link is valid for 30 days. Asking again while it is valid gives you the same link. The order’s paymentStatus.status turns paid once the payer is done (Get an order).

The VPS is in List VPS instances from the moment you order, with serviceStatus pending. It stays pending until the invoice is paid. Then Hostup builds the server. It is ready when serviceStatus is active and primaryIp is set. Its id stays the same throughout.

GET /api/v2/vps
curl "https://cloud.hostup.se/api/v2/vps?name=$VPS_NAME" \
-H "Authorization: Bearer $HOSTUP_API_KEY"
Response 200
{
"data": [
{
"id": "vps_01hxa3b4c5d6e7f8g9h0j1k2m3",
"name": "app-01.example.com",
"primaryIp": "192.0.2.10",
"serviceStatus": "active",
"powerState": "running",
"…": "…"
}
],
"hasMore": false,
"nextCursor": null
}

The same as a loop, every 30 seconds:

GET /api/v2/vps
for attempt in $(seq 1 60); do
VPS=$(curl -s "https://cloud.hostup.se/api/v2/vps?name=$VPS_NAME" \
-H "Authorization: Bearer $HOSTUP_API_KEY" | jq '.data[0]')
echo "$VPS" | jq -r '[.id, .serviceStatus, .primaryIp] | @tsv'
[ "$(echo "$VPS" | jq -r '.serviceStatus')" = "active" ] && \
[ "$(echo "$VPS" | jq -r '.primaryIp')" != "null" ] && break
sleep 30
done
export VPS_ID="vps_…" # the id the loop printed

The loop stops after 60 tries, about 30 minutes. An empty line means no VPS has that name. Check $VPS_NAME against the customHostname you sent in step 3. If the invoice is paid and the server is still pending after the loop, contact Hostup support with the id.

Get VPS details and action gates has the login.

GET /api/v2/vps/{id}/details
curl "https://cloud.hostup.se/api/v2/vps/$VPS_ID/details" \
-H "Authorization: Bearer $HOSTUP_API_KEY"
Response 200
{
"id": "vps_01hxa3b4c5d6e7f8g9h0j1k2m3",
"ipv4": "192.0.2.10",
"powerState": "running",
"operatingSystem": {
"displayName": "Ubuntu 24.04",
"family": "ubuntu",
"version": "24.04",
"variant": null
},
"sshKeys": [],
"access": {
"username": "root",
"rootPassword": "example-root-password"
},
"…": "…"
}

Log in as access.username with the password in access.rootPassword. sshKeys lists the SSH keys installed on the server. Connect to the IP address:

  • Money. Placing the order creates the invoice. An unpaid order is never built. The invoice for a VPS order is due the same day. The exception is an organisation account that sends paymentMethod invoice, bankgiro or sepa: its invoice is due in 14 days.
  • The invoice e-mail. Hostup e-mails the unpaid invoice to the account holder. With paymentMethod card that e-mail waits a short while. If the invoice is paid by then, a private account gets no invoice e-mail. A company account gets the paid invoice.
  • The server e-mail. When the server is up, the account holder gets “Your server app-01.example.com is ready” in the account’s language. It holds the server name, operating system, IP address and user name (Administrator on Windows, root on the others). The password is never e-mailed. Step 7 reads it through the API. When the order included an SSH key, the e-mail says the key is already installed.

Next: Point a domain at your server

  • 400 invalid_request: a field the API does not know. It also comes back for an option value that cannot be ordered. errors[].pointer names the field. For an option, configurableOptionErrors gives min, max, step and closestOrderableValues. Pick one of those and preview again.
  • 422 product_not_orderable: productSlug is not a VPS plan. Use a slug from step 1.
  • 409 payment_method_invalid: the pay call found no saved card it could use. Add a card in the client area, pay with credit or send a payment link.
  • 400 payment_failed: the card was declined. detail says why. The invoice stays unpaid. Send a payment link so the payer can use another card.
  • 409 invoice_payment_in_progress: a pay call came while the first charge was still running. Read the invoice (step 4) to see how it ended.
  • 409 invoice_already_paid: the invoice is paid. Nothing was charged again.
  • 401 or 403: the key was refused. When a key is refused gives each cause and what to do.