Docs /api-reference/error-codes

Error Codes Reference

Complete reference of error codes, HTTP status codes, and error handling patterns in Izri.

📋 Overview

All API errors follow consistent patterns for predictable error handling across the platform.

Error Format:

{
  code: string           // Error code
  message: string        // Human-readable message
  data?: any            // Additional context
  path?: string         // tRPC procedure path
  statusCode?: number   // HTTP status code
}

🔴 Authentication Errors

UNAUTHORIZED (401)

Description: No valid authentication credentials provided.

HTTP Status: 401

Common Causes:

  • No session cookie
  • Expired session
  • Invalid session token
  • No API token provided

Examples:

{
  "code": "UNAUTHORIZED",
  "message": "Not authenticated"
}
{
  "code": "UNAUTHORIZED",
  "message": "Session expired"
}
{
  "code": "UNAUTHORIZED",
  "message": "Invalid or expired API token"
}

Client Handling:

if (error.code === 'UNAUTHORIZED') {
  // Clear local state
  queryClient.clear()
  
  // Redirect to login
  window.location.href = '/login'
}

FORBIDDEN (403)

Description: Authenticated but insufficient permissions.

HTTP Status: 403

Common Causes:

  • Insufficient role (e.g., MEMBER trying to delete organization)
  • API token with limited scopes
  • Accessing resource owned by different user/org

Examples:

{
  "code": "FORBIDDEN",
  "message": "Only organization owners can delete organizations"
}
{
  "code": "FORBIDDEN",
  "message": "Insufficient permissions to access this resource"
}

Client Handling:

if (error.code === 'FORBIDDEN') {
  toast.error('You don\'t have permission to perform this action')
}

SESSION_EXPIRED

Description: Session was valid but has now expired.

HTTP Status: 401

Example:

{
  "code": "SESSION_EXPIRED",
  "message": "Your session has expired. Please log in again."
}

Client Handling: Same as UNAUTHORIZED


🔍 Resource Errors

NOT_FOUND (404)

Description: Requested resource doesn't exist or user doesn't have access.

HTTP Status: 404

Common Causes:

  • Invalid resource ID
  • Resource deleted
  • User doesn't have access (privacy)

Examples:

{
  "code": "NOT_FOUND",
  "message": "User not found"
}
{
  "code": "NOT_FOUND",
  "message": "Project not found"
}
{
  "code": "NOT_FOUND",
  "message": "Organization not found"
}

Client Handling:

if (error.code === 'NOT_FOUND') {
  return <NotFoundPage />
}

CONFLICT (409)

Description: Request conflicts with existing resource state.

HTTP Status: 409

Common Causes:

  • Duplicate unique field (email, slug)
  • Resource already exists
  • Concurrent modification

Examples:

{
  "code": "CONFLICT",
  "message": "Project with this repository already exists"
}
{
  "code": "CONFLICT",
  "message": "An organization with this slug already exists"
}
{
  "code": "CONFLICT",
  "message": "User is already a member of this organization"
}

Client Handling:

if (error.code === 'CONFLICT') {
  setFieldError('repository', error.message)
}

✅ Validation Errors

BAD_REQUEST (400)

Description: Invalid request data or parameters.

HTTP Status: 400

Common Causes:

  • Missing required field
  • Invalid format (email, URL)
  • Out of range value
  • Zod validation failure

Examples:

{
  "code": "BAD_REQUEST",
  "message": "Invalid input: name is required",
  "data": {
    "zodError": {
      "fieldErrors": {
        "name": ["Required"]
      }
    }
  }
}
{
  "code": "BAD_REQUEST",
  "message": "Invalid email format"
}

Client Handling:

if (error.code === 'BAD_REQUEST' && error.data?.zodError) {
  const fieldErrors = error.data.zodError.fieldErrors
  
  Object.entries(fieldErrors).forEach(([field, messages]) => {
    setFieldError(field, messages[0])
  })
}

VALIDATION_ERROR

Description: Business logic validation failed.

