Skip to content

Error Handling

Handle moderation errors gracefully in production.

Error Types

Vettly returns specific HTTP status codes for different error conditions:

StatusErrorCause
401UnauthorizedInvalid or missing API key
402Payment RequiredQuota exceeded
422Unprocessable EntityInvalid request parameters
429Too Many RequestsRate limit exceeded
5xxServer ErrorInternal error (retry automatically)

SDK Error Handling

Both TypeScript and Python SDKs provide typed exceptions:

typescript
import {
  ModerationClient,
  VettlyAuthError,
  VettlyRateLimitError,
  VettlyQuotaError,
  VettlyValidationError,
  VettlyError,
} from '@nextauralabs/vettly-sdk'

const client = new ModerationClient({ apiKey: 'sk_live_...' })

try {
  const result = await client.check({
    content: 'User message',
    policyId: 'moderate',
    contentType: 'text'
  })
} catch (error) {
  if (error instanceof VettlyAuthError) {
    // 401: Invalid API key
    console.error('Check your API key')
  } else if (error instanceof VettlyRateLimitError) {
    // 429: Rate limited
    console.error(`Retry after ${error.retryAfter}s`)
  } else if (error instanceof VettlyQuotaError) {
    // 402: Quota exceeded
    console.error(`Upgrade plan: ${error.quota}`)
  } else if (error instanceof VettlyValidationError) {
    // 422: Invalid request
    console.error(`Fix request: ${error.errors}`)
  } else if (error instanceof VettlyError) {
    // 5xx: Server error (SDK auto-retries these)
    console.error('Server error, try again later')
  }
}
python
from vettly import (
    ModerationClient,
    VettlyAuthError,
    VettlyRateLimitError,
    VettlyQuotaError,
    VettlyValidationError,
    VettlyServerError,
)

client = ModerationClient(api_key="sk_live_...")

try:
    result = client.check(content="User message", policy_id="moderate")
except VettlyAuthError:
    # 401: Invalid API key
    print("Check your API key")
except VettlyRateLimitError as e:
    # 429: Rate limited
    print(f"Retry after {e.retry_after}s")
except VettlyQuotaError as e:
    # 402: Quota exceeded
    print(f"Upgrade plan: {e.quota}")
except VettlyValidationError as e:
    # 422: Invalid request
    print(f"Fix request: {e.errors}")
except VettlyServerError:
    # 5xx: Server error (SDK auto-retries these)
    print("Server error, try again later")

Automatic Retry

Both SDKs automatically retry failed requests with exponential backoff:

  • Retried: Rate limits (429) and server errors (5xx)
  • Not retried: Auth errors (401), validation errors (422), quota errors (402)
  • Backoff: 1s, 2s, 4s, etc. (configurable)
  • Max retries: 3 by default (configurable)
  • Retry-After: Respects the Retry-After header from the API

Configuration

typescript
const client = new ModerationClient({
  apiKey: 'sk_live_...',
  maxRetries: 5,      // Default: 3
  retryDelay: 2000,   // Default: 1000ms
  timeout: 60000,     // Default: 30000ms
})
python
client = ModerationClient(
    api_key="sk_live_...",
    max_retries=5,      # Default: 3
    retry_delay=2.0,    # Default: 1.0s
    timeout=60.0,       # Default: 30.0s
)

Fail Open vs Fail Closed

Choose a strategy for when moderation fails:

Fail Open (Allow on Error)

Allow content through when moderation is unavailable. Use when:

  • Uptime is more important than safety
  • You have other safeguards (human review queue)
  • Content is low-risk (internal tools)
typescript
async function moderateContent(content: string): Promise<boolean> {
  try {
    const result = await client.check({
      content,
      policyId: 'moderate',
      contentType: 'text'
    })
    return result.action !== 'block'
  } catch (error) {
    // Fail open: allow on error
    console.error('Moderation unavailable:', error)
    return true
  }
}
python
def moderate_content(content: str) -> bool:
    try:
        result = client.check(content=content, policy_id="moderate")
        return result.action != "block"
    except Exception as e:
        # Fail open: allow on error
        print(f"Moderation unavailable: {e}")
        return True

Fail Closed (Block on Error)

Block content when moderation is unavailable. Use when:

  • Safety is more important than uptime
  • Content is high-risk (public-facing, children's app)
  • You can afford to reject valid content temporarily
typescript
async function moderateContent(content: string): Promise<boolean> {
  try {
    const result = await client.check({
      content,
      policyId: 'moderate',
      contentType: 'text'
    })
    return result.action !== 'block'
  } catch (error) {
    // Fail closed: block on error
    console.error('Moderation unavailable:', error)
    return false
  }
}
python
def moderate_content(content: str) -> bool:
    try:
        result = client.check(content=content, policy_id="moderate")
        return result.action != "block"
    except Exception as e:
        # Fail closed: block on error
        print(f"Moderation unavailable: {e}")
        return False

Hybrid: Queue for Review

Queue content for human review when moderation fails:

typescript
async function moderateContent(content: string, userId: string) {
  try {
    const result = await client.check({
      content,
      policyId: 'moderate',
      contentType: 'text'
    })

    if (result.action === 'block') {
      return { allowed: false, reason: 'blocked' }
    }
    return { allowed: true }

  } catch (error) {
    // Queue for human review
    await reviewQueue.add({
      content,
      userId,
      error: error.message,
      createdAt: new Date()
    })

    // Allow through but flag for review
    return { allowed: true, pendingReview: true }
  }
}

Logging and Monitoring

Log moderation decisions for debugging and analytics:

typescript
const result = await client.check({
  content,
  policyId: 'moderate',
  contentType: 'text'
})

// Log decision
console.log({
  decisionId: result.decisionId,
  action: result.action,
  safe: result.safe,
  latency: result.latency,
  categories: result.categories.filter(c => c.triggered)
})

Track error rates in your monitoring:

typescript
try {
  await client.check({ ... })
  metrics.increment('moderation.success')
} catch (error) {
  metrics.increment('moderation.error', { type: error.constructor.name })
  throw error
}

Circuit Breaker Pattern

For high-traffic applications, consider a circuit breaker:

typescript
import CircuitBreaker from 'opossum'

const breaker = new CircuitBreaker(
  (content: string) => client.check({
    content,
    policyId: 'moderate',
    contentType: 'text'
  }),
  {
    timeout: 5000,           // 5s timeout
    errorThresholdPercentage: 50,
    resetTimeout: 30000      // Try again after 30s
  }
)

breaker.on('open', () => {
  console.warn('Moderation circuit opened - falling back')
})

// Use the breaker
const result = await breaker.fire(content)

Next Steps