Skip to content

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

FieldTypeDescription
errorstringGeneral error category
messagestringHuman-readable error message
detailsobjectAdditional error details (optional)
request_idstringUnique 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

  1. Always handle errors gracefully - Never let unhandled errors crash your application
  2. Provide meaningful error messages - Help users understand what went wrong
  3. Implement retry logic - For transient errors like rate limits and server errors
  4. Log errors appropriately - Include context but never log sensitive data
  5. Monitor error rates - Set up alerts for unusual error patterns
  6. Test error scenarios - Include error cases in your test suite
  7. Fail fast - Don't continue processing if critical errors occur
  8. Use circuit breakers - Prevent cascading failures in distributed systems
  9. Cache when possible - Reduce API calls that might fail
  10. Have fallback plans - Consider what happens when the API is unavailable

Built with ❤️ for PocketDNS Partners