API Error Handling - arilonUK/iotagentmesh GitHub Wiki

Error Handling

This guide provides comprehensive information about error handling in the IoT Agent Mesh API, including error codes, response formats, troubleshooting strategies, and best practices for robust error handling in client applications.

Overview

The IoT Agent Mesh API uses conventional HTTP response codes to indicate the success or failure of API requests. Error responses include detailed information to help developers identify and resolve issues quickly.

Error Response Format

All error responses follow a consistent JSON structure:

{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable error description",
    "details": {
      "field": "specific_field_name",
      "value": "problematic_value",
      "reason": "Detailed explanation of the issue"
    },
    "request_id": "req_01H8K3L4M5N6O7P8Q9R0S1T2U3V4",
    "timestamp": "2024-07-23T16:45:00Z"
  },
  "status": 400
}

Error Response Fields

Field Type Description
error.code string Machine-readable error code
error.message string Human-readable error description
error.details object Additional context about the error
error.request_id string Unique request identifier for support
error.timestamp string ISO 8601 timestamp when error occurred
status integer HTTP status code

Error Handling Best Practices

Client-Side Error Handling

1. Implement Proper Error Detection

// JavaScript/TypeScript example
async function createAgent(agentData) {
  try {
    const { data, error } = await supabase
      .from('agents')
      .insert(agentData)
      .select()
      .single()
if (error) {
  handleSupabaseError(error)
  return null
}

return data

} catch (error) { handleNetworkError(error) return null } }

function handleSupabaseError(error) { switch (error.code) { case '23505': // Unique constraint violation showUserMessage('Agent name already exists') break case 'PGRST116': // Not found showUserMessage('Resource not found') break default: console.error('Database error:', error) showUserMessage('An error occurred. Please try again.') } }

2. Implement Retry Logic with Exponential Backoff

async function apiRequestWithRetry(requestFn, maxRetries = 3) {
  let lastError

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

  // Don't retry client errors (4xx) except 429
  if (error.status &gt;= 400 &amp;&amp; error.status &lt; 500 &amp;&amp; error.status !== 429) {
    throw error
  }
  
  if (attempt === maxRetries) {
    throw error
  }
  
  // Exponential backoff: 1s, 2s, 4s, 8s...
  const delay = Math.pow(2, attempt - 1) * 1000
  await new Promise(resolve =&gt; setTimeout(resolve, delay))
}

}

throw lastError }

// Usage const agent = await apiRequestWithRetry(() => createAgent(agentData))

3. Handle Rate Limiting

async function handleRateLimiting(error) {
  if (error.status === 429) {
    const retryAfter = error.headers?.['retry-after'] || 
                      error.error?.details?.retry_after_seconds || 
                      60
console.log(`Rate limited. Retrying after ${retryAfter} seconds`)

await new Promise(resolve =&gt; setTimeout(resolve, retryAfter * 1000))

// Retry the original request
return true

}

return false }

4. User-Friendly Error Messages

function getUserFriendlyMessage(error) {
  const errorMessages = {
    'VALIDATION_ERROR': 'Please check your input and try again.',
    'NOT_FOUND': 'The requested item could not be found.',
    'ALREADY_EXISTS': 'An item with this name already exists.',
    'RATE_LIMIT_EXCEEDED': 'Too many requests. Please wait a moment and try again.',
    'INSUFFICIENT_PERMISSIONS': 'You don\'t have permission to perform this action.',
    'TOKEN_EXPIRED': 'Your session has expired. Please sign in again.',
    'AGENT_OFFLINE': 'This device is currently offline. Please try again when it\'s online.'
  }

return errorMessages[error.code] || 'An unexpected error occurred. Please try again.' }

Server-Side Error Handling (Edge Functions)

// Edge Function error handling
import { serve } from 'https://deno.land/[email protected]/http/server.ts'

const corsHeaders = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', }

class APIError extends Error { constructor(code: string, message: string, status: number = 400, details?: any) { super(message) this.name = 'APIError' this.code = code this.status = status this.details = details } }

serve(async (req) => { try { // Handle CORS preflight if (req.method === 'OPTIONS') { return new Response('ok', { headers: corsHeaders }) }

// Validate authentication
const authHeader = req.headers.get('Authorization')
if (!authHeader) {
  throw new APIError('UNAUTHORIZED', 'Missing authorization header', 401)
}

// Validate request body
const body = await req.json()
validateRequestBody(body)

// Process request
const result = await processRequest(body)

return new Response(
  JSON.stringify(result),
  {
    headers: { ...corsHeaders, 'Content-Type': 'application/json' },
    status: 200,
  }
)

} catch (error) { return handleError(error) } })

function validateRequestBody(body: any) { if (!body.agent_id) { throw new APIError( 'MISSING_REQUIRED_FIELD', 'Agent ID is required', 400, { field: 'agent_id', required: true } ) }

if (typeof body.agent_id !== 'string') { throw new APIError( 'INVALID_FORMAT', 'Agent ID must be a string', 400, { field: 'agent_id', expected_type: 'string', received_type: typeof body.agent_id } ) } }

function handleError(error: any): Response { console.error('Function error:', error)

let errorResponse = { error: { code: 'INTERNAL_ERROR', message: 'An internal error occurred', request_id: crypto.randomUUID(), timestamp: new Date().toISOString() } }

let status = 500

if (error instanceof APIError) { errorResponse.error.code = error.code errorResponse.error.message = error.message if (error.details) { errorResponse.error.details = error.details } status = error.status } else if (error.message?.includes('JWT')) { errorResponse.error.code = 'UNAUTHORIZED' errorResponse.error.message = 'Invalid or expired token' status = 401 } else if (error.code === '23505') { errorResponse.error.code = 'ALREADY_EXISTS' errorResponse.error.message = 'Resource already exists' status = 409 }

return new Response( JSON.stringify(errorResponse), { headers: { ...corsHeaders, 'Content-Type': 'application/json' }, status, } ) }


