NodeOps
UK

How-to: handle errors

Calls can fail for several distinct reasons — bad credentials, missing resources, rate limits, validation errors, server faults, and transport failures. This guide shows you how to branch on the cause using instanceof narrowing so you never parse error messages.

At a glance

  • Package: @nodeops-createos/sandbox (npm)
  • Import: import { createClient } from "@nodeops-createos/sandbox"
  • Base URL: https://api.sb.createos.sh — override with CREATEOS_SANDBOX_BASE_URL
  • Auth: API key via the apiKey option or CREATEOS_SANDBOX_API_KEY

Error class overview

Every SDK failure throws a subclass of CreateosSandboxError.

CreateosSandboxError
├─ CreateosSandboxApiError          non-2xx HTTP response
│  ├─ CreateosSandboxAuthError      401
│  ├─ CreateosSandboxPermissionError  403
│  ├─ CreateosSandboxNotFoundError  404
│  ├─ CreateosSandboxPaymentRequiredError  402
│  ├─ CreateosSandboxValidationError  400 / 409 / 422
│  ├─ CreateosSandboxRateLimitError  429
│  └─ CreateosSandboxServerError    5xx
├─ CreateosSandboxConnectionError   network failure — no response received
└─ CreateosSandboxTimeoutError      request or waitUntil* deadline exceeded

See Errors reference for full field tables.

Basic catch-and-branch

Catch the base class to handle all SDK failures uniformly, then narrow to subclasses for specific recovery logic.

TypeScript
1import {
2 CreateosSandboxError,
3 CreateosSandboxAuthError,
4 CreateosSandboxPermissionError,
5 CreateosSandboxNotFoundError,
6 CreateosSandboxValidationError,
7 CreateosSandboxRateLimitError,
8 CreateosSandboxServerError,
9 CreateosSandboxConnectionError,
10 CreateosSandboxTimeoutError,
11} from "@nodeops-createos/sandbox";
12
13try {
14 const sandbox = await client.createSandbox({ rootfs: "tpl-abc123" });
15 try {
16 await sandbox.runCommand("echo", ["hello"]);
17 } finally {
18 await sandbox.destroy();
19 }
20} catch (err) {
21 if (!(err instanceof CreateosSandboxError)) throw err; // not ours
22
23 if (err instanceof CreateosSandboxAuthError) {
24 // 401 — key missing, malformed, or unrecognised.
25 console.error("Check CREATEOS_SANDBOX_API_KEY.");
26 throw err;
27 }
28
29 if (err instanceof CreateosSandboxPermissionError) {
30 // 403 — key is valid but cannot access this resource.
31 console.error("API key lacks permission for this resource.");
32 throw err;
33 }
34
35 if (err instanceof CreateosSandboxNotFoundError) {
36 // 404 — resource does not exist (or was already destroyed).
37 console.error("Resource not found:", err.resourceId);
38 return null;
39 }
40
41 if (err instanceof CreateosSandboxValidationError) {
42 // 400 / 409 / 422 — request shape rejected by the server.
43 console.error("Bad request:", err.envelope?.data);
44 throw err;
45 }
46
47 if (err instanceof CreateosSandboxRateLimitError) {
48 // 429 — retries already exhausted by the SDK; see rate-limit recipe below.
49 const wait = (err.retryAfterSeconds ?? 5) * 1000;
50 console.warn(`Rate limited. Retry in ${wait}ms.`);
51 throw err;
52 }
53
54 if (err instanceof CreateosSandboxServerError) {
55 // 5xx — server accepted the request but failed to fulfil it.
56 console.error("Server error:", err.statusCode, err.requestId);
57 throw err;
58 }
59
60 if (err instanceof CreateosSandboxConnectionError) {
61 // Network failure — DNS, TCP reset, socket closed — no response received.
62 console.error("Network failure:", err.cause);
63 throw err;
64 }
65
66 if (err instanceof CreateosSandboxTimeoutError) {
67 // Per-request timeout or waitUntil* poll deadline exceeded.
68 console.error("Timeout:", err.cause);
69 throw err;
70 }
71
72 throw err;
73}

Recipe: distinguish missing key vs revoked key vs wrong tenant

CreateosSandboxAuthError (401) and CreateosSandboxPermissionError (403) signal different problems and require different remediation.