HTTP Status: 400

Examples:

{
  "code": "VALIDATION_ERROR",
  "message": "Cannot remove the last owner of an organization"
}
{
  "code": "VALIDATION_ERROR",
  "message": "Slug must contain only lowercase letters, numbers, and hyphens"
}

⚠️ Business Logic Errors

PRECONDITION_FAILED (412)

Description: Request cannot be completed due to current state.

HTTP Status: 412

Examples:

{
  "code": "PRECONDITION_FAILED",
  "message": "Cannot change your own role to prevent lockout"
}
{
  "code": "PRECONDITION_FAILED",
  "message": "Project must be analyzed before generating tests"
}

UNPROCESSABLE_ENTITY (422)

Description: Request is well-formed but semantically incorrect.

HTTP Status: 422

Examples:

{
  "code": "UNPROCESSABLE_ENTITY",
  "message": "GitHub token is required for private repositories"
}
{
  "code": "UNPROCESSABLE_ENTITY",
  "message": "Test execution failed: No test files found"
}

🚫 Rate Limiting

TOO_MANY_REQUESTS (429)

Description: Rate limit exceeded.

HTTP Status: 429

Example:

{
  "code": "TOO_MANY_REQUESTS",
  "message": "Rate limit exceeded. Try again in 60 seconds.",
  "data": {
    "limit": 100,
    "remaining": 0,
    "resetAt": "2024-01-01T00:01:00.000Z"
  }
}

Headers:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1704067260
Retry-After: 60

Client Handling:

if (error.code === 'TOO_MANY_REQUESTS') {
  const retryAfter = error.data?.resetAt
  toast.error(`Rate limit exceeded. Try again at ${formatTime(retryAfter)}`)
}

💥 Server Errors

INTERNAL_SERVER_ERROR (500)

Description: Unexpected server error.

HTTP Status: 500

Example:

{
  "code": "INTERNAL_SERVER_ERROR",
  "message": "An unexpected error occurred",
  "data": {
    "requestId": "req_abc123",
    "timestamp": "2024-01-01T00:00:00.000Z"
  }
}

Client Handling:

if (error.code === 'INTERNAL_SERVER_ERROR') {
  console.error('Request ID:', error.data?.requestId)
  toast.error('Something went wrong. Please try again.')
  
  // Optionally report to error tracking
  Sentry.captureException(error, {
    extra: { requestId: error.data?.requestId }
  })
}

SERVICE_UNAVAILABLE (503)

Description: Service temporarily unavailable.

HTTP Status: 503

Common Causes:

  • Database connection failure
  • External service (GitHub API) down
  • Maintenance mode

Example:

{
  "code": "SERVICE_UNAVAILABLE",
  "message": "Service temporarily unavailable",
  "data": {
    "retryAfter": 300
  }
}

TIMEOUT (408)

Description: Request took too long to process.

HTTP Status: 408

Example:

{
  "code": "TIMEOUT",
  "message": "Request timeout - repository analysis is taking longer than expected",
  "data": {
    "timeoutMs": 60000
  }
}

🔧 Integration Errors

EXTERNAL_SERVICE_ERROR

Description: External service (GitHub, AI) failed.

HTTP Status: 502

Examples:

{
  "code": "EXTERNAL_SERVICE_ERROR",
  "message": "GitHub API error: Repository not found",
  "data": {
    "service": "github",
    "statusCode": 404
  }
}
{
  "code": "EXTERNAL_SERVICE_ERROR",
  "message": "AI service unavailable",
  "data": {
    "service": "openai",
    "error": "Rate limit exceeded"
  }
}

📊 Error Handling Patterns

Frontend Global Error Handler

import { TRPCClientError } from '@trpc/client'
import type { AppRouter } from '@izri/trpc'