Language-Specific Error Handling

Python

import requests
from typing import Optional, Dict, Any
import time
import random

class IOTAgentMeshError(Exception): def init(self, code: str, message: str, status: int, details: Optional[Dict] = None): self.code = code self.message = message self.status = status self.details = details or {} super().init(f"{code}: {message}")

class IOTAgentMeshClient: def init(self, base_url: str, api_key: str): self.base_url = base_url self.headers = { 'Authorization': f'Bearer {api_key}', 'Content-Type': 'application/json' }

def _make_request(self, method: str, endpoint: str, data: Optional[Dict] = None, 
                 max_retries: int = 3) -&gt; Dict[Any, Any]:
    """Make API request with retry logic and error handling."""
    
    for attempt in range(max_retries + 1):
        try:
            response = requests.request(
                method=method,
                url=f"{self.base_url}{endpoint}",
                headers=self.headers,
                json=data,
                timeout=30
            )
            
            if response.status_code == 429:
                # Handle rate limiting
                retry_after = int(response.headers.get('Retry-After', 60))
                if attempt &lt; max_retries:
                    time.sleep(retry_after + random.uniform(0, 5))  # Add jitter
                    continue
            
            if not response.ok:
                error_data = response.json().get('error', {})
                raise IOTAgentMeshError(
                    code=error_data.get('code', 'UNKNOWN_ERROR'),
                    message=error_data.get('message', 'Unknown error occurred'),
                    status=response.status_code,
                    details=error_data.get('details')
                )
            
            return response.json()
            
        except requests.exceptions.RequestException as e:
            if attempt == max_retries:
                raise IOTAgentMeshError(
                    code='NETWORK_ERROR',
                    message=f'Network error: {str(e)}',
                    status=0
                )
            
            # Exponential backoff for network errors
            time.sleep(2 ** attempt + random.uniform(0, 1))

def create_agent(self, agent_data: Dict) -&gt; Dict:
    """Create a new IoT agent with proper error handling."""
    try:
        return self._make_request('POST', '/rest/v1/agents', agent_data)
    except IOTAgentMeshError as e:
        if e.code == 'VALIDATION_ERROR':
            print(f"Validation error: {e.details.get('reason', e.message)}")
        elif e.code == 'ALREADY_EXISTS':
            print("Agent with this name already exists")
        else:
            print(f"Error creating agent: {e.message}")
        raise

Usage example

client = IOTAgentMeshClient('https://your-project.supabase.co', 'your-api-key')

try: agent = client.create_agent({ 'name': 'Temperature Sensor 01', 'type': 'sensor', 'configuration': {'sampling_rate': 60000} }) print(f"Agent created: {agent['id']}") except IOTAgentMeshError as e: print(f"Failed to create agent: {e}")

Java

import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import com.fasterxml.jackson.databind.ObjectMapper;

public class IOTAgentMeshClient {

public static class APIException extends Exception {
    private final String code;
    private final int status;
    private final Object details;
    
    public APIException(String code, String message, int status, Object details) {
        super(message);
        this.code = code;
        this.status = status;
        this.details = details;
    }
    
    // Getters...
}

private final HttpClient httpClient;
private final String baseUrl;
private final String apiKey;
private final ObjectMapper objectMapper;

public IOTAgentMeshClient(String baseUrl, String apiKey) {
    this.baseUrl = baseUrl;
    this.apiKey = apiKey;
    this.httpClient = HttpClient.newBuilder()
        .connectTimeout(Duration.ofSeconds(10))
        .build();
    this.objectMapper = new ObjectMapper();
}

public &lt;T&gt; T makeRequest(String method, String endpoint, Object data, Class&lt;T&gt; responseType) 
        throws APIException, InterruptedException {
    
    int maxRetries = 3;
    
    for (int attempt = 0; attempt &lt;= maxRetries; attempt++) {
        try {
            HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
                .uri(URI.create(baseUrl + endpoint))
                .header("Authorization", "Bearer " + apiKey)
                .header("Content-Type", "application/json")
                .timeout(Duration.ofSeconds(30));
            
            if (data != null) {
                String jsonData = objectMapper.writeValueAsString(data);
                requestBuilder.method(method, HttpRequest.BodyPublishers.ofString(jsonData));
            } else {
                requestBuilder.method(method, HttpRequest.BodyPublishers.noBody());
            }
            
            HttpResponse&lt;String&gt; response = httpClient.send(
                requestBuilder.build(),
                HttpResponse.BodyHandlers.ofString()
            );
            
            if (response.statusCode() == 429) {
                // Handle rate limiting
                String retryAfter = response.headers().firstValue("Retry-After").orElse("60");
                if (attempt &lt; maxRetries) {
                    Thread.sleep(Integer.parseInt(retryAfter) * 1000 + 
                               (int)(Math.random() * 5000)); // Add jitter
                    continue;
                }
            }
            
            if (response.statusCode() &gt;= 400) {
                Map&lt;String, Object&gt; errorResponse = objectMapper.readValue(
                    response.body(), 
                    new TypeReference&lt;Map&lt;String, Object&gt;&gt;() {}
                );
                
                Map&lt;String, Object&gt; error = (Map&lt;String, Object&gt;) errorResponse.get("error");
                throw new APIException(
                    (String) error.get("code"),
                    (String) error.get("message"),
                    response.statusCode(),
                    error.get("details")
                );
            }
            
            return objectMapper.readValue(response.body(), responseType);
            
        } catch (IOException e) {
            if (attempt == maxRetries) {
                throw new APIException("NETWORK_ERROR", "Network error: " + e.getMessage(), 0, null);
            }
            
            // Exponential backoff
            Thread.sleep((long) Math.pow(2, attempt) * 1000 + (long)(Math.random() * 1000));
        }
    }
    
    throw new APIException("MAX_RETRIES_EXCEEDED", "Maximum retries exceeded", 0, null);
}

}


Debugging and Troubleshooting

Debug Mode

Enable debug mode to get additional error information (development only):

// Add debug headers to requests
const debugHeaders = {
  'X-Debug-Mode': 'true',
  'X-Client-Version': '1.0.0',
  'X-Request-ID': generateRequestId()
}

// Enhanced error response in debug mode { "error": { "code": "VALIDATION_ERROR", "message": "Invalid agent configuration", "details": { "field": "configuration.sampling_rate", "value": -1000, "reason": "Sampling rate must be a positive integer" }, "debug_info": { "sql_query": "INSERT INTO agents...", "execution_time_ms": 45, "stack_trace": "Error at line 23...", "database_error": "23514: Check constraint violation" } }, "status": 400 }

Request Logging

Implement comprehensive request logging for debugging:

function logRequest(method, url, data, response, error) {
  const logEntry = {
    timestamp: new Date().toISOString(),
    method,
    url,
    request_data: data,
    response_status: response?.status,
    response_data: response?.data,
    error: error ? {
      code: error.code,
      message: error.message,
      details: error.details
    } : null,
    user_id: getCurrentUserId(),
    organization_id: getCurrentOrganizationId()
  }

// Send to logging service or local storage console.log('API Request:', logEntry) }

Error Monitoring Integration

// Sentry integration example
import * as Sentry from '@sentry/browser'

function reportError(error, context) { Sentry.withScope((scope) => { scope.setTag('api_error', true) scope.setContext('api_request', context) scope.setLevel('error')

if (error.status &gt;= 500) {
  // Server errors
  Sentry.captureException(error)
} else if (error.code === 'RATE_LIMIT_EXCEEDED') {
  // Rate limiting - might indicate client issue
  Sentry.captureMessage(`Rate limit exceeded: ${error.message}`, 'warning')
}

}) }


Common Troubleshooting Scenarios

1. Authentication Issues

Problem: Getting 401 Unauthorized errors Solutions:

  • Verify JWT token is included in Authorization header
  • Check token expiration and refresh if needed
  • Ensure API key is valid and has proper permissions
  • Verify user has access to the requested organization
// Debug authentication
async function debugAuth() {
  const token = localStorage.getItem('supabase.auth.token')
  if (!token) {
    console.log('No token found - user needs to sign in')
    return
  }

try { const { data: user } = await supabase.auth.getUser() console.log('Current user:', user)

const { data: session } = await supabase.auth.getSession()
console.log('Session expires at:', new Date(session.expires_at * 1000))

} catch (error) { console.log('Auth error:', error) } }

2. Data Validation Failures

Problem: Getting 400 validation errors Solutions:

  • Check request body format matches API specification
  • Verify all required fields are included
  • Validate data types and formats
  • Check field length limits
// Validation helper
function validateAgentData(agentData) {
  const errors = []

if (!agentData.name || agentData.name.length < 2) { errors.push('Name must be at least 2 characters') }

if (!['sensor', 'gateway', 'controller'].includes(agentData.type)) { errors.push('Type must be sensor, gateway, or controller') }

if (agentData.configuration?.sampling_rate < 1000) { errors.push('Sampling rate must be at least 1000ms') }

return errors }

3. Rate Limiting Issues

Problem: Getting 429 Too Many Requests Solutions:

  • Implement exponential backoff retry logic
  • Reduce request frequency
  • Use batch operations where available
  • Consider upgrading API plan
// Rate limiting handler
class RateLimitHandler {
  constructor() {
    this.requestQueue = []
    this.isProcessing = false
  }

async enqueue(requestFn) { return new Promise((resolve, reject) => { this.requestQueue.push({ requestFn, resolve, reject }) this.processQueue() }) }

async processQueue() { if (this.isProcessing || this.requestQueue.length === 0) return

this.isProcessing = true

while (this.requestQueue.length &gt; 0) {
  const { requestFn, resolve, reject } = this.requestQueue.shift()
  
  try {
    const result = await requestFn()
    resolve(result)
  } catch (error) {
    if (error.status === 429) {
      const retryAfter = error.details?.retry_after_seconds || 60
      await new Promise(resolve =&gt; setTimeout(resolve, retryAfter * 1000))
      this.requestQueue.unshift({ requestFn, resolve, reject })
    } else {
      reject(error)
    }
  }
  
  // Add small delay between requests
  await new Promise(resolve =&gt; setTimeout(resolve, 100))
}

this.isProcessing = false

} }

4. Network and Connectivity Issues

Problem: Intermittent network failures Solutions:

  • Implement retry logic with exponential backoff
  • Check network connectivity
  • Use circuit breaker pattern for degraded service
  • Implement offline capability where appropriate
// Circuit breaker implementation
class CircuitBreaker {
  constructor(threshold = 5, timeout = 60000) {
    this.threshold = threshold
    this.timeout = timeout
    this.failureCount = 0
    this.lastFailureTime = null
    this.state = 'CLOSED' // CLOSED, OPEN, HALF_OPEN
  }

async execute(operation) { if (this.state === 'OPEN') { if (Date.now() - this.lastFailureTime > this.timeout) { this.state = 'HALF_OPEN' } else { throw new Error('Circuit breaker is OPEN') } }

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

}

onSuccess() { this.failureCount = 0 this.state = 'CLOSED' }

onFailure() { this.failureCount++ this.lastFailureTime = Date.now()

if (this.failureCount &gt;= this.threshold) {
  this.state = 'OPEN'
}

} }


Error Recovery Strategies

1. Graceful Degradation

// Implement fallback behavior when API is unavailable
async function getAgentData(agentId) {
  try {
    // Try to get live data
    const { data } = await supabase
      .from('agents')
      .select('*')
      .eq('id', agentId)
      .single()
return data

} catch (error) { if (error.status >= 500 || !navigator.onLine) { // Fall back to cached data const cachedData = localStorage.getItem(agent_${agentId}) if (cachedData) { console.log('Using cached data due to API unavailability') return JSON.parse(cachedData) } }

// Show user-friendly error message
showErrorMessage('Unable to load agent data. Please try again later.')
throw error

} }

2. Optimistic Updates

// Update UI immediately, rollback on error
async function updateAgentConfig(agentId, newConfig) {
  // Optimistically update UI
  updateAgentInUI(agentId, newConfig)

try { const { data } = await supabase .from('agents') .update({ configuration: newConfig }) .eq('id', agentId) .select() .single()

// Confirm update with server response
updateAgentInUI(agentId, data.configuration)
showSuccessMessage('Configuration updated')

} catch (error) { // Rollback optimistic update rollbackAgentUpdate(agentId) showErrorMessage('Failed to update configuration') throw error } }

3. Error Boundaries (React)

import React from 'react'

class ErrorBoundary 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('Error boundary caught error:', error, errorInfo)

// Report to error tracking service
reportError(error, {
  component: 'ErrorBoundary',
  errorInfo,
  userId: this.props.userId
})

}

render() { if (this.state.hasError) { return ( <div className="error-fallback"> <h2>Something went wrong</h2> <p>We're sorry, but something unexpected happened.</p> <button onClick={() => window.location.reload()}> Reload Page </button> </div> ) }

return this.props.children

} }


Support and Debugging Resources

Getting Help

  1. Check Status Page: https://status.iotagentmesh.com
  2. Review Documentation: Ensure you're following API specifications correctly
  3. Search Community: Check GitHub discussions for similar issues
  4. Contact Support: Include request_id from error responses

Useful Debug Information

When reporting issues, include:

  • Request ID from error response
  • Complete error response body
  • Request method, URL, and body
  • Authentication method used
  • Timestamp of the issue
  • Client library and version
  • Steps to reproduce

Error Tracking Template

// Error reporting template
function reportIssue(error, context) {
  const report = {
    timestamp: new Date().toISOString(),
    error: {
      code: error.code,
      message: error.message,
      status: error.status,
      details: error.details,
      request_id: error.request_id
    },
    context: {
      user_id: getCurrentUserId(),
      organization_id: getCurrentOrganizationId(),
      url: window.location.href,
      user_agent: navigator.userAgent,
      request_method: context.method,
      request_url: context.url,
      request_body: context.body
    },
    environment: {
      app_version: '1.0.0',
      api_version: 'v1',
      browser: getBrowserInfo(),
      network_status: navigator.onLine ? 'online' : 'offline'
    }
  }

// Send to support or save locally console.error('Issue Report:', report) return report }


Related Resources

Remember: Proper error handling is crucial for building robust IoT applications. Always implement retry logic, user-friendly error messages, and comprehensive logging for production applications.

# Error Handling

This guide provides comprehensive information about error handling in the IoT Agent Mesh API, including error codes, response formats, troubleshooting strategies, and best practices for robust error handling in client applications.

Overview

The IoT Agent Mesh API uses conventional HTTP response codes to indicate the success or failure of API requests. Error responses include detailed information to help developers identify and resolve issues quickly.

Error Response Format

All error responses follow a consistent JSON structure:

{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable error description",
    "details": {
      "field": "specific_field_name",
      "value": "problematic_value",
      "reason": "Detailed explanation of the issue"
    },
    "request_id": "req_01H8K3L4M5N6O7P8Q9R0S1T2U3V4",
    "timestamp": "2024-07-23T16:45:00Z"
  },
  "status": 400
}

Error Response Fields

Field Type Description
error.code string Machine-readable error code
error.message string Human-readable error description
error.details object Additional context about the error
error.request_id string Unique request identifier for support
error.timestamp string ISO 8601 timestamp when error occurred
status integer HTTP status code

HTTP Status Codes

Success Codes (2xx)

Code Description Usage
200 OK Successful GET, PATCH, DELETE requests
201 Created Successful POST requests that create resources
204 No Content Successful DELETE requests with no response body

Client Error Codes (4xx)

Code Description Common Causes
400 Bad Request Invalid request format, missing required fields
401 Unauthorized Missing, invalid, or expired JWT token
403 Forbidden Insufficient permissions for the requested operation
404 Not Found Resource does not exist or user lacks access
409 Conflict Resource already exists or constraint violation
422 Unprocessable Entity Valid request format but invalid data
429 Too Many Requests Rate limit exceeded

Server Error Codes (5xx)

Code Description Action Required
500 Internal Server Error Contact support with request_id
502 Bad Gateway Temporary service issue, retry with backoff
503 Service Unavailable Service maintenance, check status page
504 Gateway Timeout Request timeout, retry with backoff

Common Error Codes

Authentication Errors

Error Code HTTP Status Description Solution
UNAUTHORIZED 401 Missing or invalid authentication Include valid JWT token in Authorization header
TOKEN_EXPIRED 401 JWT token has expired Refresh token using refresh endpoint
INVALID_API_KEY 401 API key is invalid or revoked Generate new API key or check key format
INSUFFICIENT_PERMISSIONS 403 User lacks required permissions Check user role and permissions

Example:

{
  "error": {
    "code": "TOKEN_EXPIRED",
    "message": "JWT token has expired",
    "details": {
      "expired_at": "2024-07-23T15:30:00Z",
      "current_time": "2024-07-23T16:45:00Z"
    }
  },
  "status": 401
}

Validation Errors

Error Code HTTP Status Description Solution
VALIDATION_ERROR 400 Request data validation failed Check request format and required fields
INVALID_FORMAT 400 Data format is incorrect Verify data types and format specifications
MISSING_REQUIRED_FIELD 400 Required field is missing Include all required fields in request
FIELD_TOO_LONG 400 Field exceeds maximum length Reduce field length to within limits

Example:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid agent configuration",
    "details": {
      "field": "configuration.sampling_rate",
      "value": -1000,
      "reason": "Sampling rate must be a positive integer",
      "valid_range": "1-3600000"
    }
  },
  "status": 400
}

Resource Errors

Error Code HTTP Status Description Solution
NOT_FOUND 404 Resource does not exist Verify resource ID and user access
ALREADY_EXISTS 409 Resource already exists Use unique identifier or update existing resource
RESOURCE_LIMIT_EXCEEDED 403 Organization resource limit reached Upgrade plan or remove unused resources
DEPENDENCY_ERROR 422 Resource has dependencies that prevent operation Remove dependencies first

Example:

{
  "error": {
    "code": "NOT_FOUND",
    "message": "IoT agent not found",
    "details": {
      "resource_type": "agent",
      "resource_id": "agent_invalid_id",
      "organization_id": "org_01H8K2M3N4P5Q6R7S8T9U0V1W2"
    }
  },
  "status": 404
}

Rate Limiting Errors

Error Code HTTP Status Description Solution
RATE_LIMIT_EXCEEDED 429 Too many requests in time window Implement exponential backoff and retry
QUOTA_EXCEEDED 403 Monthly quota exceeded Upgrade plan or wait for quota reset
CONCURRENT_LIMIT_EXCEEDED 429 Too many concurrent requests Reduce concurrent request count

Example:

{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded for telemetry ingestion",
    "details": {
      "limit": 1000,
      "window_seconds": 60,
      "reset_at": "2024-07-23T16:46:00Z",
      "retry_after_seconds": 15
    }
  },
  "status": 429
}

Data Processing Errors

Error Code HTTP Status Description Solution
INVALID_TELEMETRY_DATA 422 Telemetry data format is invalid Check data structure and field types
AGENT_OFFLINE 422 Cannot perform operation on offline agent Wait for agent to come online
PROCESSING_ERROR 500 Server error during data processing Retry request or contact support
STORAGE_ERROR 500 Database storage error Contact support with request_id

Error Handling Best Practices

Client-Side Error Handling

1. Implement Proper Error Detection

// JavaScript/TypeScript example
async function createAgent(agentData) {
  try {
    const { data, error } = await supabase
      .from('agents')
      .insert(agentData)
      .select()
      .single()
    
    if (error) {
      handleSupabaseError(error)
      return null
    }
    
    return data
  } catch (error) {
    handleNetworkError(error)
    return null
  }
}

function handleSupabaseError(error) {
  switch (error.code) {
    case '23505': // Unique constraint violation
      showUserMessage('Agent name already exists')
      break
    case 'PGRST116': // Not found
      showUserMessage('Resource not found')
      break
    default:
      console.error('Database error:', error)
      showUserMessage('An error occurred. Please try again.')
  }
}

2. Implement Retry Logic with Exponential Backoff

async function apiRequestWithRetry(requestFn, maxRetries = 3) {
  let lastError
  
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await requestFn()
    } catch (error) {
      lastError = error
      
      // Don't retry client errors (4xx) except 429
      if (error.status >= 400 && error.status < 500 && error.status !== 429) {
        throw error
      }
      
      if (attempt === maxRetries) {
        throw error
      }
      
      // Exponential backoff: 1s, 2s, 4s, 8s...
      const delay = Math.pow(2, attempt - 1) * 1000
      await new Promise(resolve => setTimeout(resolve, delay))
    }
  }
  
  throw lastError
}

// Usage
const agent = await apiRequestWithRetry(() => createAgent(agentData))

3. Handle Rate Limiting

async function handleRateLimiting(error) {
  if (error.status === 429) {
    const retryAfter = error.headers?.['retry-after'] || 
                      error.error?.details?.retry_after_seconds || 
                      60
    
    console.log(`Rate limited. Retrying after ${retryAfter} seconds`)
    
    await new Promise(resolve => setTimeout(resolve, retryAfter * 1000))
    
    // Retry the original request
    return true
  }
  
  return false
}

4. User-Friendly Error Messages

function getUserFriendlyMessage(error) {
  const errorMessages = {
    'VALIDATION_ERROR': 'Please check your input and try again.',
    'NOT_FOUND': 'The requested item could not be found.',
    'ALREADY_EXISTS': 'An item with this name already exists.',
    'RATE_LIMIT_EXCEEDED': 'Too many requests. Please wait a moment and try again.',
    'INSUFFICIENT_PERMISSIONS': 'You don\'t have permission to perform this action.',
    'TOKEN_EXPIRED': 'Your session has expired. Please sign in again.',
    'AGENT_OFFLINE': 'This device is currently offline. Please try again when it\'s online.'
  }
  
  return errorMessages[error.code] || 'An unexpected error occurred. Please try again.'
}

