CrowNest
TypeScript SDK

Errors

Handle CrowNestApiError, understand common error codes, and retry safely with idempotency keys.

Every failed API call in the TypeScript SDK throws a CrowNestApiError. The error carries the HTTP status and a stable, machine-readable code so you can branch on failures without parsing messages.

CrowNestApiError

CrowNestApiError extends Error with structured fields:

PropertyTypeDescription
statusnumberHTTP status code.
codestringStable error code in lowercase snake_case, for example quota_exceeded.
messagestringHuman-readable description.
detailsobject | undefinedAdditional context for some errors.
import { createCrowNestClient, CrowNestApiError } from "@crownest/sdk";

const client = createCrowNestClient();

try {
  await client.sandboxes.create({ ttlMs: 24 * 60 * 60 * 1000 });
} catch (error) {
  if (error instanceof CrowNestApiError) {
    console.error(error.status, error.code, error.message);
    if (error.code === "sandbox_ttl_exceeded") {
      // retry with a smaller ttlMs
    }
  } else {
    throw error;
  }
}

Non-zero exit codes don't throw

A command that runs to completion with a non-zero exit code is a successful API call. commands.run resolves normally; the SDK only throws when the API itself fails (for example, the sandbox is destroyed or the command times out).

const result = await sandbox.commands.run("false");
console.log(result.status); // "exited"
console.log(result.exitCode); // 1 — no error thrown

Always check command.status and command.exitCode to decide whether the process succeeded.

Common error codes

CodeHTTPMeaning
invalid_request400Malformed request or validation failure.
sandbox_ttl_exceeded400Requested ttlMs exceeds your plan limit.
reserved_env_key400An env key uses the reserved CROWNEST prefix.
unauthenticated / invalid_api_key401Missing, invalid, or revoked credentials.
billing_required402Billing configuration required.
forbidden403Missing scope or no access to the resource.
org_suspended403The organization is suspended.
not_found404The resource doesn't exist or isn't visible to you.
conflict409State conflict.
idempotency_key_reused409The same idempotency key was used with a different request.
idempotency_request_in_progress409The original request with this key is still running.
sandbox_destroyed410The sandbox is already destroyed.
file_too_large413Content exceeds the 256 KB direct read/write limit.
quota_exceeded429A plan quota was reached.
rate_limited429The request rate limit was reached (120 requests per 60 seconds per API key).
command_timed_out500The command exceeded its timeoutMs.
internal_error / runtime_error500Server-side failure.
stream_gap500Requested log sequence is outside the retention window.
preview_unavailable502The preview port isn't responding.

File operations can also fail with path_outside_workspace, path_is_directory, path_not_directory, file_not_found, file_already_exists, parent_directory_not_found, directory_not_empty, and invalid_file_encoding. See Files for where each applies.

Retries and idempotency

The create-style operations — sandboxes.create, commands.run, commands.start, and artifacts.create — accept an idempotencyKey. The SDK generates one automatically when you don't pass it, so retrying a failed call with the same key never duplicates work.

Idempotency keys are retained for 24 hours and scoped to the org, credential, method, and route:

  • Replaying the same key with the same request returns the original response.
  • Reusing the key with a different request fails with idempotency_key_reused (409).
  • Retrying while the original request is still running fails with idempotency_request_in_progress (409).

We recommend this retry policy:

  • Retry 429 (rate_limited, quota_exceeded) and 5xx errors with exponential backoff.
  • Pass your own idempotencyKey when you retry create operations across process restarts, so the key survives the restart.
  • Don't retry 4xx validation errors (invalid_request, sandbox_ttl_exceeded, forbidden) — fix the request instead.
  • Treat sandbox_destroyed (410) as terminal for that sandbox: create a new one.

Next steps

On this page