API Error Handling - arilonUK/iotagentmesh GitHub Wiki
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.
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.
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
}
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 |
// 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.')
}
}
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))
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
}
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.'
}
// 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,
}
)
}
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}")
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);
}
}
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
}
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)
}
// 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')
}
})
}
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)
}
}
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
}
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
}
}
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'
}
}
}
// 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
}
}
// 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
}
}
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
}
}
- Check Status Page: https://status.iotagentmesh.com
- Review Documentation: Ensure you're following API specifications correctly
- Search Community: Check GitHub discussions for similar issues
- Contact Support: Include request_id from error responses
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 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
}
- Authentication API - Authentication error handling
- Rate Limiting - Rate limiting policies and handling
- API Home - General API usage guidelines
- SDKs & Libraries - Client library error handling
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 HandlingThis 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.
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.
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
}
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 |
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 |
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 |
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 |
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
}
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
}
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
}
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
}
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 |
// 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.')
}
}
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))
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
}
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.'
}
// 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,
}
)
}
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}")
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);
}
}
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
}
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)
}
// 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')
}
})
}
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)
}
}
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
}
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
}
}
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'
}
}
}
// 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
}
}
// 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
}
}
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
}
}
- Check Status Page: https://status.iotagentmesh.com
- Review Documentation: Ensure you're following API specifications correctly
- Search Community: Check GitHub discussions for similar issues
- Contact Support: Include request_id from error responses
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 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
}
- [Authentication API](API-Authentication.md) - Authentication error handling
- [Rate Limiting](API-Rate-Limiting.md) - Rate limiting policies and handling
- [API Home](API-Home.md) - General API usage guidelines
- [SDKs & Libraries](API-SDKs.md) - Client library error handling
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.