Server-Side Error Handling (Edge Functions)

// Edge Function error handling
import { serve } from 'https://deno.land/[email protected]/http/server.ts'

const corsHeaders = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}

class APIError extends Error {
  constructor(code: string, message: string, status: number = 400, details?: any) {
    super(message)
    this.name = 'APIError'
    this.code = code
    this.status = status
    this.details = details
  }
}

serve(async (req) => {
  try {
    // Handle CORS preflight
    if (req.method === 'OPTIONS') {
      return new Response('ok', { headers: corsHeaders })
    }

    // Validate authentication
    const authHeader = req.headers.get('Authorization')
    if (!authHeader) {
      throw new APIError('UNAUTHORIZED', 'Missing authorization header', 401)
    }

    // Validate request body
    const body = await req.json()
    validateRequestBody(body)

    // Process request
    const result = await processRequest(body)

    return new Response(
      JSON.stringify(result),
      {
        headers: { ...corsHeaders, 'Content-Type': 'application/json' },
        status: 200,
      }
    )

  } catch (error) {
    return handleError(error)
  }
})

function validateRequestBody(body: any) {
  if (!body.agent_id) {
    throw new APIError(
      'MISSING_REQUIRED_FIELD',
      'Agent ID is required',
      400,
      { field: 'agent_id', required: true }
    )
  }

  if (typeof body.agent_id !== 'string') {
    throw new APIError(
      'INVALID_FORMAT',
      'Agent ID must be a string',
      400,
      { field: 'agent_id', expected_type: 'string', received_type: typeof body.agent_id }
    )
  }
}

