Skip to main content

Error Types

When the SDK encounters an error, it throws a specific error class based on the HTTP status code or error type.

Error Hierarchy

All SDK errors extend from BrewSDK.APIError:
BrewSDKError
└── APIError
    ├── BadRequestError (400)
    ├── AuthenticationError (401)
    ├── PermissionDeniedError (403)
    ├── NotFoundError (404)
    ├── ConflictError (409)
    ├── UnprocessableEntityError (422)
    ├── RateLimitError (429)
    ├── InternalServerError (>=500)
    └── APIConnectionError (network issues)
        └── APIConnectionTimeoutError

Error Types by Status Code

Status CodeError TypeDescription
400BadRequestErrorInvalid request data
401AuthenticationErrorInvalid or missing API key
403PermissionDeniedErrorInsufficient permissions
404NotFoundErrorResource not found
409ConflictErrorConflict (e.g., duplicate resource)
422UnprocessableEntityErrorValidation error
429RateLimitErrorRate limit exceeded
>=500InternalServerErrorServer error
N/AAPIConnectionErrorNetwork connectivity issue
N/AAPIConnectionTimeoutErrorRequest timed out

Handling Errors

Basic Error Handling

import BrewSDK from 'brew-sdk';

const client = new BrewSDK();

try {
  const result = await client.contacts.import.create({
    contacts: [{ email: '[email protected]' }],
  });
} catch (error) {
  if (error instanceof BrewSDK.APIError) {
    console.error('API Error:', error.status, error.message);
  } else {
    throw error;
  }
}

Handling Specific Error Types

import BrewSDK from 'brew-sdk';

const client = new BrewSDK();

try {
  await client.send.transactional.send({
    chatId: 'template-id',
    to: '[email protected]',
  });
} catch (error) {
  if (error instanceof BrewSDK.BadRequestError) {
    console.error('Invalid request:', error.message);
    // Fix the request data
  } else if (error instanceof BrewSDK.AuthenticationError) {
    console.error('Authentication failed - check your API key');
    // Re-authenticate or alert the user
  } else if (error instanceof BrewSDK.NotFoundError) {
    console.error('Template not found');
    // Check the template ID
  } else if (error instanceof BrewSDK.RateLimitError) {
    console.error('Rate limited - waiting before retry');
    // Wait and retry
  } else if (error instanceof BrewSDK.InternalServerError) {
    console.error('Server error - will be retried automatically');
  } else if (error instanceof BrewSDK.APIConnectionError) {
    console.error('Network error:', error.message);
  } else {
    throw error;
  }
}

Using .catch() with Promises

const result = await client.contacts.import
  .create({ contacts: [{ email: '[email protected]' }] })
  .catch(async (error) => {
    if (error instanceof BrewSDK.APIError) {
      console.error('Status:', error.status);
      console.error('Message:', error.name);
      console.error('Headers:', error.headers);
    }
    throw error;
  });

Error Properties

All APIError instances include:
try {
  // ...
} catch (error) {
  if (error instanceof BrewSDK.APIError) {
    console.log(error.status);    // HTTP status code (e.g., 400)
    console.log(error.name);      // Error type name (e.g., 'BadRequestError')
    console.log(error.message);   // Error message from API
    console.log(error.headers);   // Response headers
  }
}

Automatic Retries

The SDK automatically retries certain errors with exponential backoff.

Retried Errors

The following errors are automatically retried (2 times by default):
  • Connection errors (network issues)
  • 408 Request Timeout
  • 409 Conflict
  • 429 Rate Limit
  • 500+ Internal Server Errors

Configure Retry Behavior

// Configure default retries for all requests
const client = new BrewSDK({
  maxRetries: 0,  // Disable retries
});

// Or configure per-request
await client.contacts.import.create(
  { contacts: [{ email: '[email protected]' }] },
  { maxRetries: 5 }  // Override for this request
);

Retry Timing

Retries use exponential backoff:
  • First retry: ~0.5 seconds
  • Second retry: ~1 second
  • Maximum delay: 8 seconds
The SDK also respects Retry-After headers from the server.

Timeouts

Default Timeout

Requests timeout after 60 seconds (1 minute) by default.

Configure Timeouts

// Configure default timeout for all requests
const client = new BrewSDK({
  timeout: 20 * 1000,  // 20 seconds
});

// Or configure per-request
await client.contacts.import.create(
  { contacts: [{ email: '[email protected]' }] },
  { timeout: 5 * 1000 }  // 5 seconds for this request
);

Handling Timeouts

try {
  await client.send.transactional.send({
    chatId: 'template-id',
    to: '[email protected]',
  });
} catch (error) {
  if (error instanceof BrewSDK.APIConnectionTimeoutError) {
    console.error('Request timed out');
    // Note: Timed out requests are retried by default
  }
}

Logging

The SDK includes built-in logging for debugging.

Log Levels

LevelDescription
debugAll messages including HTTP requests/responses
infoInfo, warnings, and errors
warnWarnings and errors (default)
errorOnly errors
offDisable all logging

Configure via Environment Variable

export BREW_SDK_LOG=debug

Configure via Client Option

