Error Handling
This guide covers best practices for handling errors in your PocketDNS integration.
Error Response Format
All PocketDNS API errors follow a consistent format:
json
{
"error": "error_type",
"message": "Human-readable error description",
"details": {
"field": "field_name",
"code": "specific_error_code"
},
"request_id": "req_abc123def456"
}Error Response Fields
| Field | Type | Description |
|---|---|---|
error | string | General error category |
message | string | Human-readable error message |
details | object | Additional error details (optional) |
request_id | string | Unique request identifier for debugging |
Common Error Types
Authentication Errors (401)
json
{
"error": "unauthorized",
"message": "Invalid API key",
"request_id": "req_abc123"
}Causes:
- Missing Authorization header
- Invalid API key format
- Expired or revoked API key
- Wrong environment (sandbox key in production)
Handling:
javascript
const handleAuthError = (error) => {
console.error('Authentication failed:', error.message);
// Check API key configuration
if (!process.env.POCKETDNS_API_KEY) {
throw new Error('POCKETDNS_API_KEY environment variable is not set');
}
// Log for debugging (without exposing the key)
const keyPrefix = process.env.POCKETDNS_API_KEY.substring(0, 8);
console.error(`API key starts with: ${keyPrefix}...`);
throw new Error('Please check your PocketDNS API key configuration');
};Validation Errors (400, 422)
json
{
"error": "validation_error",
"message": "user_identifier is required",
"details": {
"field": "user_identifier",
"code": "missing_required_field"
},
"request_id": "req_def456"
}Common validation issues:
- Missing required fields
- Invalid email format
- Invalid domain name format
- Invalid DNS record content
Handling:
javascript
const handleValidationError = (error, context) => {
console.error(`Validation error in ${context}:`, error);
const field = error.details?.field;
const code = error.details?.code;
// Provide user-friendly error messages
const userMessages = {
'user_identifier.missing_required_field': 'User identifier is required',
'email.invalid_format': 'Please enter a valid email address',
'domain_name.invalid_format': 'Please enter a valid domain name',
'dns_record.invalid_ip': 'Please enter a valid IP address'
};
const userMessage = userMessages[`${field}.${code}`] || error.message;
return {
field,
message: userMessage,
originalError: error
};
};Rate Limiting (429)
json
{
"error": "rate_limit_exceeded",
"message": "Too many requests",
"details": {
"limit": 100,
"window": 60,
"retry_after": 30
},
"request_id": "req_ghi789"
}Handling:
javascript
const handleRateLimit = async (error, retryAttempt = 0) => {
const maxRetries = 3;
const baseDelay = 1000; // 1 second
if (retryAttempt >= maxRetries) {
throw new Error('Rate limit exceeded after maximum retries');
}
const retryAfter = error.details?.retry_after || Math.pow(2, retryAttempt);
const delay = baseDelay * retryAfter;
console.log(`Rate limited. Retrying in ${delay}ms (attempt ${retryAttempt + 1})`);
await new Promise(resolve => setTimeout(resolve, delay));
return retryAttempt + 1;
};Not Found (404)
json
{
"error": "not_found",
"message": "Domain not found",
"request_id": "req_jkl012"
}Handling:
javascript
const handleNotFound = (error, resourceType) => {
console.warn(`${resourceType} not found:`, error.message);
const messages = {
'user': 'User not found. Please check the user identifier.',
'domain': 'Domain not found. It may have been deleted or transferred.',
'dns_record': 'DNS record not found. It may have been deleted.',
'session': 'Session expired or not found. Please create a new session.'
};
return messages[resourceType] || `${resourceType} not found`;
};Server Errors (500, 502, 503)
json
{
"error": "internal_error",
"message": "An internal error occurred",
"request_id": "req_mno345"
}Handling:
javascript
const handleServerError = async (error, operation, retryAttempt = 0) => {
const maxRetries = 3;
const baseDelay = 2000; // 2 seconds
console.error(`Server error during ${operation}:`, error);
// Log for monitoring
if (typeof trackError === 'function') {
trackError(error, { operation, retryAttempt });
}
if (retryAttempt < maxRetries) {
const delay = baseDelay * Math.pow(2, retryAttempt);
console.log(`Retrying ${operation} in ${delay}ms (attempt ${retryAttempt + 1})`);
await new Promise(resolve => setTimeout(resolve, delay));
return retryAttempt + 1;
}
throw new Error(`${operation} failed after ${maxRetries} retries`);
};Comprehensive Error Handler
Create a centralized error handler:
javascript
class PocketDNSError extends Error {
constructor(message, type, statusCode, details, requestId) {
super(message);
this.name = 'PocketDNSError';
this.type = type;
this.statusCode = statusCode;
this.details = details;
this.requestId = requestId;
}
}
const handlePocketDNSError = async (response, context, retryFn = null) => {
const errorData = await response.json().catch(() => ({}));
const error = new PocketDNSError(
errorData.message || 'Unknown error',
errorData.error || 'unknown',
response.status,
errorData.details,
errorData.request_id
);
console.error(`PocketDNS API Error (${context}):`, {
status: response.status,
type: error.type,
message: error.message,
requestId: error.requestId
});
switch (response.status) {
case 401:
throw new Error('Invalid API key. Please check your configuration.');
case 400:
case 422:
const validation = handleValidationError(error, context);
throw new Error(validation.message);
case 404:
const notFoundMsg = handleNotFound(error, context);
throw new Error(notFoundMsg);
case 429:
if (retryFn) {
const retryAttempt = await handleRateLimit(error);
return retryFn(retryAttempt);
}
throw new Error('Rate limit exceeded. Please try again later.');
case 500:
case 502:
case 503:
if (retryFn) {
const retryAttempt = await handleServerError(error, context);
return retryFn(retryAttempt);
}
throw new Error('Server error. Please try again later.');
default:
throw error;
}
};API Client with Error Handling
Create a robust API client:
javascript
class PocketDNSClient {
constructor(apiKey, baseUrl = 'https://api.pocketdns.com') {
this.apiKey = apiKey;
this.baseUrl = baseUrl;
this.timeout = 30000; // 30 seconds
}
async request(method, endpoint, data = null, retryAttempt = 0) {
const url = `${this.baseUrl}/api/v1${endpoint}`;
const options = {
method,
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
'User-Agent': 'PocketDNS-Client/1.0'
},
timeout: this.timeout
};
if (data) {
options.body = JSON.stringify(data);
}
try {
const response = await fetch(url, options);
if (!response.ok) {
const retryFn = (newRetryAttempt) =>
this.request(method, endpoint, data, newRetryAttempt);
return await handlePocketDNSError(response, endpoint, retryFn);
}
return await response.json();
} catch (error) {
if (error.name === 'AbortError' || error.code === 'ECONNABORTED') {
throw new Error('Request timeout. Please check your connection.');
}
if (error.code === 'ENOTFOUND' || error.code === 'ECONNREFUSED') {
throw new Error('Unable to connect to PocketDNS API. Please check your internet connection.');
}
throw error;
}
}
async createUserSession(userIdentifier, email = null) {
try {
return await this.request('POST', '/users', {
user_identifier: userIdentifier,
email
});
} catch (error) {
throw new Error(`Failed to create user session: ${error.message}`);
}
}
async getUserDomains(userIdentifier) {
try {
const response = await this.request('GET', `/users/${userIdentifier}/domains`);
return response.domains || [];
} catch (error) {
if (error.message.includes('not found')) {
return []; // Return empty array for non-existent users
}
throw new Error(`Failed to fetch user domains: ${error.message}`);
}
}
}Frontend Error Handling
React Error Boundary
Create an error boundary for React components:
jsx
import React from 'react';
class PocketDNSErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('PocketDNS Error Boundary caught an error:', error, errorInfo);
// Report to error tracking service
if (typeof reportError === 'function') {
reportError(error, { component: 'PocketDNS', ...errorInfo });
}
}
handleRetry = () => {
this.setState({ hasError: false, error: null });
};
render() {
if (this.state.hasError) {
return (
<div className="pocketdns-error">
<h3>Domain Interface Unavailable</h3>
<p>We're having trouble loading the domain interface. Please try again.</p>
<button onClick={this.handleRetry}>Retry</button>
{process.env.NODE_ENV === 'development' && (
<details>
<summary>Error Details</summary>
<pre>{this.state.error?.stack}</pre>
</details>
)}
</div>
);
}
return this.props.children;
}
}
// Usage
const DomainManagement = () => (
<PocketDNSErrorBoundary>
<PocketDNSEmbed userIdentifier="user_123" />
</PocketDNSErrorBoundary>
);React Hook for Error Handling
jsx
import { useState, useCallback } from 'react';
const usePocketDNSError = () => {
const [error, setError] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const handleAsync = useCallback(async (asyncFn) => {
setError(null);
setIsLoading(true);
try {
const result = await asyncFn();
setIsLoading(false);
return result;
} catch (err) {
setError({
message: err.message,
timestamp: new Date().toISOString()
});
setIsLoading(false);
throw err;
}
}, []);
const clearError = useCallback(() => {
setError(null);
}, []);
return {
error,
isLoading,
handleAsync,
clearError
};
};
// Usage in component
const PocketDNSEmbed = ({ userIdentifier }) => {
const { error, isLoading, handleAsync } = usePocketDNSError();
const [session, setSession] = useState(null);
const loadSession = useCallback(() => {
return handleAsync(async () => {
const sessionData = await createUserSession(userIdentifier);
setSession(sessionData);
return sessionData;
});
}, [userIdentifier, handleAsync]);
if (error) {
return (
<div className="error-state">
<p>Error: {error.message}</p>
<button onClick={loadSession}>Retry</button>
</div>
);
}
if (isLoading) {
return <div>Loading...</div>;
}
return session ? (
<iframe src={session.login_url} />
) : (
<button onClick={loadSession}>Load Domain Interface</button>
);
};Webhook Error Handling
Handle webhook delivery failures:
javascript
app.post('/webhook/pocketdns', async (req, res) => {
try {
// Verify webhook signature
if (!verifyWebhookSignature(req.body, req.headers['pocketdns-signature'])) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process webhook
await processWebhook(req.body);
res.status(200).json({ received: true });
} catch (error) {
console.error('Webhook processing failed:', error);
// Return appropriate status code
if (error.name === 'ValidationError') {
res.status(400).json({ error: 'Invalid webhook data' });
} else if (error.name === 'DatabaseError') {
res.status(500).json({ error: 'Database error' });
} else {
res.status(500).json({ error: 'Internal error' });
}
// Track error for monitoring
trackWebhookError(error, req.body);
}
});Error Monitoring
Set up comprehensive error monitoring:
javascript
const errorMetrics = {
total: 0,
byType: {},
byEndpoint: {},
recentErrors: []
};
const trackAPIError = (error, context) => {
errorMetrics.total++;
errorMetrics.byType[error.type] = (errorMetrics.byType[error.type] || 0) + 1;
errorMetrics.byEndpoint[context] = (errorMetrics.byEndpoint[context] || 0) + 1;
errorMetrics.recentErrors.push({
error: error.message,
type: error.type,
context,
timestamp: new Date().toISOString()
});
// Keep only last 100 errors
if (errorMetrics.recentErrors.length > 100) {
errorMetrics.recentErrors = errorMetrics.recentErrors.slice(-100);
}
// Alert on high error rate
const recentErrorCount = errorMetrics.recentErrors.filter(
e => Date.now() - new Date(e.timestamp).getTime() < 300000 // 5 minutes
).length;
if (recentErrorCount > 10) {
sendAlert(`High error rate: ${recentErrorCount} errors in 5 minutes`);
}
};
// Expose metrics endpoint
app.get('/metrics/pocketdns-errors', (req, res) => {
res.json(errorMetrics);
});Best Practices
- Always handle errors gracefully - Never let unhandled errors crash your application
- Provide meaningful error messages - Help users understand what went wrong
- Implement retry logic - For transient errors like rate limits and server errors
- Log errors appropriately - Include context but never log sensitive data
- Monitor error rates - Set up alerts for unusual error patterns
- Test error scenarios - Include error cases in your test suite
- Fail fast - Don't continue processing if critical errors occur
- Use circuit breakers - Prevent cascading failures in distributed systems
- Cache when possible - Reduce API calls that might fail
- Have fallback plans - Consider what happens when the API is unavailable