function handleError(error: any): Response {
  console.error('Function error:', error)

  let errorResponse = {
    error: {
      code: 'INTERNAL_ERROR',
      message: 'An internal error occurred',
      request_id: crypto.randomUUID(),
      timestamp: new Date().toISOString()
    }
  }

  let status = 500

  if (error instanceof APIError) {
    errorResponse.error.code = error.code
    errorResponse.error.message = error.message
    if (error.details) {
      errorResponse.error.details = error.details
    }
    status = error.status
  } else if (error.message?.includes('JWT')) {
    errorResponse.error.code = 'UNAUTHORIZED'
    errorResponse.error.message = 'Invalid or expired token'
    status = 401
  } else if (error.code === '23505') {
    errorResponse.error.code = 'ALREADY_EXISTS'
    errorResponse.error.message = 'Resource already exists'
    status = 409
  }

  return new Response(
    JSON.stringify(errorResponse),
    {
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
      status,
    }
  )
}

Language-Specific Error Handling

Python

import requests
from typing import Optional, Dict, Any
import time
import random

class IOTAgentMeshError(Exception):
    def __init__(self, code: str, message: str, status: int, details: Optional[Dict] = None):
        self.code = code
        self.message = message
        self.status = status
        self.details = details or {}
        super().__init__(f"{code}: {message}")

class IOTAgentMeshClient:
    def __init__(self, base_url: str, api_key: str):
        self.base_url = base_url
        self.headers = {
            'Authorization': f'Bearer {api_key}',
            'Content-Type': 'application/json'
        }

    def _make_request(self, method: str, endpoint: str, data: Optional[Dict] = None, 
                     max_retries: int = 3) -> Dict[Any, Any]:
        """Make API request with retry logic and error handling."""
        
        for attempt in range(max_retries + 1):
            try:
                response = requests.request(
                    method=method,
                    url=f"{self.base_url}{endpoint}",
                    headers=self.headers,
                    json=data,
                    timeout=30
                )
                
                if response.status_code == 429:
                    # Handle rate limiting
                    retry_after = int(response.headers.get('Retry-After', 60))
                    if attempt < max_retries:
                        time.sleep(retry_after + random.uniform(0, 5))  # Add jitter
                        continue
                
                if not response.ok:
                    error_data = response.json().get('error', {})
                    raise IOTAgentMeshError(
                        code=error_data.get('code', 'UNKNOWN_ERROR'),
                        message=error_data.get('message', 'Unknown error occurred'),
                        status=response.status_code,
                        details=error_data.get('details')
                    )
                
                return response.json()
                
            except requests.exceptions.RequestException as e:
                if attempt == max_retries:
                    raise IOTAgentMeshError(
                        code='NETWORK_ERROR',
                        message=f'Network error: {str(e)}',
                        status=0
                    )
                
                # Exponential backoff for network errors
                time.sleep(2 ** attempt + random.uniform(0, 1))

    def create_agent(self, agent_data: Dict) -> Dict:
        """Create a new IoT agent with proper error handling."""
        try:
            return self._make_request('POST', '/rest/v1/agents', agent_data)
        except IOTAgentMeshError as e:
            if e.code == 'VALIDATION_ERROR':
                print(f"Validation error: {e.details.get('reason', e.message)}")
            elif e.code == 'ALREADY_EXISTS':
                print("Agent with this name already exists")
            else:
                print(f"Error creating agent: {e.message}")
            raise

# Usage example
client = IOTAgentMeshClient('https://your-project.supabase.co', 'your-api-key')

try:
    agent = client.create_agent({
        'name': 'Temperature Sensor 01',
        'type': 'sensor',
        'configuration': {'sampling_rate': 60000}
    })
    print(f"Agent created: {agent['id']}")
except IOTAgentMeshError as e:
    print(f"Failed to create agent: {e}")

Java

import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import com.fasterxml.jackson.databind.ObjectMapper;

public class IOTAgentMeshClient {
    
    public static class APIException extends Exception {
        private final String code;
        private final int status;
        private final Object details;
        
        public APIException(String code, String message, int status, Object details) {
            super(message);
            this.code = code;
            this.status = status;
            this.details = details;
        }
        
        // Getters...
    }
    
    private final HttpClient httpClient;
    private final String baseUrl;
    private final String apiKey;
    private final ObjectMapper objectMapper;
    
    public IOTAgentMeshClient(String baseUrl, String apiKey) {
        this.baseUrl = baseUrl;
        this.apiKey = apiKey;
        this.httpClient = HttpClient.newBuilder()
            .connectTimeout(Duration.ofSeconds(10))
            .build();
        this.objectMapper = new ObjectMapper();
    }
    
