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 withCREATEOS_SANDBOX_BASE_URL - Auth: API key via the
apiKeyoption orCREATEOS_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.
TypeScript1import {2 CreateosSandboxError,3 CreateosSandboxAuthError,4 CreateosSandboxPermissionError,5 CreateosSandboxNotFoundError,6 CreateosSandboxValidationError,7 CreateosSandboxRateLimitError,8 CreateosSandboxServerError,9 CreateosSandboxConnectionError,10 CreateosSandboxTimeoutError,11} from "@nodeops-createos/sandbox";1213try {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 ours2223 if (err instanceof CreateosSandboxAuthError) {24 // 401 — key missing, malformed, or unrecognised.25 console.error("Check CREATEOS_SANDBOX_API_KEY.");26 throw err;27 }2829 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 }3435 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 }4041 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 }4647 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 }5354 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 }5960 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 }6566 if (err instanceof CreateosSandboxTimeoutError) {67 // Per-request timeout or waitUntil* poll deadline exceeded.68 console.error("Timeout:", err.cause);69 throw err;70 }7172 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.
TypeScript1import {2 CreateosSandboxAuthError,3 CreateosSandboxPermissionError,4} from "@nodeops-createos/sandbox";56async 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}1415try {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.
TypeScript1import { CreateosSandboxRateLimitError } from "@nodeops-createos/sandbox";23async 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.
TypeScript1import {2 CreateosSandboxApiError,3 CreateosSandboxError,4} from "@nodeops-createos/sandbox";56function logSdkError(err: unknown): void {7 if (err instanceof CreateosSandboxApiError) {8 console.error({9 type: err.name, // e.g. "CreateosSandboxNotFoundError"10 statusCode: err.statusCode, // 40411 method: err.method, // "GET"12 endpoint: err.endpoint, // "/v1/sandboxes/sb-abc123"13 resourceId: err.resourceId, // "sb-abc123" — parsed from path14 requestId: err.requestId, // quote this in support tickets15 code: err.code, // stable machine-readable code, when set16 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}2728// 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.
TypeScript1import {2 CreateosSandboxConnectionError,3 CreateosSandboxTimeoutError,4} from "@nodeops-createos/sandbox";56try {7 await client.createSandbox({ rootfs: "tpl-abc123" });8} catch (err) {9 if (err instanceof CreateosSandboxConnectionError) {10 // err.cause: underlying fetch / socket error11 console.error("No response from server:", err.cause);12 }13 if (err instanceof CreateosSandboxTimeoutError) {14 // err.cause: AbortError from the AbortController15 console.error("Request timed out:", err.cause);16 }17}
See also
- Errors reference — full field tables for every class.
- Reliability — retry policy, backoff strategy, and which methods are retried automatically.
- How-to: observability — hook-based request/response logging.