Error Handling

Understand API error codes, responses, and how to handle errors gracefully.

Overview

The ArcanFlows API uses conventional HTTP status codes and returns detailed error information in JSON format to help you handle errors gracefully.

Error Response Format

All errors follow a consistent JSON structure:

json
{
  "error": {
    "code": "error_code",
    "message": "Human-readable error description",
    "details": {
      "field": "specific_field",
      "reason": "additional_context"
    },
    "request_id": "req_abc123xyz"
  }
}

Fields

FieldTypeDescription
codestringMachine-readable error code
messagestringHuman-readable description
detailsobjectAdditional context (optional)
request_idstringUnique request ID for support

HTTP Status Codes

Success Codes (2xx)

CodeStatusDescription
200OKRequest succeeded
201CreatedResource created successfully
202AcceptedRequest accepted for processing
204No ContentRequest succeeded, no content returned

Client Error Codes (4xx)

CodeStatusDescription
400Bad RequestInvalid request format or parameters
401UnauthorizedMissing or invalid API key
403ForbiddenValid key but insufficient permissions
404Not FoundResource doesn't exist
409ConflictResource conflict (e.g., duplicate)
422Unprocessable EntityValidation failed
429Too Many RequestsRate limit exceeded

Server Error Codes (5xx)

CodeStatusDescription
500Internal Server ErrorUnexpected server error
502Bad GatewayUpstream service error
503Service UnavailableTemporary unavailability
504Gateway TimeoutRequest timed out

Error Codes Reference

Authentication Errors

unauthorized

json
{
  "error": {
    "code": "unauthorized",
    "message": "Invalid API key provided",
    "details": {
      "reason": "api_key_invalid"
    }
  }
}

Causes:

  • Missing Authorization header
  • Invalid or malformed API key
  • Expired API key
  • Deleted API key

Solution: Check your API key and ensure it's correctly formatted.

forbidden

json
{
  "error": {
    "code": "forbidden",
    "message": "API key lacks required scope",
    "details": {
      "required_scope": "agents:write",
      "available_scopes": ["agents:read"]
    }
  }
}

Solution: Create a new API key with the required scopes.

Validation Errors

validation_error

json
{
  "error": {
    "code": "validation_error",
    "message": "Validation failed",
    "details": {
      "errors": [
        {
          "field": "name",
          "message": "Name is required",
          "code": "required"
        },
        {
          "field": "model",
          "message": "Invalid model: 'gpt-5'. Allowed: gpt-4, gpt-3.5-turbo, claude-3-sonnet",
          "code": "invalid_enum"
        }
      ]
    }
  }
}

Common validation codes:

  • required - Field is required
  • invalid_type - Wrong data type
  • invalid_enum - Value not in allowed list
  • min_length - String too short
  • max_length - String too long
  • min_value - Number too small
  • max_value - Number too large
  • invalid_format - Invalid format (email, URL, etc.)

invalid_request

json
{
  "error": {
    "code": "invalid_request",
    "message": "Request body must be valid JSON",
    "details": {
      "parse_error": "Unexpected token at position 42"
    }
  }
}

Resource Errors

not_found

json
{
  "error": {
    "code": "not_found",
    "message": "Agent not found",
    "details": {
      "resource_type": "agent",
      "resource_id": "agent_123abc"
    }
  }
}

conflict

json
{
  "error": {
    "code": "conflict",
    "message": "An agent with this name already exists",
    "details": {
      "field": "name",
      "value": "Support Bot"
    }
  }
}

resource_exhausted

json
{
  "error": {
    "code": "resource_exhausted",
    "message": "Agent limit reached for your plan",
    "details": {
      "resource": "agents",
      "limit": 10,
      "current": 10,
      "plan": "pro"
    }
  }
}

Rate Limit Errors

rate_limited

json
{
  "error": {
    "code": "rate_limited",
    "message": "Rate limit exceeded",
    "details": {
      "limit": 300,
      "window_seconds": 60,
      "retry_after": 23
    }
  }
}

Execution Errors

execution_failed

json
{
  "error": {
    "code": "execution_failed",
    "message": "Workflow execution failed",
    "details": {
      "execution_id": "exec_abc123",
      "node_id": "http_request_1",
      "node_error": "Connection timeout after 30s"
    }
  }
}

execution_timeout

json
{
  "error": {
    "code": "execution_timeout",
    "message": "Workflow execution timed out",
    "details": {
      "execution_id": "exec_abc123",
      "timeout_seconds": 300,
      "elapsed_seconds": 300
    }
  }
}

AI/Model Errors

model_error

json
{
  "error": {
    "code": "model_error",
    "message": "AI model request failed",
    "details": {
      "provider": "openai",
      "model": "gpt-4",
      "provider_error": "Rate limit exceeded"
    }
  }
}

context_length_exceeded

json
{
  "error": {
    "code": "context_length_exceeded",
    "message": "Input exceeds model context length",
    "details": {
      "model": "gpt-4",
      "max_tokens": 8192,
      "input_tokens": 9500
    }
  }
}

Server Errors

internal_error

json
{
  "error": {
    "code": "internal_error",
    "message": "An unexpected error occurred",
    "request_id": "req_abc123xyz"
  }
}

Action: Retry the request. If persistent, contact support with the request_id.

service_unavailable

json
{
  "error": {
    "code": "service_unavailable",
    "message": "Service temporarily unavailable",
    "details": {
      "retry_after": 60
    }
  }
}

Handling Errors

JavaScript/TypeScript

typescript
interface APIError {
  error: {
    code: string;
    message: string;
    details?: Record<string, unknown>;
    request_id?: string;
  };
}