    public <T> T makeRequest(String method, String endpoint, Object data, Class<T> responseType) 
            throws APIException, InterruptedException {
        
        int maxRetries = 3;
        
        for (int attempt = 0; attempt <= maxRetries; attempt++) {
            try {
                HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
                    .uri(URI.create(baseUrl + endpoint))
                    .header("Authorization", "Bearer " + apiKey)
                    .header("Content-Type", "application/json")
                    .timeout(Duration.ofSeconds(30));
                
                if (data != null) {
                    String jsonData = objectMapper.writeValueAsString(data);
                    requestBuilder.method(method, HttpRequest.BodyPublishers.ofString(jsonData));
                } else {
                    requestBuilder.method(method, HttpRequest.BodyPublishers.noBody());
                }
                
                HttpResponse<String> response = httpClient.send(
                    requestBuilder.build(),
                    HttpResponse.BodyHandlers.ofString()
                );
                
                if (response.statusCode() == 429) {
                    // Handle rate limiting
                    String retryAfter = response.headers().firstValue("Retry-After").orElse("60");
                    if (attempt < maxRetries) {
                        Thread.sleep(Integer.parseInt(retryAfter) * 1000 + 
                                   (int)(Math.random() * 5000)); // Add jitter
                        continue;
                    }
                }
                
                if (response.statusCode() >= 400) {
                    Map<String, Object> errorResponse = objectMapper.readValue(
                        response.body(), 
                        new TypeReference<Map<String, Object>>() {}
                    );
                    
                    Map<String, Object> error = (Map<String, Object>) errorResponse.get("error");
                    throw new APIException(
                        (String) error.get("code"),
                        (String) error.get("message"),
                        response.statusCode(),
                        error.get("details")
                    );
                }
                
                return objectMapper.readValue(response.body(), responseType);
                
            } catch (IOException e) {
                if (attempt == maxRetries) {
                    throw new APIException("NETWORK_ERROR", "Network error: " + e.getMessage(), 0, null);
                }
                
                // Exponential backoff
                Thread.sleep((long) Math.pow(2, attempt) * 1000 + (long)(Math.random() * 1000));
            }
        }
        
        throw new APIException("MAX_RETRIES_EXCEEDED", "Maximum retries exceeded", 0, null);
    }
}

Debugging and Troubleshooting

Debug Mode

Enable debug mode to get additional error information (development only):

// Add debug headers to requests
const debugHeaders = {
  'X-Debug-Mode': 'true',
  'X-Client-Version': '1.0.0',
  'X-Request-ID': generateRequestId()
}

// Enhanced error response in debug mode
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid agent configuration",
    "details": {
      "field": "configuration.sampling_rate",
      "value": -1000,
      "reason": "Sampling rate must be a positive integer"
    },
    "debug_info": {
      "sql_query": "INSERT INTO agents...",
      "execution_time_ms": 45,
      "stack_trace": "Error at line 23...",
      "database_error": "23514: Check constraint violation"
    }
  },
  "status": 400
}

Request Logging

Implement comprehensive request logging for debugging:

function logRequest(method, url, data, response, error) {
  const logEntry = {
    timestamp: new Date().toISOString(),
    method,
    url,
    request_data: data,
    response_status: response?.status,
    response_data: response?.data,
    error: error ? {
      code: error.code,
      message: error.message,
      details: error.details
    } : null,
    user_id: getCurrentUserId(),
    organization_id: getCurrentOrganizationId()
  }
  
  // Send to logging service or local storage
  console.log('API Request:', logEntry)
}

Error Monitoring Integration

// Sentry integration example
import * as Sentry from '@sentry/browser'

function reportError(error, context) {
  Sentry.withScope((scope) => {
    scope.setTag('api_error', true)
    scope.setContext('api_request', context)
    scope.setLevel('error')
    
    if (error.status >= 500) {
      // Server errors
      Sentry.captureException(error)
    } else if (error.code === 'RATE_LIMIT_EXCEEDED') {
      // Rate limiting - might indicate client issue
      Sentry.captureMessage(`Rate limit exceeded: ${error.message}`, 'warning')
    }
  })
}

Common Troubleshooting Scenarios

1. Authentication Issues

Problem: Getting 401 Unauthorized errors Solutions:

  • Verify JWT token is included in Authorization header
  • Check token expiration and refresh if needed
  • Ensure API key is valid and has proper permissions
  • Verify user has access to the requested organization
// Debug authentication
async function debugAuth() {
  const token = localStorage.getItem('supabase.auth.token')
  if (!token) {
    console.log('No token found - user needs to sign in')
    return
  }
  
  try {
    const { data: user } = await supabase.auth.getUser()
    console.log('Current user:', user)
    
    const { data: session } = await supabase.auth.getSession()
    console.log('Session expires at:', new Date(session.expires_at * 1000))
  } catch (error) {
    console.log('Auth error:', error)
  }
}

2. Data Validation Failures

Problem: Getting 400 validation errors Solutions:

  • Check request body format matches API specification
  • Verify all required fields are included
  • Validate data types and formats
  • Check field length limits
// Validation helper
function validateAgentData(agentData) {
  const errors = []
  
  if (!agentData.name || agentData.name.length < 2) {
    errors.push('Name must be at least 2 characters')
  }
  
  if (!['sensor', 'gateway', 'controller'].includes(agentData.type)) {
    errors.push('Type must be sensor, gateway, or controller')
  }
  
  if (agentData.configuration?.sampling_rate < 1000) {
    errors.push('Sampling rate must be at least 1000ms')
  }
  
  return errors
}

3. Rate Limiting Issues

Problem: Getting 429 Too Many Requests Solutions:

  • Implement exponential backoff retry logic
  • Reduce request frequency
  • Use batch operations where available
  • Consider upgrading API plan
