Errors and retries
Checked against the API code 3 November 2026
When a call fails, the response says what went wrong in a form your code can act on. This page shows that format and which errors are worth retrying. It ends with how to retry without doing the same thing twice.
What an error looks like
Section titled “What an error looks like”Almost every error from a /api/v2/ endpoint is a Problem Details document (RFC 7807) with the content type application/problem+json. Errors in another shape lists the exceptions.
This list request asks for too many items per page:
curl "https://cloud.hostup.se/api/v2/vps?limit=500" \ -H "Authorization: Bearer $HOSTUP_API_KEY"{ "type": "https://developer.hostup.se/errors/invalid_pagination_limit", "title": "Validation failed", "status": 400, "detail": "Limit must be an integer between 1 and 100.", "instance": "/api/v2/vps", "code": "invalid_pagination_limit", "requestId": "9b2e4f61-3c8a-4d17-a5e0-7f6b1c2d3e4f", "timestamp": "2026-11-02T10:15:20.000Z", "errors": [ { "pointer": "/limit", "detail": "Limit must be an integer between 1 and 100.", "code": "invalid_pagination_limit" } ]}| Field | What it is |
|---|---|
code | The stable machine code. Branch on this. |
status | The HTTP status, repeated in the body. |
title | A short summary. |
detail | What went wrong, for a person to read. It is in English, with one exception under Codes with 429. The wording can change. Do not parse it. |
type | A link to the page for this code on this site. |
instance | The path you called. |
requestId | The ID of this request. Log it. |
timestamp | When the error happened, in UTC. |
errors | Mostly on validation errors, sometimes on a 409. One entry per field that failed, with pointer, detail and code. Some endpoints leave it out when the whole body is wrong. |
pointer shows which field failed. Most pointers look like /limit for a query parameter or /items/0/domainName for a field in the body. A few endpoints put /body/ or /query/ in front, as in /body/domain. Remove that part before you match the pointer to a field. When the whole body is wrong, pointer is / or empty.
More fields on some errors
Section titled “More fields on some errors”requiredScopes, onforbidden: the scopes the endpoint checks. When a key is refused says what to do.recovery: the next step, as a fixedactionvalue. The list below says what to do for each one.- A block such as
vps,domain,invoiceorexistingInvoice: the resource that is in the way.
What to do for each recovery.action:
retry_after: waitretryAfterSeconds. Then send the same request again.await_cooldown: the same action ran a short while ago. Read the resource first. The earlier action may already have done what you want. To run it again, waitretryAfterSecondsor untilretryAfterAt. When both are missing ornull, the end of the wait is not known.pay_outstanding_invoice: pay the invoice the error names. Then read the resource. Paying often completes the action, a renewal for example.resend_with_cancel_existing_invoice: addcancelExistingInvoice: trueto the body. Then send the request again. This cancels the unpaid invoice that is in the way.suggestedBodyshows the fields to send.
Other recovery.action values belong to a single endpoint. The detail says what to do. When suggestedBody is there, it holds the fields to send in the next request.
Branch on the code
Section titled “Branch on the code”Use status for the broad class and code for what to do. Two errors with the same status can need different handling. A 409 can be vps_not_ready or domain_conflict, for example. Error codes lists every code with the endpoints that return it.
Keep requestId with every error you log. Take it from the body. The X-Request-ID response header carries a different value today. When you contact support about a failed call, send the requestId and the time of the call. Support can then find your request.
Errors in another shape
Section titled “Errors in another shape”A few errors come from the layer in front of the endpoints, not from the endpoint itself. They use a simpler JSON shape with an upper-case code:
{ "error": "Unsupported Media Type", "message": "Content-Type must be one of: application/json, multipart/form-data", "code": "UNSUPPORTED_MEDIA_TYPE", "timestamp": "2026-11-02T10:15:20.000Z", "requestId": "5d0c7e2b-1f4a-4b8e-9c3d-2a6f8e1b7c90"}| Status | code | Cause |
|---|---|---|
400 | BAD_REQUEST | The body is not valid JSON or is larger than 10 MB. |
415 | UNSUPPORTED_MEDIA_TYPE | A POST, PUT or PATCH without Content-Type: application/json. Send the header also when there is no body. |
504 | TIMEOUT | We stopped waiting for the endpoint, after 30 seconds for most endpoints. The endpoint may still be running. It can still finish the action. Read the resource before you send a change again. |
Two errors have no JSON body at all:
| Status | Body | Cause |
|---|---|---|
404 | An HTML page | The path does not exist. Check it for typos. A path that exists with an unknown ID returns 404 not_found as Problem Details. |
405 | Plain text, such as POST method not allowed | The endpoint does not take this method. The Allow header lists the methods it takes. |
Check Content-Type before you parse an error body.
GET /api/jobs/{jobId} uses the upper-case shape for all its errors. Jobs lists them.
Which errors to retry
Section titled “Which errors to retry”A read (GET) is always safe to send again. A change is safe to send again only when you know the first attempt changed nothing.
| Status | Retry? | What to do |
|---|---|---|
429 | Depends on code | Most clear after a wait. Codes with 429 says what to do for each one. |
500 | Reads, yes. Changes, check first. | Something failed on our side. Retry a read a few times with a growing pause. If it keeps failing, contact support with the requestId. Today most endpoints also answer 500 internal_error when the key is over its rate limit. If 500s come in a row after a burst, wait five minutes. |
502, 503 | Reads, yes. Changes, check first. | A service the request depends on failed or is busy. Wait as Retry-After says when the header is there. Otherwise retry with a growing pause. |
504 | Check first | We stopped waiting. The action may still finish in the background. |
409 | Only when recovery says so | The resource is in a state that blocks the action. Read its state before you decide. vps_not_ready comes with retry_after while a new VPS is being set up. |
Other 4xx | No | The same request fails the same way. Fix it first. |
“Check first” means: read the resource before you send the change again. Did the server restart? Is the DNS record there? For a VPS, GET /api/v2/vps/{id}/active-jobs shows whether the action is already running.
Codes with 429
Section titled “Codes with 429”rate_limit_exceeded: you reached one of the rate limits. Wait the number of seconds in theRetry-Afterheader. Then send the request again. OnPOST /api/v2/ordersitsdetailis in Swedish today.rate_limited: a limit inside one endpoint. Wait asRetry-Aftersays. Some of these responses do not have the header. Without it, wait untilrecovery.retryAfterAtwhen it is set. Otherwise retry with a growing pause.cooldown_active: the same action ran a short while ago, for example a reinstall of the VPS. Do not send it again blindly. A retry after the wait runs the action a second time. Read the resource first, asawait_cooldownabove says.unblock_limit_reached: a pause on the same mail sender was lifted 3 times in 7 days. Waiting a few seconds does not clear it. Contact support to lift it again.
A growing pause means waiting longer after each failure, for example 1, 2, 4 and 8 seconds. Stop after a handful of tries. For reads, curl --retry does this for you and follows Retry-After:
curl --retry 4 "https://cloud.hostup.se/api/v2/vps" \ -H "Authorization: Bearer $HOSTUP_API_KEY"--retry also retries a 500. Today that includes the 500 a key gets over its rate limit. Pauses of a few seconds do not outlast that five-minute window. When the retries all fail, stop and wait as Rate limits says.
Do not use --retry on a POST, PUT, PATCH or DELETE.
Sending a change twice
Section titled “Sending a change twice”Most changes have no retry key. Some actions hand back the job that is already running when you send them a second time. Others run again. After a timeout or a lost response, check what happened before you send the change again.
POST /api/v2/orders is the exception. It takes an attemptKey in the body:
- Make a new random value for every new order, for example a UUID. Never use a counter or a fixed text such as
order-1. Such a key can match an earlier order. Then no order is placed. - Send the same value again only when you retry that same order after a timeout or a lost response.
- If the same
attemptKeycomes back within an hour of the order it placed, no second order is placed. The response is200instead of201, with the order the first request created. - A repeat sent while the first request is still running waits for it. If that takes too long, the repeat ends in
504TIMEOUT. Send it again a little later with the sameattemptKey.
All error codes
Section titled “All error codes”Error codes has one page per code: what it means, which endpoints return it and what to do about it.
Next: Rate limits. How many requests a key may send and what happens over the limit.