Skip to main content

Overview

This guide covers all possible errors you may encounter when using the SundayPyjamas AI Suite API, along with best practices for handling them gracefully in your applications.
All API errors follow a consistent JSON format with human-readable error messages and appropriate HTTP status codes.

Error Response Format

Standard Error Format

All API errors return a consistent JSON structure:
{
  "error": "Human-readable error message"
}

Enhanced Error Format

Some errors may include additional fields for better debugging:
{
  "error": "Detailed error message",
  "code": "ERROR_CODE",
  "details": {
    "field": "additional context"
  }
}

HTTP Status Codes

400 Bad Request

Invalid request format or parameters.
{
  "error": "Messages array is required"
}
Cause: Request body doesn’t include a messages arraySolution: Ensure your request includes a valid messages array
const request = { model: "llama-3.3-70b-versatile" };
{
  "error": "Last message must have valid content"
}
Cause: Message missing required content field or empty contentSolution: Ensure all messages have valid role and content fields
const messages = [
  { role: "user" }, // Missing content
  { role: "user", content: "" } // Empty content
];
{
  "error": "Invalid request body"
}
Cause: Malformed JSON or invalid Content-TypeSolution: Ensure proper JSON formatting and Content-Type header
curl -X POST /api/v1/chat \
  -H "Authorization: Bearer API_KEY" \
  -d '{"messages": [{"role": "user", "content": "Hello"}]}'

401 Unauthorized

Authentication issues with your API key.
{
  "error": "Invalid API key"
}
Common Causes:
  • API key doesn’t exist or has been deleted
  • API key format is incorrect
  • API key has been deactivated
Solutions:
  • Verify your API key is correct and active
  • Check the key format: spj_ai_[64-character-string]
  • Generate a new API key if needed
// Check API key format
function validateApiKeyFormat(key) {
  return /^spj_ai_[a-zA-Z0-9_]{32,}$/.test(key);
}

if (!validateApiKeyFormat(apiKey)) {
  console.error("Invalid API key format");
}
{
  "error": "Invalid API key"
}
Cause: Missing or malformed Authorization headerSolution: Include proper Bearer token authorization
fetch('/api/v1/chat', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ messages })
});

403 Forbidden

Permission or limit issues.
{
  "error": "Token limit exceeded"
}
Cause: Workspace has exceeded monthly token quotaSolutions:
  • Wait for monthly reset
  • Upgrade subscription plan
  • Optimize prompts to use fewer tokens
async function handleTokenLimit() {
  try {
    const response = await chatAPI(messages);
    return response;
  } catch (error) {
    if (error.message.includes('Token limit exceeded')) {
      // Implement graceful degradation
      return "I'm temporarily unavailable due to usage limits. Please try again later.";
    }
    throw error;
  }
}
{
  "error": "Insufficient permissions to create API keys"
}
Cause: User doesn’t have required role (owner/admin) for API key managementSolution: Contact workspace owner to grant appropriate permissions

404 Not Found

Resource doesn’t exist.
{
  "error": "API key not found"
}
Cause: Attempting to delete or access a non-existent API keySolution: Verify the API key ID is correct
Cause: Invalid API endpoint URLSolution: Check the API documentation for correct endpoints
const response = await fetch('/api/v2/chat'); // v2 doesn't exist

429 Too Many Requests

Rate limiting applied.
{
  "error": "Rate limit exceeded"
}
Cause: Making requests too quickly Solution: Implement exponential backoff and retry logic
async function retryWithBackoff(fn, maxRetries = 3) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (error.status === 429 && attempt < maxRetries) {
        const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s, 8s
        await new Promise(resolve => setTimeout(resolve, delay));
        continue;
      }
      throw error;
    }
  }
}

500 Internal Server Error

Server-side issues.
{
  "error": "Failed to generate response"
}
Causes:
  • AI model temporarily unavailable
  • Server overload
  • Temporary service disruption
Solution: Implement retry logic with exponential backoff
{
  "error": "Failed to initialize AI model"
}
Cause: AI service configuration issuesSolution: Retry the request; contact support if persistent

502 Bad Gateway

{
  "error": "AI service is temporarily unavailable"
}
Cause: Upstream AI service is down Solution: Retry with exponential backoff

503 Service Unavailable

{
  "error": "Service temporarily unavailable"
}
Cause: Scheduled maintenance or high load Solution: Wait and retry; check status page

Error Handling Patterns

Basic Error Handling

