Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.devhelm.io/llms.txt

Use this file to discover all available pages before exploring further.

The SDK throws typed errors for API failures, validation issues, and network problems. The taxonomy mirrors the API’s ErrorResponse envelope, with subclasses per HTTP class for ergonomic catching.

Error class hierarchy

DevhelmError                  ← umbrella; catch as a fallback
├── DevhelmValidationError    ← local request/response shape validation
├── DevhelmTransportError     ← connection refused, DNS, TLS, timeout
└── DevhelmApiError           ← non-2xx HTTP response
    ├── DevhelmAuthError      ← 401, 403
    ├── DevhelmNotFoundError  ← 404
    ├── DevhelmConflictError  ← 409
    ├── DevhelmRateLimitError ← 429
    └── DevhelmServerError    ← 5xx
All classes are importable from the package root:
import {
  DevhelmError,
  DevhelmValidationError,
  DevhelmApiError,
  DevhelmAuthError,
  DevhelmNotFoundError,
  DevhelmConflictError,
  DevhelmRateLimitError,
  DevhelmServerError,
  DevhelmTransportError,
} from "@devhelm/sdk";

DevhelmApiError

The most common error class — thrown for any non-2xx HTTP response.
PropertyTypeDescription
statusnumberHTTP status code (e.g. 404)
codestringCoarse machine-readable category from ErrorResponse.code (e.g. "NOT_FOUND"). Defaults to "API_ERROR" if the server didn’t supply one.
messagestringHuman-readable message — for logs and user-facing copy
detailstring | undefinedAdditional context, when available
bodyErrorResponse | undefinedParsed canonical error envelope if the body matched the contract
rawBodyunknownRaw response body for debugging non-conforming responses
requestIdstring | undefinedPer-request id from the X-Request-Id header. Always include in support tickets.
import { DevhelmApiError } from "@devhelm/sdk";

try {
  await client.monitors.get("missing");
} catch (err) {
  if (err instanceof DevhelmApiError) {
    console.error(err.status);    // 404
    console.error(err.code);      // "NOT_FOUND"
    console.error(err.requestId); // "req_01HJV..."
  }
}

DevhelmValidationError

Thrown before HTTP I/O when a request body fails the Zod schema, and after a successful response when the response body doesn’t match the expected schema (rare — usually indicates a stale SDK).
PropertyTypeDescription
messagestringSummary of the first 5 issues
issuesReadonlyArray<ValidationIssue>Structured issues with path, message, and code
import { DevhelmValidationError } from "@devhelm/sdk";

try {
  await client.monitors.create({ type: "HTTP" } as any); // missing fields
} catch (err) {
  if (err instanceof DevhelmValidationError) {
    for (const issue of err.issues) {
      console.error(`${issue.path.join(".")}: ${issue.message}`);
    }
  }
}

DevhelmTransportError

Thrown for network-level failures (connection refused, DNS, TLS handshake, timeout). The original fetch error is on .cause.
import { DevhelmTransportError } from "@devhelm/sdk";

try {
  await client.monitors.list();
} catch (err) {
  if (err instanceof DevhelmTransportError) {
    console.error("Network error:", err.message);
    console.error("Underlying:", err.cause);
  }
}

Handling patterns

Catch by HTTP class

import {
  DevhelmAuthError,
  DevhelmNotFoundError,
  DevhelmRateLimitError,
  DevhelmError,
} from "@devhelm/sdk";

try {
  const monitor = await client.monitors.get(monitorId);
} catch (err) {
  if (err instanceof DevhelmAuthError) {
    console.error("Bad token or insufficient permissions");
  } else if (err instanceof DevhelmNotFoundError) {
    console.error(`Monitor ${monitorId} no longer exists`);
  } else if (err instanceof DevhelmRateLimitError) {
    console.error(`Rate limited (request_id=${err.requestId})`);
  } else if (err instanceof DevhelmError) {
    throw err;
  }
}

Retry on rate limits and 5xx

import {
  DevhelmRateLimitError,
  DevhelmServerError,
  DevhelmTransportError,
} from "@devhelm/sdk";

async function withRetry<T>(
  fn: () => Promise<T>,
  { maxRetries = 3, baseDelayMs = 1000 } = {},
): Promise<T> {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (err) {
      const retryable =
        err instanceof DevhelmRateLimitError ||
        err instanceof DevhelmServerError ||
        err instanceof DevhelmTransportError;
      if (!retryable || attempt === maxRetries) throw err;
      const delay = baseDelayMs * Math.pow(2, attempt);
      await new Promise((r) => setTimeout(r, delay));
    }
  }
  throw new Error("unreachable");
}

const monitors = await withRetry(() => client.monitors.list());
Don’t retry on DevhelmValidationError, DevhelmAuthError, DevhelmNotFoundError, or DevhelmConflictError — these are deterministic and won’t change without code or config changes.

Deploy lock contention

import { DevhelmConflictError } from "@devhelm/sdk";

try {
  const lock = await client.deployLock.acquire({
    lockedBy: "ci-pipeline",
    ttlMinutes: 30,
  });
} catch (err) {
  if (err instanceof DevhelmConflictError) {
    const current = await client.deployLock.current();
    console.error(`Lock held by ${current?.lockedBy} (since ${current?.acquiredAt})`);
  }
}

Catch-all logging

import { DevhelmApiError, DevhelmError } from "@devhelm/sdk";

try {
  await doWork(client);
} catch (err) {
  if (err instanceof DevhelmApiError) {
    log.error("DevHelm API error", {
      status: err.status,
      code: err.code,
      requestId: err.requestId,
      detail: err.detail,
    });
  } else if (err instanceof DevhelmError) {
    log.error("DevHelm SDK error", { message: err.message });
  } else {
    throw err;
  }
}

Next steps

Pagination

Iterate through paginated results.

Error patterns

Common error scenarios across all surfaces.