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 '@vettly/sdk'

const client = new ModerationClient({ apiKey: 'vettly_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="vettly_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: 'vettly_live_...',
  maxRetries: 5,      // Default: 3
  retryDelay: 2000,   // Default: 1000ms
  timeout: 60000,     // Default: 30000ms
})
python
client = ModerationClient(
    api_key="vettly_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 ​