async function basicChatRequest(messages) {
  try {
    const response = await fetch('/api/v1/chat', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${apiKey}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ messages })
    });

    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(`API Error (${response.status}): ${errorData.error}`);
    }

    return await response.text();
  } catch (error) {
    console.error('Chat request failed:', error.message);
    throw error;
  }
}

Comprehensive Error Handling

class APIError extends Error {
  constructor(message, status, code = null) {
    super(message);
    this.name = 'APIError';
    this.status = status;
    this.code = code;
  }
}

class SundayPyjamasClient {
  constructor(apiKey, apiUrl, maxRetries = 3) {
    this.apiKey = apiKey;
    this.apiUrl = apiUrl;
    this.maxRetries = maxRetries;
  }

  async chat(messages, options = {}) {
    const { timeout = 30000 } = options;
    
    for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
      try {
        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), timeout);

        const response = await fetch(`${this.apiUrl}/chat`, {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${this.apiKey}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({ messages }),
          signal: controller.signal
        });

        clearTimeout(timeoutId);

        if (!response.ok) {
          await this.handleErrorResponse(response, attempt);
        }

        return await this.readStreamingResponse(response);

      } catch (error) {
        if (error.name === 'AbortError') {
          throw new APIError('Request timeout', 408);
        }
        
        if (attempt === this.maxRetries) {
          throw error;
        }
        
        // Wait before retry (exponential backoff)
        await this.wait(Math.pow(2, attempt) * 1000);
      }
    }
  }

  async handleErrorResponse(response, attempt) {
    const errorData = await response.json().catch(() => ({ error: 'Unknown error' }));
    
    switch (response.status) {
      case 400:
        throw new APIError(errorData.error, 400, 'BAD_REQUEST');
      
      case 401:
        throw new APIError('Invalid API key', 401, 'UNAUTHORIZED');
      
      case 403:
        if (errorData.error.includes('Token limit')) {
          throw new APIError('Token limit exceeded', 403, 'TOKEN_LIMIT_EXCEEDED');
        }
        throw new APIError(errorData.error, 403, 'FORBIDDEN');
      
      case 404:
        throw new APIError('Endpoint not found', 404, 'NOT_FOUND');
      
      case 429:
        if (attempt < this.maxRetries) {
          // Don't throw immediately for rate limits, let retry logic handle it
          await this.wait(Math.pow(2, attempt + 1) * 1000);
          return;
        }
        throw new APIError('Rate limit exceeded', 429, 'RATE_LIMITED');
      
      case 500:
      case 502:
      case 503:
      case 504:
        if (attempt < this.maxRetries) {
          // Retry server errors
          return;
        }
        throw new APIError('Server error', response.status, 'SERVER_ERROR');
      
      default:
        throw new APIError(errorData.error || 'Unknown error', response.status);
    }
  }

  async readStreamingResponse(response) {
    const reader = response.body.getReader();
    const decoder = new TextDecoder();
    let fullResponse = '';

    try {
      while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        
        const chunk = decoder.decode(value);
        fullResponse += chunk;
      }
      return fullResponse;
    } finally {
      reader.releaseLock();
    }
  }

  wait(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

// Usage with comprehensive error handling
const client = new SundayPyjamasClient(apiKey, apiUrl);

try {
  const response = await client.chat(messages);
  console.log('Success:', response);
} catch (error) {
  if (error instanceof APIError) {
    switch (error.code) {
      case 'UNAUTHORIZED':
        console.error('Authentication failed. Check your API key.');
        break;
      case 'TOKEN_LIMIT_EXCEEDED':
        console.error('Monthly token limit reached. Upgrade plan or wait for reset.');
        break;
      case 'RATE_LIMITED':
        console.error('Too many requests. Please slow down.');
        break;
      case 'BAD_REQUEST':
        console.error('Invalid request:', error.message);
        break;
      default:
        console.error('API error:', error.message);
    }
  } else {
    console.error('Unexpected error:', error.message);
  }
}

Error Recovery Strategies

Graceful Degradation

class ChatService {
  constructor(apiKey) {
    this.client = new SundayPyjamasClient(apiKey);
    this.fallbackResponses = {
      'TOKEN_LIMIT_EXCEEDED': 'I\'m temporarily unavailable due to usage limits. Please try again later.',
      'RATE_LIMITED': 'I\'m receiving too many requests. Please wait a moment and try again.',
      'SERVER_ERROR': 'I\'m experiencing technical difficulties. Please try again in a few minutes.',
      'UNAUTHORIZED': 'There\'s an authentication issue. Please contact support.'
    };
  }

  async chat(messages, options = {}) {
    try {
      return await this.client.chat(messages, options);
    } catch (error) {
      if (error instanceof APIError && this.fallbackResponses[error.code]) {
        return this.fallbackResponses[error.code];
      }
      
      // Log error for debugging but provide user-friendly message
      console.error('Chat service error:', error);
      return 'I\'m currently unavailable. Please try again later.';
    }
  }
}

Circuit Breaker Pattern

class CircuitBreaker {
  constructor(failureThreshold = 5, resetTimeout = 60000) {
    this.failureThreshold = failureThreshold;
    this.resetTimeout = resetTimeout;
    this.failureCount = 0;
    this.lastFailureTime = null;
    this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
  }

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

    try {
      const result = await fn();
      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.failureThreshold) {
      this.state = 'OPEN';
    }
  }
}