export function handleTRPCError(error: unknown) {
  if (!(error instanceof TRPCClientError)) {
    console.error('Unknown error:', error)
    return
  }
  
  const trpcError = error as TRPCClientError<AppRouter>
  const code = trpcError.data?.code
  const message = trpcError.message
  
  switch (code) {
    case 'UNAUTHORIZED':
      queryClient.clear()
      window.location.href = '/login'
      break
      
    case 'FORBIDDEN':
      toast.error('You don\'t have permission for this action')
      break
      
    case 'NOT_FOUND':
      toast.error('Resource not found')
      break
      
    case 'CONFLICT':
      toast.error(message)
      break
      
    case 'BAD_REQUEST':
      // Handle validation errors
      if (trpcError.data?.zodError) {
        const fieldErrors = trpcError.data.zodError.fieldErrors
        Object.entries(fieldErrors).forEach(([field, messages]) => {
          console.error(`${field}: ${messages[0]}`)
        })
      }
      break
      
    case 'INTERNAL_SERVER_ERROR':
      toast.error('Something went wrong. Please try again.')
      Sentry.captureException(error)
      break
      
    default:
      toast.error(message || 'An error occurred')
  }
}

Mutation Error Handling

const { mutate } = trpc.projects.createProject.useMutation({
  onError: (error) => {
    if (error.data?.code === 'CONFLICT') {
      setError('repository', {
        type: 'manual',
        message: 'This repository is already added'
      })
    } else if (error.data?.zodError) {
      const fieldErrors = error.data.zodError.fieldErrors
      Object.entries(fieldErrors).forEach(([field, messages]) => {
        setError(field as any, {
          type: 'manual',
          message: messages[0]
        })
      })
    } else {
      toast.error(error.message)
    }
  },
  onSuccess: (data) => {
    toast.success('Project created!')
    navigate(`/projects/${data.project.id}`)
  }
})

Query Error Handling

const { data, error, isError } = trpc.projects.getProject.useQuery(
  { projectId: 'proj_123', userEmail: user.email },
  {
    retry: (failureCount, error) => {
      // Don't retry on client errors
      if (error.data?.code === 'NOT_FOUND') return false
      if (error.data?.code === 'FORBIDDEN') return false
      
      // Retry server errors up to 3 times
      return failureCount < 3
    }
  }
)

if (isError) {
  if (error.data?.code === 'NOT_FOUND') {
    return <NotFoundPage />
  }
  
  return <ErrorBoundary error={error} />
}

Backend Error Throwing

import { TRPCError } from '@trpc/server'

export const projectsRouter = router({
  getProject: protectedProcedure
    .input(z.object({ projectId: z.string() }))
    .query(async ({ ctx, input }) => {
      const project = await db.query.projects.findFirst({
        where: eq(projects.id, input.projectId)
      })
      
      if (!project) {
        throw new TRPCError({
          code: 'NOT_FOUND',
          message: 'Project not found'
        })
      }
      
      if (project.userId !== ctx.user.id) {
        throw new TRPCError({
          code: 'FORBIDDEN',
          message: 'You don\'t have access to this project'
        })
      }
      
      return { project }
    })
})

🔗 HTTP Status Code Reference

Code Name tRPC Code Usage
200 OK - Successful request
201 Created - Resource created
400 Bad Request BAD_REQUEST Validation error
401 Unauthorized UNAUTHORIZED Not authenticated
403 Forbidden FORBIDDEN Insufficient permissions
404 Not Found NOT_FOUND Resource not found
408 Timeout TIMEOUT Request timeout
409 Conflict CONFLICT Resource conflict
412 Precondition Failed PRECONDITION_FAILED State validation failed
422 Unprocessable UNPROCESSABLE_ENTITY Semantic error
429 Too Many Requests TOO_MANY_REQUESTS Rate limit exceeded
500 Internal Error INTERNAL_SERVER_ERROR Server error
502 Bad Gateway EXTERNAL_SERVICE_ERROR External service failed
503 Unavailable SERVICE_UNAVAILABLE Service down

🔗 Related Documentation

Reading this with an agent? /docs/api-reference/error-codes.md serves the raw markdown.

All docs