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
- tRPC Routers - API endpoints
- REST Endpoints - REST API errors
- Authentication API - Auth errors
- Backend Error Handling - Server-side error handling
- Frontend Error Handling - UI error boundaries