Skip to content
Raster
Esc
navigateopen⌘Jpreview
On this page

Errors

The closed set of codes, their HTTP statuses, and which are worth retrying.

Every non-2xx response is the same envelope, whatever the route:

{ "error": { "code": "...", "message": "...", "request_id": "...", "details": [], "quota": {} } }

Switch on code, never on message. The code is a closed set with a fixed status per member; the message is written for a person and may be reworded in any release.

The codes

Code Status Means Retry?
invalid_request 400 the request was malformed or a field was wrong. details names the fields no
unauthenticated 401 no credential, or one the API does not accept no
quota_exceeded 402 a plan entitlement declined it. quota says which no
permission_denied 403 authenticated, but the role or scope does not allow it no
not_found 404 no such object in this organization no
conflict 409 the current state forbids it — an illegal transition, a held lease, a reused idempotency key no
precondition_failed 412 something that had to be true first was not no
rate_limited 429 too many attempts; wait yes
internal_error 500 a fault on our side yes
unsupported_operation 501 the deployment is not configured for this feature no
compute_unavailable 503 the machine is not reachable yet, or the substrate is unwell yes

not_found rather than permission_denied for another organization’s object is deliberate: a 403 would confirm that the id exists.

Fields

Field Always? What it is
code yes the closed set above
message yes human-readable, not machine-parseable
request_id yes quote it in a support request
details on invalid_request { field, message }, with field a dotted path into the body
quota on quota_exceeded which limit declined this request

Handling them

import { RasterError } from "@raster/sdk";

try {
  await client.machines.create({ size: "large" });
} catch (error) {
  if (!RasterError.is(error)) throw error;

  switch (error.code) {
    case "quota_exceeded":
      // render this, not the message: it names the limit, the current value,
      // the maximum, and where to go to lift it.
      show(error.quota);
      break;
    case "invalid_request":
      for (const d of error.details) markField(d.field, d.message);
      break;
    default:
      report(error.code, error.requestId);
  }
}
from raster import RasterError

try:
    client.machines.create(size="large")
except RasterError as error:
    if error.code == "invalid_request":
        for d in error.details:
            mark_field(d["field"], d["message"])
    else:
        report(error.code, error.request_id)

error.status is 0 on a transport failure — a timeout, a DNS failure, a proxy that answered instead of the API — and request_id is null for the same reason. Anything the API itself produced has both.

compute_unavailable is usually not a failure

A machine that is running but whose agent has not published its address yet answers every desktop route with compute_unavailable. That happens on every boot. It is why both SDKs treat the code as retryable and why waitForDesktop swallows it while polling.

Treat it as “not yet”, not as “broken”, and put a deadline on the waiting rather than a retry count.

Gateway close codes

A websocket failure is not this envelope — it is a close code. See the realtime gateway. They map onto the same vocabulary: 4401 is unauthenticated, 4403 is permission_denied, 4404 is not_found, 4409 is conflict, 4429 is rate_limited, and 4503 is compute_unavailable.

Was this page helpful?