// Rate limiting handler
class RateLimitHandler {
  constructor() {
    this.requestQueue = []
    this.isProcessing = false
  }
  
  async enqueue(requestFn) {
    return new Promise((resolve, reject) => {
      this.requestQueue.push({ requestFn, resolve, reject })
      this.processQueue()
    })
  }
  
  async processQueue() {
    if (this.isProcessing || this.requestQueue.length === 0) return
    
    this.isProcessing = true
    
    while (this.requestQueue.length > 0) {
      const { requestFn, resolve, reject } = this.requestQueue.shift()
      
      try {
        const result = await requestFn()
        resolve(result)
      } catch (error) {
        if (error.status === 429) {
          const retryAfter = error.details?.retry_after_seconds || 60
          await new Promise(resolve => setTimeout(resolve, retryAfter * 1000))
          this.requestQueue.unshift({ requestFn, resolve, reject })
        } else {
          reject(error)
        }
      }
      
      // Add small delay between requests
      await new Promise(resolve => setTimeout(resolve, 100))
    }
    
    this.isProcessing = false
  }
}

4. Network and Connectivity Issues

Problem: Intermittent network failures Solutions:

  • Implement retry logic with exponential backoff
  • Check network connectivity
  • Use circuit breaker pattern for degraded service
  • Implement offline capability where appropriate
// Circuit breaker implementation
class CircuitBreaker {
  constructor(threshold = 5, timeout = 60000) {
    this.threshold = threshold
    this.timeout = timeout
    this.failureCount = 0
    this.lastFailureTime = null
    this.state = 'CLOSED' // CLOSED, OPEN, HALF_OPEN
  }
  
  async execute(operation) {
    if (this.state === 'OPEN') {
      if (Date.now() - this.lastFailureTime > this.timeout) {
        this.state = 'HALF_OPEN'
      } else {
        throw new Error('Circuit breaker is OPEN')
      }
    }
    
    try {
      const result = await operation()
      this.onSuccess()
      return result
    } catch (error) {
      this.onFailure()
      throw error
    }
  }
  
  onSuccess() {
    this.failureCount = 0
    this.state = 'CLOSED'
  }
  
  onFailure() {
    this.failureCount++
    this.lastFailureTime = Date.now()
    
    if (this.failureCount >= this.threshold) {
      this.state = 'OPEN'
    }
  }
}

Error Recovery Strategies

1. Graceful Degradation

// Implement fallback behavior when API is unavailable
async function getAgentData(agentId) {
  try {
    // Try to get live data
    const { data } = await supabase
      .from('agents')
      .select('*')
      .eq('id', agentId)
      .single()
    
    return data
  } catch (error) {
    if (error.status >= 500 || !navigator.onLine) {
      // Fall back to cached data
      const cachedData = localStorage.getItem(`agent_${agentId}`)
      if (cachedData) {
        console.log('Using cached data due to API unavailability')
        return JSON.parse(cachedData)
      }
    }
    
    // Show user-friendly error message
    showErrorMessage('Unable to load agent data. Please try again later.')
    throw error
  }
}

2. Optimistic Updates

// Update UI immediately, rollback on error
async function updateAgentConfig(agentId, newConfig) {
  // Optimistically update UI
  updateAgentInUI(agentId, newConfig)
  
  try {
    const { data } = await supabase
      .from('agents')
      .update({ configuration: newConfig })
      .eq('id', agentId)
      .select()
      .single()
    
    // Confirm update with server response
    updateAgentInUI(agentId, data.configuration)
    showSuccessMessage('Configuration updated')
    
  } catch (error) {
    // Rollback optimistic update
    rollbackAgentUpdate(agentId)
    showErrorMessage('Failed to update configuration')
    throw error
  }
}

3. Error Boundaries (React)

import React from 'react'

class ErrorBoundary 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('Error boundary caught error:', error, errorInfo)
    
    // Report to error tracking service
    reportError(error, {
      component: 'ErrorBoundary',
      errorInfo,
      userId: this.props.userId
    })
  }
  
  render() {
    if (this.state.hasError) {
      return (
        <div className="error-fallback">
          <h2>Something went wrong</h2>
          <p>We're sorry, but something unexpected happened.</p>
          <button onClick={() => window.location.reload()}>
            Reload Page
          </button>
        </div>
      )
    }
    
    return this.props.children
  }
}

Support and Debugging Resources

Getting Help

  1. Check Status Page: https://status.iotagentmesh.com
  2. Review Documentation: Ensure you're following API specifications correctly
  3. Search Community: Check GitHub discussions for similar issues
  4. Contact Support: Include request_id from error responses

Useful Debug Information

When reporting issues, include:

  • Request ID from error response
  • Complete error response body
  • Request method, URL, and body
  • Authentication method used
  • Timestamp of the issue
  • Client library and version
  • Steps to reproduce

Error Tracking Template

// Error reporting template
function reportIssue(error, context) {
  const report = {
    timestamp: new Date().toISOString(),
    error: {
      code: error.code,
      message: error.message,
      status: error.status,
      details: error.details,
      request_id: error.request_id
    },
    context: {
      user_id: getCurrentUserId(),
      organization_id: getCurrentOrganizationId(),
      url: window.location.href,
      user_agent: navigator.userAgent,
      request_method: context.method,
      request_url: context.url,
      request_body: context.body
    },
    environment: {
      app_version: '1.0.0',
      api_version: 'v1',
      browser: getBrowserInfo(),
      network_status: navigator.onLine ? 'online' : 'offline'
    }
  }
  
  // Send to support or save locally
  console.error('Issue Report:', report)
  return report
}

Related Resources

Remember: Proper error handling is crucial for building robust IoT applications. Always implement retry logic, user-friendly error messages, and comprehensive logging for production applications.

⚠️ **GitHub.com Fallback** ⚠️