const client = new BrewSDK({
  logLevel: 'debug',  // Show all log messages
});

Custom Logger

Use a custom logger (compatible with pino, winston, bunyan, etc.):
import BrewSDK from 'brew-sdk';
import pino from 'pino';

const logger = pino();

const client = new BrewSDK({
  logger: logger.child({ name: 'BrewSDK' }),
  logLevel: 'debug',
});
At the debug level, all HTTP requests and responses are logged, including headers and bodies. Some authentication headers are redacted, but sensitive data in request/response bodies may still be visible.

Accessing Raw Responses

Get Response Headers

Use .asResponse() to access the raw Response object:
const response = await client.contacts.import
  .create({ contacts: [{ email: '[email protected]' }] })
  .asResponse();

console.log(response.headers.get('X-Request-Id'));
console.log(response.statusText);

Get Both Data and Response

Use .withResponse() to get both the parsed data and raw response:
const { data, response } = await client.contacts.import
  .create({ contacts: [{ email: '[email protected]' }] })
  .withResponse();

console.log(response.headers.get('X-RateLimit-Remaining'));
console.log(data.data.stats);

Best Practices

Wrap API Calls

Create a wrapper function for consistent error handling:
async function safeApiCall<T>(
  apiCall: () => Promise<T>
): Promise<{ data?: T; error?: Error }> {
  try {
    const data = await apiCall();
    return { data };
  } catch (error) {
    if (error instanceof BrewSDK.RateLimitError) {
      // Log and potentially retry with backoff
      console.warn('Rate limited, consider reducing request frequency');
    } else if (error instanceof BrewSDK.AuthenticationError) {
      // Alert about authentication issues
      console.error('API key invalid or expired');
    }
    return { error: error as Error };
  }
}

// Usage
const { data, error } = await safeApiCall(() =>
  client.contacts.import.create({ contacts: [{ email: '[email protected]' }] })
);

Implement Circuit Breaker

For high-throughput applications, implement a circuit breaker pattern:
class CircuitBreaker {
  private failures = 0;
  private lastFailure: number | null = null;
  private readonly threshold = 5;
  private readonly resetTime = 60000; // 1 minute

  async execute<T>(fn: () => Promise<T>): Promise<T> {
    if (this.isOpen()) {
      throw new Error('Circuit breaker is open');
    }

    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  private isOpen(): boolean {
    if (this.failures < this.threshold) return false;
    if (Date.now() - (this.lastFailure ?? 0) > this.resetTime) {
      this.reset();
      return false;
    }
    return true;
  }

  private onSuccess() {
    this.failures = 0;
  }

  private onFailure() {
    this.failures++;
    this.lastFailure = Date.now();
  }

  private reset() {
    this.failures = 0;
    this.lastFailure = null;
  }
}

Log Errors with Context

Include context in error logs for easier debugging:
try {
  await client.contacts.update({
    email: userEmail,
    customFields: userData,
  });
} catch (error) {
  if (error instanceof BrewSDK.APIError) {
    console.error('Contact update failed', {
      email: userEmail,
      errorType: error.name,
      status: error.status,
      message: error.message,
      requestId: error.headers?.get('x-request-id'),
    });
  }
  throw error;
}

Production Error Handler

Here’s a production-ready error handler:
import BrewSDK from 'brew-sdk';

interface BrewResult<T> {
  data?: T;
  error?: {
    type: string;
    message: string;
    status?: number;
    retryable: boolean;
  };
}

async function withBrewErrorHandling<T>(
  operation: () => Promise<T>,
  context: Record<string, unknown> = {}
): Promise<BrewResult<T>> {
  try {
    const data = await operation();
    return { data };
  } catch (error) {
    if (error instanceof BrewSDK.RateLimitError) {
      return {
        error: {
          type: 'RateLimitError',
          message: 'Too many requests. Please slow down.',
          status: 429,
          retryable: true,
        },
      };
    }
    
    if (error instanceof BrewSDK.AuthenticationError) {
      // Log for ops team - API key may need rotation
      console.error('Brew authentication failed', context);
      return {
        error: {
          type: 'AuthenticationError',
          message: 'Authentication failed. Please contact support.',
          status: 401,
          retryable: false,
        },
      };
    }
    
    if (error instanceof BrewSDK.APIError) {
      return {
        error: {
          type: error.name,
          message: error.message,
          status: error.status,
          retryable: error.status >= 500,
        },
      };
    }
    
    throw error; // Re-throw unexpected errors
  }
}

// Usage
const result = await withBrewErrorHandling(
  () => client.contacts.import.create({ contacts }),
  { operation: 'importContacts', count: contacts.length }
);

if (result.error) {
  if (result.error.retryable) {
    // Queue for retry
  } else {
    // Handle permanent failure
  }
}

Need Help?

Our team is ready to support you at every step of your journey with Brew. Choose the option that works best for you:

Search Documentation

Type in the “Ask any question” search bar at the top left to instantly find relevant documentation pages.

ChatGPT/Claude Integration

Click “Open in ChatGPT” at the top right of any page to analyze documentation with ChatGPT or Claude for deeper insights.