// Usage
const circuitBreaker = new CircuitBreaker(3, 30000); // 3 failures, 30s timeout

async function robustChatRequest(messages) {
  try {
    return await circuitBreaker.execute(async () => {
      return await client.chat(messages);
    });
  } catch (error) {
    if (error.message === 'Circuit breaker is OPEN') {
      return 'Service is temporarily unavailable. Please try again later.';
    }
    throw error;
  }
}

Retry with Jitter

async function retryWithJitter(fn, maxRetries = 3, baseDelay = 1000) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (attempt === maxRetries) {
        throw error;
      }
      
      // Don't retry on client errors (4xx)
      if (error instanceof APIError && error.status >= 400 && error.status < 500) {
        throw error;
      }
      
      // Exponential backoff with jitter
      const delay = baseDelay * Math.pow(2, attempt);
      const jitter = Math.random() * 0.1 * delay; // 10% jitter
      await new Promise(resolve => setTimeout(resolve, delay + jitter));
    }
  }
}

Debugging Tips

Enable Detailed Logging

class DebugClient extends SundayPyjamasClient {
  async chat(messages, options = {}) {
    const requestId = Math.random().toString(36).substring(7);
    
    console.log(`[${requestId}] Starting chat request`, {
      messageCount: messages.length,
      totalChars: messages.reduce((sum, m) => sum + m.content.length, 0),
      options
    });

    try {
      const startTime = Date.now();
      const response = await super.chat(messages, options);
      const duration = Date.now() - startTime;
      
      console.log(`[${requestId}] Chat request successful`, {
        duration: `${duration}ms`,
        responseLength: response.length
      });
      
      return response;
    } catch (error) {
      console.error(`[${requestId}] Chat request failed`, {
        error: error.message,
        status: error.status,
        code: error.code
      });
      throw error;
    }
  }
}

Test Error Scenarios

curl -X POST "${API_URL}/chat" \
  -H "Authorization: Bearer invalid_key" \
  -H "Content-Type: application/json" \
  -d '{"messages": [{"role": "user", "content": "test"}]}'

Error Monitoring

class ErrorMonitor {
  constructor() {
    this.errors = [];
    this.errorCounts = new Map();
  }

  logError(error, context = {}) {
    const errorInfo = {
      timestamp: new Date().toISOString(),
      message: error.message,
      status: error.status,
      code: error.code,
      context
    };
    
    this.errors.push(errorInfo);
    
    // Count error types
    const errorKey = `${error.status}-${error.code}`;
    this.errorCounts.set(errorKey, (this.errorCounts.get(errorKey) || 0) + 1);
    
    // Alert on high error rates
    if (this.errorCounts.get(errorKey) > 10) {
      console.warn(`High error rate detected: ${errorKey}`);
    }
  }

  getErrorSummary() {
    return {
      totalErrors: this.errors.length,
      errorBreakdown: Object.fromEntries(this.errorCounts),
      recentErrors: this.errors.slice(-10)
    };
  }
}

const errorMonitor = new ErrorMonitor();

// Use in your error handling
catch (error) {
  errorMonitor.logError(error, { userId, requestId });
  // ... handle error
}

Best Practices Summary

Always Handle Errors

Implement comprehensive error handling for all API calls

Use Exponential Backoff

Retry with increasing delays for transient errors

Graceful Degradation

Provide fallback responses when the API is unavailable

Monitor Error Patterns

Track error frequencies to identify and fix issues

Validate Inputs

Validate requests before sending to avoid 400 errors

Secure API Keys

Protect API keys and handle auth errors appropriately

Log for Debugging

Implement detailed logging for troubleshooting

Circuit Breaker

Use circuit breakers to prevent cascade failures

Next Steps