async function apiRequest(url: string, options: RequestInit) {
  const response = await fetch(url, options);

  if (!response.ok) {
    const error: APIError = await response.json();

    switch (error.error.code) {
      case 'unauthorized':
        throw new AuthenticationError(error.error.message);

      case 'forbidden':
        throw new PermissionError(error.error.message);

      case 'validation_error':
        throw new ValidationError(error.error.message, error.error.details);

      case 'not_found':
        throw new NotFoundError(error.error.message);

      case 'rate_limited':
        const retryAfter = error.error.details?.retry_after || 60;
        throw new RateLimitError(error.error.message, retryAfter);

      default:
        throw new APIError(error.error.message, error.error.code);
    }
  }

  return response.json();
}

// Usage
try {
  const agent = await apiRequest('/v1/agents', {
    method: 'POST',
    body: JSON.stringify({ name: 'My Agent' }),
  });
} catch (error) {
  if (error instanceof ValidationError) {
    // Show validation errors to user
    console.error('Validation failed:', error.details);
  } else if (error instanceof RateLimitError) {
    // Wait and retry
    await sleep(error.retryAfter * 1000);
  } else {
    // Log and show generic error
    console.error('API error:', error);
  }
}

Python

python
import requests
from dataclasses import dataclass
from typing import Optional, Dict, Any

@dataclass
class APIError(Exception):
    code: str
    message: str
    details: Optional[Dict[str, Any]] = None
    request_id: Optional[str] = None

def api_request(method: str, url: str, **kwargs) -> dict:
    response = requests.request(method, url, **kwargs)

    if not response.ok:
        error_data = response.json().get('error', {})

        raise APIError(
            code=error_data.get('code', 'unknown'),
            message=error_data.get('message', 'Unknown error'),
            details=error_data.get('details'),
            request_id=error_data.get('request_id'),
        )

    return response.json()

# Usage
try:
    agent = api_request('POST', '/v1/agents', json={'name': 'My Agent'})
except APIError as e:
    if e.code == 'validation_error':
        print(f"Validation failed: {e.details}")
    elif e.code == 'rate_limited':
        retry_after = e.details.get('retry_after', 60)
        print(f"Rate limited. Retry after {retry_after}s")
    else:
        print(f"API error: {e.message} (request_id: {e.request_id})")

Retry Logic

javascript
const RETRYABLE_ERRORS = [
  'rate_limited',
  'internal_error',
  'service_unavailable',
  'gateway_timeout',
];

async function apiRequestWithRetry(url, options, maxRetries = 3) {
  let lastError;

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await apiRequest(url, options);
    } catch (error) {
      lastError = error;

      if (!RETRYABLE_ERRORS.includes(error.code)) {
        throw error; // Don't retry non-retryable errors
      }

      const delay = error.details?.retry_after || Math.pow(2, attempt) * 1000;
      console.log(`Attempt ${attempt + 1} failed. Retrying in ${delay}ms...`);
      await sleep(delay);
    }
  }

  throw lastError;
}

Debugging Tips

1. Use Request IDs

Always log the request_id from error responses:

javascript
catch (error) {
  console.error(`API Error [request_id: ${error.request_id}]: ${error.message}`);
}

Provide this ID when contacting support.

2. Check Response Headers

javascript
const response = await fetch(url, options);
console.log('X-Request-ID:', response.headers.get('X-Request-ID'));
console.log('X-RateLimit-Remaining:', response.headers.get('X-RateLimit-Remaining'));

3. Validate Locally First

Before sending to the API, validate your data:

javascript
function validateAgent(data) {
  const errors = [];

  if (!data.name) errors.push({ field: 'name', message: 'Required' });
  if (data.name?.length > 100) errors.push({ field: 'name', message: 'Max 100 chars' });

  const validModels = ['gpt-4', 'gpt-3.5-turbo', 'claude-3-sonnet'];
  if (!validModels.includes(data.model)) {
    errors.push({ field: 'model', message: `Must be one of: ${validModels.join(', ')}` });
  }

  if (errors.length > 0) {
    throw new ValidationError('Validation failed', errors);
  }
}

4. Enable Debug Logging

javascript
const DEBUG = process.env.DEBUG === 'true';

async function apiRequest(url, options) {
  if (DEBUG) {
    console.log('Request:', { url, ...options });
  }

  const response = await fetch(url, options);

  if (DEBUG) {
    console.log('Response:', {
      status: response.status,
      headers: Object.fromEntries(response.headers),
    });
  }

  return response;
}

Common Mistakes

1. Not Checking Error Type

javascript
// ❌ Wrong - treats all errors the same
try {
  await apiRequest('/v1/agents', options);
} catch (error) {
  alert('Something went wrong!');
}

// ✅ Right - handle different error types
try {
  await apiRequest('/v1/agents', options);
} catch (error) {
  if (error.code === 'validation_error') {
    showValidationErrors(error.details.errors);
  } else if (error.code === 'unauthorized') {
    redirectToLogin();
  } else {
    showGenericError(error.message);
  }
}

2. Ignoring Rate Limits

javascript
// ❌ Wrong - no rate limit handling
await Promise.all(ids.map(id => fetch(`/v1/agents/${id}`)));

// ✅ Right - respect rate limits
const rateLimiter = new Bottleneck({ minTime: 100 });
await Promise.all(ids.map(id =>
  rateLimiter.schedule(() => fetch(`/v1/agents/${id}`))
));

3. Not Logging Request IDs

javascript
// ❌ Wrong - hard to debug
console.error('API failed:', error.message);

// ✅ Right - includes request ID
console.error(`API failed [request_id: ${error.request_id}]: ${error.message}`);