TypeScript
1import {
2 CreateosSandboxAuthError,
3 CreateosSandboxPermissionError,
4} from "@nodeops-createos/sandbox";
5
6async function run() {
7 const sandbox = await client.createSandbox({ rootfs: "tpl-abc123" });
8 try {
9 await sandbox.runCommand("ls", ["/"]);
10 } finally {
11 await sandbox.destroy();
12 }
13}
14
15try {
16 await run();
17} catch (err) {
18 if (err instanceof CreateosSandboxAuthError) {
19 // The key is absent, malformed, or unknown to the control plane.
20 // Fix: set CREATEOS_SANDBOX_API_KEY or pass apiKey to the client.
21 console.error("Authentication failed — check your API key.");
22 return;
23 }
24 if (err instanceof CreateosSandboxPermissionError) {
25 // The key authenticated but is not allowed to touch this resource.
26 // Could be: wrong tenant, ACL restriction, or quota exhausted.
27 // Fix: use a key that has access, or contact support with err.requestId.
28 console.error(
29 "Permission denied. RequestId:",
30 err.requestId,
31 "Resource:",
32 err.resourceId,
33 );
34 return;
35 }
36 throw err;
37}

Recipe: handle a rate limit

The SDK already auto-retries 429 responses with exponential backoff before surfacing CreateosSandboxRateLimitError — see Reliability. When the error reaches your catch block, retries are exhausted and you must decide what to do next.

TypeScript
1import { CreateosSandboxRateLimitError } from "@nodeops-createos/sandbox";
2
3async function withRateLimitBackoff<T>(fn: () => Promise<T>): Promise<T> {
4 for (let attempt = 0; attempt < 3; attempt++) {
5 try {
6 return await fn();
7 } catch (err) {
8 if (err instanceof CreateosSandboxRateLimitError) {
9 const delay = (err.retryAfterSeconds ?? 2 ** attempt) * 1000;
10 console.warn(`Rate limited. Waiting ${delay}ms before retry ${attempt + 1}.`);
11 await new Promise((r) => setTimeout(r, delay));
12 continue;
13 }
14 throw err;
15 }
16 }
17 throw new Error("Rate limit not resolved after 3 retries.");
18}

retryAfterSeconds is parsed from the Retry-After response header (both delta-seconds and HTTP-date formats). It is undefined when the header is absent or unparseable — fall back to a fixed delay or exponential backoff.

Recipe: read rich fields from CreateosSandboxApiError for logging

Every HTTP error (CreateosSandboxApiError and all its subclasses) carries a structured set of fields. Use them for observability instead of parsing err.message.

TypeScript
1import {
2 CreateosSandboxApiError,
3 CreateosSandboxError,
4} from "@nodeops-createos/sandbox";
5
6function logSdkError(err: unknown): void {
7 if (err instanceof CreateosSandboxApiError) {
8 console.error({
9 type: err.name, // e.g. "CreateosSandboxNotFoundError"
10 statusCode: err.statusCode, // 404
11 method: err.method, // "GET"
12 endpoint: err.endpoint, // "/v1/sandboxes/sb-abc123"
13 resourceId: err.resourceId, // "sb-abc123" — parsed from path
14 requestId: err.requestId, // quote this in support tickets
15 code: err.code, // stable machine-readable code, when set
16 envelopeData: err.envelope?.data,
17 });
18 } else if (err instanceof CreateosSandboxError) {
19 // Transport errors (ConnectionError, TimeoutError) — no HTTP fields.
20 console.error({
21 type: err.name,
22 message: err.message,
23 cause: err.cause,
24 });
25 }
26}
27
28// Use alongside your catch block:
29try {
30 const sandbox = await client.createSandbox({ rootfs: "tpl-abc123" });
31 try {
32 await sandbox.runCommand("echo", ["hello"]);
33 } finally {
34 await sandbox.destroy();
35 }
36} catch (err) {
37 logSdkError(err);
38 throw err;
39}

requestId is read from X-Request-Id first, then X-Fc-Request-Id. Always include it when filing a support ticket — it lets the operator locate your exact call in the control plane logs in O(1).

Note: error.cause for transport errors

CreateosSandboxConnectionError and CreateosSandboxTimeoutError do not extend CreateosSandboxApiError — there is no HTTP response to inspect. The underlying network or abort error is chained on err.cause when available.

TypeScript
1import {
2 CreateosSandboxConnectionError,
3 CreateosSandboxTimeoutError,
4} from "@nodeops-createos/sandbox";
5
6try {
7 await client.createSandbox({ rootfs: "tpl-abc123" });
8} catch (err) {
9 if (err instanceof CreateosSandboxConnectionError) {
10 // err.cause: underlying fetch / socket error
11 console.error("No response from server:", err.cause);
12 }
13 if (err instanceof CreateosSandboxTimeoutError) {
14 // err.cause: AbortError from the AbortController
15 console.error("Request timed out:", err.cause);
16 }
17}

See also

100,000+ Builders. One Workspace.

Get product updates, builder stories, and early access to features that help you ship faster.

CreateOS is a unified intelligent workspace where ideas move seamlessly from concept to live deployment, eliminating context-switching across tools, infrastructure, and workflows with the opportunity to monetize ideas immediately on the CreateOS Marketplace.