Skip to main content

Error Types

The SDK exports two main error classes:
import { AlignError, AlignValidationError } from "@tolbel/align";

AlignError

Thrown when the API returns an error response (4xx or 5xx status codes).
class AlignError extends Error {
  readonly status: number; // HTTP status code
  readonly code?: string; // Error code from API
  readonly requestId?: string; // Request ID for debugging
  readonly body?: unknown; // Additional error details
}

AlignValidationError

Thrown when request data fails local validation before being sent to the API.
class AlignValidationError extends Error {
  readonly errors: Record<string, string[]>; // Map of field names to error messages
}

Basic Error Handling

Always wrap API calls in try-catch blocks:
import Align, { AlignError, AlignValidationError } from "@tolbel/align";

const align = new Align({
  apiKey: process.env.ALIGN_API_KEY!,
  environment: "sandbox",
});

async function createCustomer() {
  try {
    const customer = await align.customers.create({
      email: "[email protected]",
      type: "individual",
      first_name: "Alice",
      last_name: "Smith",
    });
    return customer;
  } catch (error) {
    if (error instanceof AlignValidationError) {
      // Local validation failed
      console.error("Validation failed:");
      Object.entries(error.errors).forEach(([field, messages]) => {
        console.error(`  ${field}: ${messages.join(", ")}`);
      });
    } else if (error instanceof AlignError) {
      // API returned an error
      console.error(`API Error [${error.status}]: ${error.message}`);
      console.error(`Request ID: ${error.requestId}`);
    } else {
      // Network or unexpected error
      throw error;
    }
  }
}

Common Error Scenarios

Thrown when trying to create a customer with an email that already exists.
try {
  await align.customers.create({
    email: "[email protected]",
    type: "individual",
    first_name: "Alice",
    last_name: "Smith",
  });
} catch (error) {
  if (error instanceof AlignError && error.status === 409) {
    // Email already exists - find the existing customer
    const existing = await align.customers.list("[email protected]");
    console.log("Found existing customer:", existing.items[0].customer_id);
  }
}
Thrown when the API key is invalid or missing.
try {
  await align.customers.list();
} catch (error) {
  if (error instanceof AlignError && error.status === 401) {
    console.error("Invalid API key. Check your ALIGN_API_KEY environment variable.");
  }
}
Never log or expose your API key in error messages!
Thrown when the requested resource doesn’t exist.
try {
  const customer = await align.customers.get("non-existent-id");
} catch (error) {
  if (error instanceof AlignError && error.status === 404) {
    console.error("Customer not found");
  }
}
Caught before the API call when request data is invalid.
try {
  await align.customers.create({
    email: "not-an-email",  // Invalid email format
    type: "individual",
    // Missing first_name and last_name
  });
} catch (error) {
  if (error instanceof AlignValidationError) {
    // error.errors = {
    //   "email": ["Invalid email address"],
    //   "first_name": ["Required"],
    //   "last_name": ["Required"]
    // }
  }
}
Thrown when you’ve exceeded the API rate limit.
try {
  await align.customers.list();
} catch (error) {
  if (error instanceof AlignError && error.status === 429) {
    // Wait and retry
    // Check body for specific retry details if available
    const retryAfter = 60; 
    console.log(`Rate limited. Retry after ${retryAfter} seconds`);
    
    await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
    // Retry the request
  }
}
The SDK has built-in retry logic with exponential backoff. Most transient errors are automatically retried.

Error Handling Patterns

Centralized Error Handler

Create a reusable error handler for consistent error management:
import { AlignError, AlignValidationError } from "@tolbel/align";

type ErrorResult =
  | { type: "validation"; errors: Record<string, string[]> }
  | { type: "api"; status: number; message: string }
  | { type: "network"; message: string }
  | { type: "unknown"; error: unknown };

function handleAlignError(error: unknown): ErrorResult {
  if (error instanceof AlignValidationError) {
    return {
      type: "validation",
      errors: error.errors,
    };
  }

  if (error instanceof AlignError) {
    return {
      type: "api",
      status: error.status,
      message: error.message,
    };
  }

  if (error instanceof Error && error.message.includes("fetch")) {
    return {
      type: "network",
      message: "Network error. Check your internet connection.",
    };
  }

  return {
    type: "unknown",
    error,
  };
}

// Usage
try {
  await align.customers.create(data);
} catch (error) {
  const result = handleAlignError(error);

  switch (result.type) {
    case "validation":
      return { success: false, errors: result.errors };
    case "api":
      return { success: false, message: result.message };
    case "network":
      return { success: false, message: "Please try again later" };
    default:
      throw error;
  }
}

Retry with Backoff

For critical operations, implement custom retry logic:
async function withRetry<T>(
  fn: () => Promise<T>,
  maxRetries = 3,
  baseDelay = 1000
): Promise<T> {
  let lastError: unknown;

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error;

      if (error instanceof AlignError) {
        // Don't retry client errors (4xx) except rate limiting
        if (error.status && error.status < 500 && error.status !== 429) {
          throw error;
        }
      }

      // Exponential backoff
      const delay = baseDelay * Math.pow(2, attempt);
      console.log(`Attempt ${attempt + 1} failed. Retrying in ${delay}ms...`);
      await new Promise((resolve) => setTimeout(resolve, delay));
    }
  }

  throw lastError;
}

// Usage
const customer = await withRetry(() =>
  align.customers.create({
    email: "[email protected]",
    type: "individual",
    first_name: "Alice",
    last_name: "Smith",
  })
);

Logging Errors

Enable SDK logging for debugging:
const align = new Align({
  apiKey: process.env.ALIGN_API_KEY!,
  environment: "sandbox",
  enableLogging: true, // Enable pino logging
});
The SDK uses pino for logging. Set LOG_LEVEL environment variable to control log verbosity.

Best Practices

Always Use Try-Catch

Wrap all API calls in try-catch blocks to prevent unhandled promise rejections.

Check Error Types

Use instanceof to distinguish between validation errors and API errors.

Log Appropriately

Log errors for debugging but never log sensitive data like API keys.

User-Friendly Messages

Transform technical errors into user-friendly messages for your UI.