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 Code | Error Type | Description |
|---|
| 400 | BadRequestError | Invalid request data |
| 401 | AuthenticationError | Invalid or missing API key |
| 403 | PermissionDeniedError | Insufficient permissions |
| 404 | NotFoundError | Resource not found |
| 409 | ConflictError | Conflict (e.g., duplicate resource) |
| 422 | UnprocessableEntityError | Validation error |
| 429 | RateLimitError | Rate limit exceeded |
| >=500 | InternalServerError | Server error |
| N/A | APIConnectionError | Network connectivity issue |
| N/A | APIConnectionTimeoutError | Request 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 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 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
| Level | Description |
|---|
debug | All messages including HTTP requests/responses |
info | Info, warnings, and errors |
warn | Warnings and errors (default) |
error | Only errors |
off | Disable all logging |
export BREW_SDK_LOG=debug
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
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: