LoopLoop

Error Handling

Handle API errors with typed error classes in the Loop TypeScript SDK.

Error Handling

The Loop SDK throws typed error classes for API failures. All errors extend LoopError, which provides structured information about the failure including the HTTP status code, an error code, and optional details.

Error classes

ClassHTTP StatusCodeWhen
LoopNotFoundError404NOT_FOUNDRequested resource does not exist
LoopValidationError422VALIDATION_ERRORRequest body fails validation
LoopConflictError409CONFLICTDuplicate or conflicting operation
LoopRateLimitError429RATE_LIMITEDToo many requests
LoopErroranyHTTP_{status}All other API errors

Catching errors

All error classes extend LoopError, so you can catch broadly or narrowly:

import {
  LoopError,
  LoopNotFoundError,
  LoopValidationError,
  LoopRateLimitError,
} from '@dork-labs/loop-sdk';

try {
  const issue = await loop.issues.get('nonexistent-id');
} catch (error) {
  if (error instanceof LoopNotFoundError) {
    console.log('Issue not found');
  } else if (error instanceof LoopValidationError) {
    console.log('Validation failed:', error.message);
    console.log('Details:', error.details);
  } else if (error instanceof LoopRateLimitError) {
    console.log('Rate limited, retry later');
  } else if (error instanceof LoopError) {
    console.log(`API error ${error.status}: ${error.message}`);
    console.log('Code:', error.code);
  } else {
    throw error; // Re-throw non-Loop errors
  }
}

Error properties

Every LoopError instance has these properties:

PropertyTypeDescription
messagestringHuman-readable error description
statusnumberHTTP status code
codestringMachine-readable error code
detailsRecord<string, unknown>Additional error context (validation errors, etc.)
namestringError class name (e.g. "LoopNotFoundError")

Validation errors

LoopValidationError includes a details object with field-level information:

try {
  await loop.issues.create({ title: '' }); // Empty title
} catch (error) {
  if (error instanceof LoopValidationError) {
    console.log(error.details);
    // { title: "String must contain at least 1 character(s)" }
  }
}

Pattern: retry on rate limit

async function withRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (error instanceof LoopRateLimitError && attempt < maxRetries - 1) {
        const delay = Math.pow(2, attempt) * 1000;
        await new Promise((resolve) => setTimeout(resolve, delay));
        continue;
      }
      throw error;
    }
  }
  throw new Error('Unreachable');
}

const issues = await withRetry(() => loop.issues.list());

Pattern: safe resource lookup

async function findIssue(id: string) {
  try {
    return await loop.issues.get(id);
  } catch (error) {
    if (error instanceof LoopNotFoundError) {
      return null;
    }
    throw error;
  }
}

const issue = await findIssue('maybe-exists');
if (issue) {
  console.log(issue.title);
}